简体   繁体   中英

How to hide ContextMenus with no visible items?

I have several ContextMenus with items defined as such:

<ContextMenu>
    <MenuItem Header="Item 1" Command="{Binding Item1Command}"/>
    <MenuItem Header="Item 2" Command="{Binding Item2Command}"/>
    <MenuItem Header="Item 3" Command="{Binding Item3Command}"/>
</ContextMenu>

The Command bindings have CanExecute set so that when it's false, the MenuItem has IsEnabled=false . This goes on with a trigger that sets Visibility=Collapsed when IsEnabled=false and the opposite happens as well. The problem I'm running into is if all MenuItem s are disabled, the ContextMenu still shows up as a small blank rectangle. I have a lot of ContextMenu s, so what would be the most modular way to implement this?

I ended up doing a different workaround instead of hiding the visibility of the ContextMenu. It seems like it may be less efficient since it checks for all FrameworkElement ContextMenus, but it is working:

EventManager.RegisterClassHandler(typeof(FrameworkElement), ContextMenuOpeningEvent,
    new RoutedEventHandler(OnContextMenuOpening));

private void OnContextMenuOpening(object sender, RoutedEventArgs e)
{
    var control = (sender as FrameworkElement);
    var menu = control == null ? null : control.ContextMenu;
    if (menu != null)
    {
        var items = menu.Items.Cast<MenuItem>();
        if (items.All(i => i.Visibility != Visibility.Visible))
        {
            e.Handled = true;
        }
    }
}

If there is a better way to do this without having code behind, for the parent control nonetheless, I'm open to alternative solutions.

Here is a piece of xaml that will get you started with "Conditional" ContextMenu.
Please note: this is only starting point

<ListView>
         <ListView.Resources>
             <ContextMenu x:Key="initial">
                 <Menu>
                     <MenuItem Header="First Option"></MenuItem>
                 </Menu>
             </ContextMenu>
             <Style TargetType="ListViewItem">
                 <Style.Triggers>
                     <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="1">
                         <Setter Property="ContextMenu" Value="{StaticResource initial}" />
                     </DataTrigger>
                 </Style.Triggers>
                 <Setter Property="ContextMenu" Value="{StaticResource initial}"/>
             </Style>
         </ListView.Resources>
         <ListViewItem>1</ListViewItem>
         <ListViewItem>2</ListViewItem>
     </ListView>

The only way I found to solve that is via attached property, but it looks quite acceptable to me. First define a class with property:

public class ContextMenuExtension
{
    public static bool GetHideOnEmpty(DependencyObject obj)
    {
        return (bool)obj.GetValue(HideOnEmptyProperty);
    }

    public static void SetHideOnEmpty(DependencyObject obj, bool value)
    {
        obj.SetValue(HideOnEmptyProperty, value);
    }

    public static readonly DependencyProperty HideOnEmptyProperty =
        DependencyProperty.RegisterAttached("HideOnEmpty", typeof(bool), typeof(ContextMenuExtension), new UIPropertyMetadata(false, HideOnEmptyChanged));

    private static void HideOnEmptyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var contextMenu = d as ContextMenu;

        if (contextMenu == null)
            return;

        contextMenu.Loaded += ContextMenu_Loaded;
    }

    private static void ContextMenu_Loaded(object sender, RoutedEventArgs e)
    {
        var contextMenu = sender as ContextMenu;
        var hideOnEmpty = GetHideOnEmpty(contextMenu);

        HideContextMenu(contextMenu, hideOnEmpty);
    }

    //This is where we check if all the items are not visible
    private static void HideContextMenu(ContextMenu contextMenu, bool val)
    {
        //First, we have to know if the HideOnEmpty property is set to true. 
        if (val)
        {
            //Check if the contextMenu is either null or empty
            if (contextMenu.Items == null || contextMenu.Items.Count < 1)
                contextMenu.Visibility = Visibility.Collapsed; //Hide the contextMenu
            else
            {
                bool hide = true;
                //Check if all the items are not visible.  
                foreach (MenuItem i in contextMenu.Items)
                {
                    if (i.Visibility == Visibility.Visible)
                    {
                        hide = false;
                        break;
                    }
                }

                //If one or more items above is visible we won't hide the contextMenu.
                if (!hide)
                    contextMenu.Visibility = Visibility.Visible;
                else
                    contextMenu.Visibility = Visibility.Collapsed;
            }
        }
    }
}

Then in XAML with your ContextMenu add a namespace to class above and do following:

<ContextMenu cc:ContextMenuExtension.HideOnEmpty="True">
   <MenuItem Header="Delete" Command="<delete_command>" Visibility="Collapsed"/>
</ContextMenu>

Every time user right clicks to see the menu, it will trigger "HideContextMenu" method from "ContextMenuExtension" class and if no items or all of them are not Visible - hide entire ContextMenu.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM