Mutually exclusive checkable menu items?
Adding this at the bottom since I don't have the reputation yet...
As helpful as Patrick's answer is, it doesn't ensure that items cannot be unchecked. In order to do that, the Checked handler should be changed to a Click handler, and changed to the following:
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;
}
}
This may not be what you're looking for, but you could write an extension for the MenuItem
class that allows you to use something like the GroupName
property of the RadioButton
class. I slightly modified this handy example for similarly extending ToggleButton
controls and reworked it a little for your situation and came up with this:
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;
}
}
}
}
}
Then, in the XAML, you'd write:
<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>
It's a bit of a pain, but it offers the perk of not forcing you to write any additional procedural code (aside from the extension class, of course) to implement it.
Credit goes to Brad Cunningham who authored the original ToggleButton solution.
You can also use a Behavior. Like this one:
<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);
}
}