TER*_*ytE 44 c# wpf xaml menuitem
给出以下代码:
<MenuItem x:Name="MenuItem_Root" Header="Root">
<MenuItem x:Name="MenuItem_Item1" IsCheckable="True" Header="item1" />
<MenuItem x:Name="MenuItem_Item2" IsCheckable="True" Header="item2"/>
<MenuItem x:Name="MenuItem_Item3" IsCheckable="True" Header="item3"/>
</MenuItem>
Run Code Online (Sandbox Code Playgroud)
在XAML中,有没有办法创建相互排斥的可检查菜单项?用户在哪里检查item2,项目的1和3将自动取消选中.
我可以通过监视菜单上的点击事件,确定检查了哪个项目以及取消选中其他菜单项来完成后面的代码.我想有一种更简单的方法.
有任何想法吗?
Pat*_*ick 46
这可能不是您正在寻找的,但您可以为MenuItem该类编写一个扩展,允许您使用类的GroupName属性RadioButton.我稍微修改了这个方便的示例,用于类似扩展ToggleButton控件,并根据您的情况重新设计了一些,并提出了这个:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace WpfTest
{
public class MenuItemExtensions : DependencyObject
{
public static Dictionary<MenuItem, String> ElementToGroupNames = new Dictionary<MenuItem, String>();
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.RegisterAttached("GroupName",
typeof(String),
typeof(MenuItemExtensions),
new PropertyMetadata(String.Empty, OnGroupNameChanged));
public static void SetGroupName(MenuItem element, String value)
{
element.SetValue(GroupNameProperty, value);
}
public static String GetGroupName(MenuItem element)
{
return element.GetValue(GroupNameProperty).ToString();
}
private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Add an entry to the group name collection
var menuItem = d as MenuItem;
if (menuItem != null)
{
String newGroupName = e.NewValue.ToString();
String oldGroupName = e.OldValue.ToString();
if (String.IsNullOrEmpty(newGroupName))
{
//Removing the toggle button from grouping
RemoveCheckboxFromGrouping(menuItem);
}
else
{
//Switching to a new group
if (newGroupName != oldGroupName)
{
if (!String.IsNullOrEmpty(oldGroupName))
{
//Remove the old group mapping
RemoveCheckboxFromGrouping(menuItem);
}
ElementToGroupNames.Add(menuItem, e.NewValue.ToString());
menuItem.Checked += MenuItemChecked;
}
}
}
}
private static void RemoveCheckboxFromGrouping(MenuItem checkBox)
{
ElementToGroupNames.Remove(checkBox);
checkBox.Checked -= MenuItemChecked;
}
static void MenuItemChecked(object sender, RoutedEventArgs e)
{
var menuItem = e.OriginalSource as MenuItem;
foreach (var item in ElementToGroupNames)
{
if (item.Key != menuItem && item.Value == GetGroupName(menuItem))
{
item.Key.IsChecked = false;
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后,在XAML中,你会写:
<MenuItem x:Name="MenuItem_Root" Header="Root">
<MenuItem x:Name="MenuItem_Item1" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item1" />
<MenuItem x:Name="MenuItem_Item2" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item2"/>
<MenuItem x:Name="MenuItem_Item3" YourNamespace:MenuItemExtensions.GroupName="someGroup" IsCheckable="True" Header="item3"/>
</MenuItem>
Run Code Online (Sandbox Code Playgroud)
这有点痛苦,但它提供了不要强迫你编写任何额外的程序代码(当然除了扩展类)来实现它的好处.
感谢撰写原始ToggleButton解决方案的Brad Cunningham.
您还可以使用行为.像这个:
<MenuItem Header="menu">
<MenuItem x:Name="item1" Header="item1" IsCheckable="true" ></MenuItem>
<MenuItem x:Name="item2" Header="item2" IsCheckable="true"></MenuItem>
<MenuItem x:Name="item3" Header="item3" IsCheckable="true" ></MenuItem>
<i:Interaction.Behaviors>
<local:MenuItemButtonGroupBehavior></local:MenuItemButtonGroupBehavior>
</i:Interaction.Behaviors>
</MenuItem>
public class MenuItemButtonGroupBehavior : Behavior<MenuItem>
{
protected override void OnAttached()
{
base.OnAttached();
GetCheckableSubMenuItems(AssociatedObject)
.ToList()
.ForEach(item => item.Click += OnClick);
}
protected override void OnDetaching()
{
base.OnDetaching();
GetCheckableSubMenuItems(AssociatedObject)
.ToList()
.ForEach(item => item.Click -= OnClick);
}
private static IEnumerable<MenuItem> GetCheckableSubMenuItems(ItemsControl menuItem)
{
var itemCollection = menuItem.Items;
return itemCollection.OfType<MenuItem>().Where(menuItemCandidate => menuItemCandidate.IsCheckable);
}
private void OnClick(object sender, RoutedEventArgs routedEventArgs)
{
var menuItem = (MenuItem)sender;
if (!menuItem.IsChecked)
{
menuItem.IsChecked = true;
return;
}
GetCheckableSubMenuItems(AssociatedObject)
.Where(item => item != menuItem)
.ToList()
.ForEach(item => item.IsChecked = false);
}
}
Run Code Online (Sandbox Code Playgroud)
由于我还没有声誉,所以在底部添加这个...
尽管Patrick的答案很有帮助,但它并不能确保不能取消选中项目.为此,应将Checked处理程序更改为Click处理程序,并更改为以下内容:
static void MenuItemClicked(object sender, RoutedEventArgs e)
{
var menuItem = e.OriginalSource as MenuItem;
if (menuItem.IsChecked)
{
foreach (var item in ElementToGroupNames)
{
if (item.Key != menuItem && item.Value == GetGroupName(menuItem))
{
item.Key.IsChecked = false;
}
}
}
else // it's not possible for the user to deselect an item
{
menuItem.IsChecked = true;
}
}
Run Code Online (Sandbox Code Playgroud)
由于没有答案,我在这里发布我的解决方案:
public class RadioMenuItem : MenuItem
{
public string GroupName { get; set; }
protected override void OnClick()
{
var ic = Parent as ItemsControl;
if (null != ic)
{
var rmi = ic.Items.OfType<RadioMenuItem>().FirstOrDefault(i =>
i.GroupName == GroupName && i.IsChecked);
if (null != rmi) rmi.IsChecked = false;
IsChecked = true;
}
base.OnClick();
}
}
Run Code Online (Sandbox Code Playgroud)
在XAML中,只需将其用作常规MenuItem:
<MenuItem Header="OOO">
<local:RadioMenuItem Header="111" GroupName="G1"/>
<local:RadioMenuItem Header="222" GroupName="G1"/>
<local:RadioMenuItem Header="333" GroupName="G1"/>
<local:RadioMenuItem Header="444" GroupName="G1"/>
<local:RadioMenuItem Header="555" GroupName="G1"/>
<local:RadioMenuItem Header="666" GroupName="G1"/>
<Separator/>
<local:RadioMenuItem Header="111" GroupName="G2"/>
<local:RadioMenuItem Header="222" GroupName="G2"/>
<local:RadioMenuItem Header="333" GroupName="G2"/>
<local:RadioMenuItem Header="444" GroupName="G2"/>
<local:RadioMenuItem Header="555" GroupName="G2"/>
<local:RadioMenuItem Header="666" GroupName="G2"/>
</MenuItem>
Run Code Online (Sandbox Code Playgroud)
非常简单干净。当然,您可以GroupName通过一些其他代码将dependency属性设置为其他属性。
顺便说一句,如果您不喜欢复选标记,可以将其更改为您喜欢的任何内容:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var p = GetTemplateChild("Glyph") as Path;
if (null == p) return;
var x = p.Width/2;
var y = p.Height/2;
var r = Math.Min(x, y) - 1;
var e = new EllipseGeometry(new Point(x,y), r, r);
// this is just a flattened dot, of course you can draw
// something else, e.g. a star? ;)
p.Data = e.GetFlattenedPathGeometry();
}
Run Code Online (Sandbox Code Playgroud)
如果您RadioMenuItem在程序中使用了大量这些功能,则下面显示另一个更有效的版本。文字数据是从e.GetFlattenedPathGeometry().ToString()以前的代码片段中获取的。
private static readonly Geometry RadioDot = Geometry.Parse("M9,5.5L8.7,7.1 7.8,8.3 6.6,9.2L5,9.5L3.4,9.2 2.2,8.3 1.3,7.1L1,5.5L1.3,3.9 2.2,2.7 3.4,1.8L5,1.5L6.6,1.8 7.8,2.7 8.7,3.9L9,5.5z");
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var p = GetTemplateChild("Glyph") as Path;
if (null == p) return;
p.Data = RadioDot;
}
Run Code Online (Sandbox Code Playgroud)
最后,如果您打算包装它以便在项目中使用,则应该IsCheckable对基类隐藏属性,因为类的自动检查机制MenuItem会导致无线电检查状态标记错误的行为。
private new bool IsCheckable { get; }
Run Code Online (Sandbox Code Playgroud)
因此,如果新手尝试编译XAML,则VS将给出错误消息:
//请注意,这是错误的用法!
<local:RadioMenuItem Header="111" GroupName="G1" IsCheckable="True"/>//请注意,这是错误的用法!
是的,这可以通过使每个MenuItem成为RadioButton来轻松完成.这可以通过编辑MenuItem的模板来完成.
右键单击Document-Outline左窗格> EditTemplate> EditCopy中的MenuItem.这将在Window.Resources下添加编辑代码.
现在,您只需进行两项非常简单的更改.
一个.使用一些资源添加RadioButton以隐藏其圆圈部分.
湾 为MenuItem Border部分更改BorderThickness = 0.
这些更改在下面显示为注释,生成的样式的其余部分应按原样使用:
<Window.Resources>
<LinearGradientBrush x:Key="MenuItemSelectionFill" EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#34C5EBFF" Offset="0"/>
<GradientStop Color="#3481D8FF" Offset="1"/>
</LinearGradientBrush>
<Geometry x:Key="Checkmark">M 0,5.1 L 1.7,5.2 L 3.4,7.1 L 8,0.4 L 9.2,0 L 3.3,10.8 Z</Geometry>
<ControlTemplate x:Key="{ComponentResourceKey ResourceId=SubmenuItemTemplateKey, TypeInTargetAssembly={x:Type MenuItem}}" TargetType="{x:Type MenuItem}">
<Grid SnapsToDevicePixels="true">
<Rectangle x:Name="Bg" Fill="{TemplateBinding Background}" RadiusY="2" RadiusX="2" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1"/>
<Rectangle x:Name="InnerBorder" Margin="1" RadiusY="2" RadiusX="2"/>
<!-- Add RadioButton around the Grid
-->
<RadioButton Background="Transparent" GroupName="MENUITEM_GRP" IsHitTestVisible="False" IsChecked="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=MenuItem}}">
<RadioButton.Resources>
<Style TargetType="Themes:BulletChrome">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</RadioButton.Resources>
<!-- Add RadioButton Top part ends here
-->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="24" SharedSizeGroup="MenuItemIconColumnGroup" Width="Auto"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="37"/>
<ColumnDefinition SharedSizeGroup="MenuItemIGTColumnGroup" Width="Auto"/>
<ColumnDefinition Width="17"/>
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="Icon" ContentSource="Icon" Margin="1" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="Center"/>
<!-- Change border thickness to 0
-->
<Border x:Name="GlyphPanel" BorderBrush="#CDD3E6" BorderThickness="0" Background="#E6EFF4" CornerRadius="3" Height="22" Margin="1" Visibility="Hidden" Width="22">
<Path x:Name="Glyph" Data="{StaticResource Checkmark}" Fill="#0C12A1" FlowDirection="LeftToRight" Height="11" Width="9"/>
</Border>
<ContentPresenter Grid.Column="2" ContentSource="Header" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<TextBlock Grid.Column="4" Margin="{TemplateBinding Padding}" Text="{TemplateBinding InputGestureText}"/>
</Grid>
</RadioButton>
<!-- RadioButton closed , thats it !
-->
</Grid>
...
</Window.Resources>
Run Code Online (Sandbox Code Playgroud)应用风格,
<MenuItem IsCheckable="True" Header="Open" Style="{DynamicResource MenuItemStyle1}"
Run Code Online (Sandbox Code Playgroud)我只是认为我会提出解决方案,因为没有答案能满足我的需求。我的完整解决方案在这里...
但是,基本思想是使用ItemContainerStyle。
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Icon" Value="{DynamicResource RadioButtonResource}"/>
<EventSetter Event="Click" Handler="MenuItemWithRadioButtons_Click" />
</Style>
</MenuItem.ItemContainerStyle>
Run Code Online (Sandbox Code Playgroud)
并且应该添加以下事件单击,以便在单击MenuItem时检查RadioButton(否则,必须在RadioButton上完全单击):
private void MenuItemWithRadioButtons_Click(object sender, System.Windows.RoutedEventArgs e)
{
MenuItem mi = sender as MenuItem;
if (mi != null)
{
RadioButton rb = mi.Icon as RadioButton;
if (rb != null)
{
rb.IsChecked = true;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个简单的、基于 MVVM 的解决方案,它利用一个简单的IValueConverter和每个 MenuItem 的CommandParameter。
无需将任何 MenuItem 重新设计为不同类型的控件。当绑定值与 CommandParameter 不匹配时,MenuItems 将自动取消选择。
绑定到 DataContext (ViewModel) 上的 int 属性 (MenuSelection)。
<MenuItem x:Name="MenuItem_Root" Header="Root">
<MenuItem x:Name="MenuItem_Item1" IsCheckable="True" Header="item1" IsChecked="{Binding MenuSelection, ConverterParameter=1, Converter={StaticResource MatchingIntToBooleanConverter}, Mode=TwoWay}" />
<MenuItem x:Name="MenuItem_Item2" IsCheckable="True" Header="item2" IsChecked="{Binding MenuSelection, ConverterParameter=2, Converter={StaticResource MatchingIntToBooleanConverter}, Mode=TwoWay}" />
<MenuItem x:Name="MenuItem_Item3" IsCheckable="True" Header="item3" IsChecked="{Binding MenuSelection, ConverterParameter=3, Converter={StaticResource MatchingIntToBooleanConverter}, Mode=TwoWay}" />
</MenuItem>
Run Code Online (Sandbox Code Playgroud)
定义您的价值转换器。这将根据命令参数检查绑定值,反之亦然。
public class MatchingIntToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var paramVal = parameter as string;
var objVal = ((int)value).ToString();
return paramVal == objVal;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
var i = System.Convert.ToInt32((parameter ?? "0") as string);
return ((bool)value)
? System.Convert.ChangeType(i, targetType)
: 0;
}
return 0; // Returning a zero provides a case where none of the menuitems appear checked
}
}
Run Code Online (Sandbox Code Playgroud)
添加您的资源
<Window.Resources>
<ResourceDictionary>
<local:MatchingIntToBooleanConverter x:Key="MatchingIntToBooleanConverter"/>
</ResourceDictionary>
</Window.Resources>
Run Code Online (Sandbox Code Playgroud)
祝你好运!
| 归档时间: |
|
| 查看次数: |
29263 次 |
| 最近记录: |