[WPF 自定义控件]在MenuItem上使用RadioButton
2021-03-18 08:27
标签:frame emc 方法 comm efi 重写 menus 方便 OLE
上图这种包含多选(CheckBox)和单选(RadioButton)的菜单十分常见,可是在WPF中只提供了多选的MenuItem。顺便一提,要使MenuItem可以多选,只需要将MenuItem的1. 需求#
IsCheckable
属性设置为True:MenuItem IsCheckable="True"/>
不知出于何种考虑,WPF没有为MenuItem提供单选的功能。为了在MenuItem中添加RadioButton,可以尝试修改样式并在CodeBehind找那个处理MenuItem的Click事件,但这种事做多了还是做成一个自定义控件比较方便。这篇文章将介绍如何自定义一个RadioButtonMenuItem
控件实现MenuItem的单选功能。
2. 实现代码#
RadioButtonMenuItem
的代码比较简单(换言之,样式部分比较难),首先继承自MenuItem
,然后模仿RadioButton
添加一个GroupName属性:
Copypublic class RadioButtonMenuItem : MenuItem { ///
/// 标识 GroupName 依赖属性。 /// public static readonly DependencyProperty GroupNameProperty = DependencyProperty.Register(nameof(GroupName), typeof(string), typeof(RadioButtonMenuItem), new PropertyMetadata(default(string))); static RadioButtonMenuItem() { DefaultStyleKeyProperty.OverrideMetadata(typeof(RadioButtonMenuItem), new FrameworkPropertyMetadata(typeof(RadioButtonMenuItem))); } /// /// 获取或设置GroupName的值 /// public string GroupName { get { return (string)GetValue(GroupNameProperty); } set { SetValue(GroupNameProperty, value); } }
RadioButtonMenuItem
的分组规则很简单,只要同一个MenuItem下的RadioButtonMenuItem
为一组,然后再根据GroupName分组。因为我很少会更改GroupName,所以就难得监视GroupName的改变了。
因为MenuItem派生自ItemsControl,所以需要重写GetContainerForItemOverride
以确定它的Items也是用RadioButtonMenuItem
作为默认的ItemContainer:
Copyprotected override DependencyObject GetContainerForItemOverride() { return new RadioButtonMenuItem(); }
然后重写OnClick
,让RadioButtonMenuItem
每次点击都被选中,这个行为和RadioButton一致:
Copyprotected override void OnClick() { base.OnClick(); IsChecked = true; }
最后重写OnClick
函数,在这个函数里面找出在同一个MenuItem下且GroupName一样的RadioButtonMenuItem,将他们的IsChecked
全部设置为False,这样就实现了MenuItem的单选功能:
Copyprotected override void OnChecked(RoutedEventArgs e) { base.OnChecked(e); if (this.Parent is MenuItem parent) { foreach (var menuItem in parent.Items.OfType
()) { if (menuItem != this && menuItem.GroupName == GroupName && (menuItem.DataContext == parent.DataContext || menuItem.DataContext != DataContext)) { menuItem.IsChecked = false; } } } }
3. 实现样式#
MenuItem有一个Role属性,它的类型为MenuItemRole,定义如下:
Copy// // 摘要: // Defines the different roles that a System.Windows.Controls.MenuItem can have. public enum MenuItemRole { // // 摘要: // Top-level menu item that can invoke commands. TopLevelItem = 0, // // 摘要: // Header for top-level menus. TopLevelHeader = 1, // // 摘要: // Menu item in a submenu that can invoke commands. SubmenuItem = 2, // // 摘要: // Header for a submenu. SubmenuHeader = 3 }
根据MenuItem所处的位置,它的Role会有不同的值,大致上如下面例子所示:
CopyMenu x:Name="Men"> MenuItem Header="TopLevelItem" /> MenuItem Header="TopLevelHeader"> MenuItem Header="SubMenuHeader"> MenuItem Header="SubMenuItem" /> MenuItem> MenuItem Header="SubMenuItem" /> MenuItem> Menu>
MenuItem的样式麻烦之处就在这里。因为微软并没有在文档中提供Aero2的样式,所以在以前要获取一个控件的样式标准的做法是使用Blend选中控件后编辑控件的模板,但因为MenuItem会有不同的Role,所以它当前的模板会不一样,用Blend很难获取到它的全部的模板。大致上它的样式定义如下:
CopyControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelItemTemplateKey}" TargetType="{x:Type MenuItem}"> ControlTemplate> ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelHeaderTemplateKey}" TargetType="{x:Type MenuItem}"> ControlTemplate> ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}" TargetType="{x:Type MenuItem}"> ControlTemplate> ControlTemplate x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuHeaderTemplateKey}" TargetType="{x:Type MenuItem}"> ControlTemplate> Style x:Key="{x:Type local:RadioButtonMenuItem}" TargetType="{x:Type local:RadioButtonMenuItem}"> Setter Property="Control.Template" Value="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuItemTemplateKey}}" /> Style.Triggers> Trigger Property="MenuItem.Role" Value="TopLevelHeader"> Setter Property="Control.Template" Value="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelHeaderTemplateKey}}" /> Setter Property="Control.Padding" Value="6,0" /> Trigger> Trigger Property="MenuItem.Role" Value="TopLevelItem"> Setter Property="Control.Template" Value="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=TopLevelItemTemplateKey}}" /> Setter Property="Control.Padding" Value="6,0" /> Trigger> Trigger Property="MenuItem.Role" Value="SubmenuHeader"> Setter Property="Control.Template" Value="{StaticResource {ComponentResourceKey TypeInTargetAssembly={x:Type MenuItem}, ResourceId=SubmenuHeaderTemplateKey}}" /> Trigger> Style.Triggers> Style>
除了使用Blend,以前还可以使用ILSpy反编译出它的资源文件获取控件的样式。幸好现在WPF开元了,Aero2的样式也可以在 Github 上找到。大概500行的样子,虽然大致上只需要将CheckBox的?
换成一个圆点,但分别搞四次加上些细微的调整把我搞糊涂了。因为它只提供了Aero2的样式,如果要用在Win7最好再定义一个Aero的样式,或者直接将全局样式改为Aero2,我在 这篇文章 里介绍了如何在Win7使用Aero2的样式,可供参考。
修改完模板后效果就如文章开头的图片一样了,使用方法如下:
Copykino:RadioButtonMenuItem Header="MoreOptions"> kino:RadioButtonMenuItem Header="Option 1" GroupName="GroupA" /> kino:RadioButtonMenuItem Header="Option 2" GroupName="GroupA" /> kino:RadioButtonMenuItem Header="Option 3" GroupName="GroupA" /> Separator /> kino:RadioButtonMenuItem Header="Option 4" GroupName="GroupB" /> kino:RadioButtonMenuItem Header="Option 5" GroupName="GroupB" /> kino:RadioButtonMenuItem Header="Option 6" GroupName="GroupB" /> Separator /> kino:RadioButtonMenuItem Header="Options "> kino:RadioButtonMenuItem Header="Option 7" GroupName="GroupC" /> kino:RadioButtonMenuItem Header="Option 8" GroupName="GroupC" /> kino:RadioButtonMenuItem Header="Option 9" GroupName="GroupC" /> kino:RadioButtonMenuItem> Separator /> MenuItem IsCheckable="True" Header="Option X" /> MenuItem IsCheckable="True" Header="Option Y" /> MenuItem IsCheckable="True" Header="Option Z" /> kino:RadioButtonMenuItem>
4. 参考#
MenuItem Class (System.Windows.Controls) _ Microsoft Docs
MenuItemRole Enum (System.Windows.Controls) _ Microsoft Docs
RadioButton Class (System.Windows.Controls) _ Microsoft Docs
? WPF MenuItem as a RadioButton WPF
wpf_MenuItem.xaml at master · dotnet_wpf
5. 源码#
RadioButtonMenuItem.cs at master
[WPF 自定义控件]在MenuItem上使用RadioButton
标签:frame emc 方法 comm efi 重写 menus 方便 OLE
原文地址:https://www.cnblogs.com/lonelyxmas/p/12356048.html
文章标题:[WPF 自定义控件]在MenuItem上使用RadioButton
文章链接:http://soscw.com/index.php/essay/65705.html