惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
博客园 - 司徒正美
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog

博客园 - 八爻老骥

Gradle模块化项目中使用了非模块化库的编译方法 系统架构一一前端技术 系统架构一一ORM的应用 系统架构——多线程的应用 系统架构——前后端分离 为什么要学DirectX? D3D实战-在窗口中画一个三角形 - 八爻老骥 - 博客园 DirectX 入门1-初识DirectX Tool Kit DirectX 12 版 俄罗斯方块 WPF俄罗斯方块改进版 - 八爻老骥 - 博客园 【编程漫谈】用JAVA画多边形 【编程漫谈】单机与联机 往Angular应用程序中添加DevExtreme 【编程漫谈】程序的运行环境 创建DevExtreme应用程序 【编程漫谈】程序的运行流程 【编程漫谈】程序的进入与退出 【编程漫谈】Hello world! WPF实战俄罗斯方块
WPF下的RibbonApplicationMenu控件自定义
八爻老骥 · 2020-05-03 · via 博客园 - 八爻老骥

.Net Framework 4.5和.Net Core开始提供类似Office界面风格的控件。没事想抄个Windows自带的画图玩玩,Ribbon窗口往一套,倒也简单,只是细节方面有些不同,最明显的就是主菜单部分,画图里是扁平的形状,并且内容是用的文本。然而RibbonApplicationMenu去没有提供这样的选项,只能使用图片,而且没有地方可以设置。如果想做得跟画图一样,那就要定制这个菜单了。好在Xaml的机制下可以比较灵活地修改内容模板。以下就说说修改的过程。

定制WPF控件有两种方法,一种是通过继承,另一种是就是新建内容模板。前一种一般是要增加控件的新功能,后一种只是为了修改显式样式。这里只是为了修改显示样式,所以选择后一种就行了。

开始之前,先了解一下Ribbon窗口的结构。

Ribbon

从图中可以看出,这是一个复合控件。如果是简单控件,直接重给控件内容就行了,比如自定义一个圆形按钮,

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <Ellipse x:Name="backgroundElement" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding Foreground}" />
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</ControlTemplate>

但如果重绘一个复合控件,只是这么简单的绘制的话,会发现控件不可用了,比如一个ComboBox控件,它实际上包含一个Textbox、一个ToggleButton、一个弹出窗口以及菜单项的模板。这些子控件称为Parts,每个part有一个名称,在呈现控件树的时候使用。

ComboBox 有两个part
PART_EditableTextBox ComboBox的文本内容.
PART_Popup Popup 下拉弹出窗口.

同样,RibbonApplicationMenu也有5个part,分别是
PART_PopupToggleButton 默认按扭,类型为RibbonToggleButton
PART_Popup 下拉框,类型为Popup
PART_SubmenuPlaceholder 子菜单容器,类型为一个容器,比如StackPanel
PART_FooterPaneContentPresenter 底部内容,类型为ContentPresenter
PART_AuxiliaryPaneContentPresenter 右侧内容,类型为ContentPresenter

我们定义这个按钮:

<RibbonToggleButton x:Name="PART_ToggleButton" ClickMode="Release" Label="文件" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White" Background="#FF3E3EC7" Focusable="False" IsTabStop="False" FocusedBackground="#FF6161F7" CornerRadius="0,0,0,0" MouseOverBackground="#FF6161F7" MouseOverBorderBrush="#FF6161F7"/>

这里需要注意的两个属性,一个是ClickMode,要设置成Release,否则因为绑定的原因不能正常触发,将CornerRadius设置成"0,0,0,0",默认是“2,2,2,2”。

有了按钮后,还只能触发IsDropDownOpen属性的变化,接下来就要根据这个属性来触发弹框了。我们要定义一个Popup.

<Popup x:Name="PART_Popup" IsOpen="{TemplateBinding IsDropDownOpen}" PopupAnimation="Slide" Placement="Relative">
</Popup>

有了这个Popup,点击蓝色的按钮就看一个弹出的小框框。接下来完善一下其它部分的内容:

