开发者

disable multicheck in context menu wpf

I want to disable multicheck in context Menu items just ch开发者_如何学Pythoneck on one item how?


You could do it in code-behind as explained in DarkSquirrel42's answer. But if you want a reusable solution, the best approach is probably to implement it as an attached behavior, so that you can use it directly in XAML. Here's a basic implementation:

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 usage:

<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 is an XML namespace mapped to the CLR namespace where you declared the MenuBehavior class)

Of course there is room for improvements:

  • You might want to enforce that one option is checked (i.e. impossible to uncheck everything) DONE
  • Currently the group names are global, i.e. if you use the same group name in different menus, the "single choice" rule will be applied across all the menus. You might want to restrict it to the current menu DONE

EDIT: I updated the code to include the improvements mentioned above:

  • Groups are now restricted to MenuItems in the same menu
  • You can now define on a per-group basis the rules for checked options:
    • Exactly one option must be checked (default)
    • Zero or one option must be checked (add a ? at the end of the group name)
    • One or more options must be checked (add a + at the end of the group name)
    • Any number of options can be checked (add a * at the end of the group name). That's actually the same as not using the attached behavior at all, but I included it anyway for completeness...

The XAML usage example illustrates the various rules


handle the Click event of the MenuItems ...

private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            if (sender is MenuItem)
            {
                IEnumerable<MenuItem> menuItems = null;
                var mi = (MenuItem)sender;
                if (mi.Parent is ContextMenu)
                    menuItems = ((ContextMenu)mi.Parent).Items.OfType<MenuItem>();
                if (mi.Parent is MenuItem)
                    menuItems = ((MenuItem)mi.Parent).Items.OfType<MenuItem>();
                if(menuItems!=null)
                    foreach (var item in menuItems)
                    {
                        if (item.IsCheckable && item != mi)
                            item.IsChecked = false;
                    }
            }
        }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