Вы можете сделать это с помощью кода, как объяснено в ответе DarkSquirrel42. Но если вам нужно многократно используемое решение, возможно, лучше всего реализовать его как прикрепленное поведение, чтобы вы могли использовать его непосредственно в XAML. Вот базовая реализация:
public static class MenuBehavior
{
[AttachedPropertyBrowsableForType(typeof(MenuItem))]
public static string GetOptionGroupName(MenuItem obj)
{
return (string)obj.GetValue(OptionGroupNameProperty);
}
public static void SetOptionGroupName(MenuItem obj, string value)
{
obj.SetValue(OptionGroupNameProperty, value);
}
public static readonly DependencyProperty OptionGroupNameProperty =
DependencyProperty.RegisterAttached(
"OptionGroupName",
typeof(string),
typeof(MenuBehavior),
new UIPropertyMetadata(
null,
OptionGroupNameChanged));
private static void OptionGroupNameChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var menuItem = o as MenuItem;
if (menuItem == null)
return;
var oldValue = (string)e.OldValue;
var newValue = (string)e.NewValue;
if (!string.IsNullOrEmpty(oldValue))
{
RemoveFromOptionGroup(menuItem);
}
if (!string.IsNullOrEmpty(newValue))
{
AddToOptionGroup(menuItem);
}
}
private static Dictionary<string, HashSet<MenuItem>> GetOptionGroups(DependencyObject obj)
{
return (Dictionary<string, HashSet<MenuItem>>)obj.GetValue(OptionGroupsPropertyKey.DependencyProperty);
}
private static void SetOptionGroups(DependencyObject obj, Dictionary<string, HashSet<MenuItem>> value)
{
obj.SetValue(OptionGroupsPropertyKey, value);
}
private static readonly DependencyPropertyKey OptionGroupsPropertyKey =
DependencyProperty.RegisterAttachedReadOnly("OptionGroups", typeof(Dictionary<string, HashSet<MenuItem>>), typeof(MenuBehavior), new UIPropertyMetadata(null));
private static HashSet<MenuItem> GetOptionGroup(MenuItem menuItem, bool create)
{
string groupName = GetOptionGroupName(menuItem);
if (groupName == null)
return null;
if (menuItem.Parent == null)
return null;
var optionGroups = GetOptionGroups(menuItem.Parent);
if (optionGroups == null)
{
if (create)
{
optionGroups = new Dictionary<string, HashSet<MenuItem>>();
SetOptionGroups(menuItem.Parent, optionGroups);
}
else
{
return null;
}
}
HashSet<MenuItem> group;
if (!optionGroups.TryGetValue(groupName, out group) && create)
{
group = new HashSet<MenuItem>();
optionGroups[groupName] = group;
}
return group;
}
private static void AddToOptionGroup(MenuItem menuItem)
{
var group = GetOptionGroup(menuItem, true);
if (group == null)
return;
if (group.Add(menuItem))
{
menuItem.Checked += menuItem_Checked;
menuItem.Unchecked += menuItem_Unchecked;
}
}
private static void RemoveFromOptionGroup(MenuItem menuItem)
{
var group = GetOptionGroup(menuItem, false);
if (group == null)
return;
if (group.Remove(menuItem))
{
menuItem.Checked -= menuItem_Checked;
menuItem.Unchecked -= menuItem_Unchecked;
}
}
static void menuItem_Checked(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem == null)
return;
string groupName = GetOptionGroupName(menuItem);
if (groupName == null)
return;
// More than 1 checked option is allowed
if (groupName.EndsWith("*") || groupName.EndsWith("+"))
return;
var group = GetOptionGroup(menuItem, false);
if (group == null)
return;
foreach (var item in group)
{
if (item != menuItem)
item.IsChecked = false;
}
}
static void menuItem_Unchecked(object sender, RoutedEventArgs e)
{
MenuItem menuItem = sender as MenuItem;
if (menuItem == null)
return;
string groupName = GetOptionGroupName(menuItem);
if (groupName == null)
return;
// 0 checked option is allowed
if (groupName.EndsWith("*") || groupName.EndsWith("?"))
return;
var group = GetOptionGroup(menuItem, false);
if (group == null)
return;
if (!group.Any(item => item.IsChecked))
menuItem.IsChecked = true;
}
}
Использование XAML:
<ContextMenu>
<MenuItem Header="Choose one" IsEnabled="False" />
<MenuItem Header="Option 1.1" IsCheckable="True" IsChecked="True"
my:MenuBehavior.OptionGroupName="group1"/>
<MenuItem Header="Option 1.2" IsCheckable="True"
my:MenuBehavior.OptionGroupName="group1"/>
<MenuItem Header="Option 1.3" IsCheckable="True"
my:MenuBehavior.OptionGroupName="group1"/>
<Separator />
<MenuItem Header="Choose zero or one" IsEnabled="False" />
<MenuItem Header="Option 2.1" IsCheckable="True" IsChecked="True"
my:MenuBehavior.OptionGroupName="group2?"/>
<MenuItem Header="Option 2.2" IsCheckable="True"
my:MenuBehavior.OptionGroupName="group2?"/>
<MenuItem Header="Option 2.3" IsCheckable="True"
my:MenuBehavior.OptionGroupName="group2?"/>
<Separator />
<MenuItem Header="Choose one or more" IsEnabled="False" />
<MenuItem Header="Option 3.1" IsCheckable="True" IsChecked="True"
my:MenuBehavior.OptionGroupName="group3+"/>
<MenuItem Header="Option 3.2" IsCheckable="True"
my:MenuBehavior.OptionGroupName="group3+"/>
<MenuItem Header="Option 3.3" IsCheckable="True"
my:MenuBehavior.OptionGroupName="group3+"/>
<Separator />
<MenuItem Header="Choose any number" IsEnabled="False" />
<MenuItem Header="Option 4.1" IsCheckable="True" IsChecked="True"
my:MenuBehavior.OptionGroupName="group4*"/>
<MenuItem Header="Option 4.2" IsCheckable="True"
my:MenuBehavior.OptionGroupName="group4*"/>
<MenuItem Header="Option 4.3" IsCheckable="True"
my:MenuBehavior.OptionGroupName="group4*"/>
</ContextMenu>
(my
- это пространство имен XML, сопоставленное с пространством имен CLR, в котором вы объявили класс MenuBehavior
)
Конечно, есть возможности для улучшений:
- Возможно, вы захотите включить один вариант (т. Е. Невозможно снять все) DONE
- В настоящее время имена групп являются глобальными, т. Е. Если вы используете одно и то же имя группы в разных меню, правило «единого выбора» будет применяться ко всем меню. Возможно, вы захотите ограничить его текущим меню DONE
РЕДАКТИРОВАТЬ: я обновил код, чтобы включить улучшения, упомянутые выше:
- Группы теперь ограничены
MenuItem
с в том же меню
- Теперь вы можете определить для каждой группы правила для отмеченных опций:
- Должна быть отмечена только одна опция (по умолчанию)
- Должен быть отмечен ноль или одна опция (добавьте
?
в конце имени группы)
- Необходимо проверить одну или несколько опций (добавить
+
в конце названия группы)
- Можно проверить любое количество опций (добавьте
*
в конце названия группы). Это фактически то же самое, что вообще не использовать прикрепленное поведение, но я все равно включил его для полноты ...
Пример использования XAML иллюстрирует различные правила