<Popup x:Name="PART_Popup" IsOpen="{TemplateBinding IsDropDownOpen}" PopupAnimation="Slide" Placement="Relative" Width="450">
                                    <Border Background="#FFE6E6E6" BorderBrush="#FF5EADCB" BorderThickness="1,1,1,1">
                                        <Grid MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxHeight}">
                                            <Grid.RowDefinitions>
                                                <RowDefinition/>
                                                <RowDefinition/>
                                                <RowDefinition/>
                                            </Grid.RowDefinitions>
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition/>
                                                <ColumnDefinition/>
                                            </Grid.ColumnDefinitions>
                                            <Border Grid.Row="1" Width="150">
                                                <StackPanel  IsItemsHost="True"/>
                                            </Border>
                                            <Border Grid.Row="1" Grid.Column="1" BorderBrush="Gray" Width="150">
                                                <ContentPresenter x:Name="PART_AuxiliaryPaneContentPresenter"  ContentTemplateSelector="{TemplateBinding AuxiliaryPaneContentTemplateSelector}" Content="{TemplateBinding AuxiliaryPaneContent}" ContentTemplate="{TemplateBinding AuxiliaryPaneContentTemplate}"/>
                                            </Border>
                                            <Border Grid.Row="2" Grid.ColumnSpan="2" Height="30">
                                                <ContentPresenter x:Name="PART_FooterPaneContentPresenter"  Content="{TemplateBinding FooterPaneContent}" ContentTemplate="{TemplateBinding FooterPaneContentTemplate}" ContentTemplateSelector="{TemplateBinding FooterPaneContentTemplateSelector}"/>
                                            </Border>
                                            <RibbonToggleButton x:Name="PART_PopupToggleButton" ClickMode="Release" Label="文件" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White" Background="#FF3E3EC7" Focusable="False" IsTabStop="False" IsHitTestVisible="True" HorizontalAlignment="Left" VerticalAlignment="Center" Height="{Binding ActualHeight, ElementName=PART_ToggleButton, Mode=OneWay}" Width="{Binding ActualWidth, ElementName=PART_ToggleButton, Mode=OneWay}" FocusedBackground="#FF6161F7" CornerRadius="0,0,0,0" MouseOverBackground="#FF6161F7" MouseOverBorderBrush="#FF6161F7" />

                                        </Grid>
                                    </Border>
                                </Popup>

这个代码中多了一个RibbonToggleButton,画图程序菜单里有这么一个按钮,这个也是RibbonApplicationMenu的一个Part,实际是继承了RibbonMenuButton的内容,画图里将这个按钮显示在了最上方,如果没这个按钮,主菜单按钮会被挡在下边,为了保持外观的一至性,在弹出框再放一个相同的按钮。同样的,Windows资源管理器也是相同的设计。

整个模板的实现代码如下:

<ControlTemplate TargetType="RibbonApplicationMenu">
                            <Grid>
                                <Popup x:Name="PART_Popup" IsOpen="{TemplateBinding IsDropDownOpen}" PopupAnimation="Slide" Placement="Relative" Width="450">
                                    <Border Background="#FFE6E6E6" BorderBrush="#FF5EADCB" BorderThickness="1,1,1,1">
                                        <Grid MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxHeight}">
                                            <Grid.RowDefinitions>
                                                <RowDefinition/>
                                                <RowDefinition/>
                                                <RowDefinition/>
                                            </Grid.RowDefinitions>
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition/>
                                                <ColumnDefinition/>
                                            </Grid.ColumnDefinitions>
                                            <Border Grid.Row="1" Width="150">
                                                <StackPanel  IsItemsHost="True"/>
                                            </Border>
                                            <Border Grid.Row="1" Grid.Column="1" BorderBrush="Gray" Width="150">
                                                <ContentPresenter x:Name="PART_AuxiliaryPaneContentPresenter"  ContentTemplateSelector="{TemplateBinding AuxiliaryPaneContentTemplateSelector}" Content="{TemplateBinding AuxiliaryPaneContent}" ContentTemplate="{TemplateBinding AuxiliaryPaneContentTemplate}"/>
                                            </Border>
                                            <Border Grid.Row="2" Grid.ColumnSpan="2" Height="30">
                                                <ContentPresenter x:Name="PART_FooterPaneContentPresenter"  Content="{TemplateBinding FooterPaneContent}" ContentTemplate="{TemplateBinding FooterPaneContentTemplate}" ContentTemplateSelector="{TemplateBinding FooterPaneContentTemplateSelector}"/>
                                            </Border>
                                            <RibbonToggleButton x:Name="PART_PopupToggleButton" ClickMode="Release" Label="文件" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White" Background="#FF3E3EC7" Focusable="False" IsTabStop="False" IsHitTestVisible="True" HorizontalAlignment="Left" VerticalAlignment="Center" Height="{Binding ActualHeight, ElementName=PART_ToggleButton, Mode=OneWay}" Width="{Binding ActualWidth, ElementName=PART_ToggleButton, Mode=OneWay}" FocusedBackground="#FF6161F7" CornerRadius="0,0,0,0" MouseOverBackground="#FF6161F7" MouseOverBorderBrush="#FF6161F7" />

                                        </Grid>
                                    </Border>
                                </Popup>
                                <RibbonToggleButton x:Name="PART_ToggleButton" ClickMode="Release" Label="文件" IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Foreground="White" Background="#FF3E3EC7" Focusable="False" IsTabStop="False" FocusedBackground="#FF6161F7" CornerRadius="0,0,0,0" MouseOverBackground="#FF6161F7" MouseOverBorderBrush="#FF6161F7"/>


                            </Grid>

                        </ControlTemplate>