WPF: Hiding ContextMenu when empty

2019-04-21 02:19发布

问题:

I have a context menu that gets menu items through databinding (I'm using the MVVM pattern):

<ContextMenu ItemsSource="{Binding Path=ContextMenuItems}" />

This works fine. However, in the cases when there are no menu items to show, I don't want the context menu to show up at all. Is there a way to accomplish this? Some kind of XAML trigger maybe?

I've tried catching the Opened event och closing the context menu when there are no children. This works but the context menu still flashes by...

回答1:

Maybe bind to your menu items collections count property and use a converter to set the context menu's visibility.

 <ContextMenu ItemsSource="{Binding Path=ContextMenuItems}"
              Visibility="{Binding Path=ContextMenuItems.Count,Converter={StaticResource zeroToHiddenConverter}}">

public  class ZeroToHiddenConverter:IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    {
        int count = (int) value;

        if (count == 0) 
        {
            return Visibility.Hidden;
        }
        else
        {
            return Visibility.Visible;
        }
    }


回答2:

You can define an implicit style:

<Style TargetType="{x:Type ContextMenu}">
    <Style.Triggers>
        <Trigger Property="HasItems" Value="False">
            <Setter Property="Visibility" Value="Collapsed" />
        </Trigger>
    </Style.Triggers>
</Style>

This should work for all your context menus at once.



回答3:

Below is how you can set an application wide style for hiding empty context menus.

HasItems is a dependency property on the ContextMenu itself, so you can set the context menu's visibility based on that boolean.

Here is how to do it in a resource dictionary:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <BooleanToVisibilityConverter x:Key="VisibilityOfBool" />

    <Style TargetType="{x:Type ContextMenu}">
        <Setter Property="Visibility" Value="{Binding HasItems, RelativeSource={RelativeSource Self}, Converter={StaticResource VisibilityOfBool}}"/>
    </Style>
</ResourceDictionary>


回答4:

If you use Tendlon's solution on a TreeView control (and probably any list type control) with a context menu, it has problems.

  1. Right click on a node with not Context Menu Items => Nothing happens (which is good)
  2. Left click on a node with Context Menu Items => The context menu appears (which is bad)


回答5:

You could try making a binding on Visibility on Items.Count with a value converter - that should prevent your menu from appearing :)



回答6:

I came up with a solution for a TreeView that uses the OnContextMenuOpening callback. It prevents the problem Alex G mentioned. If you collapse the menu with a XAML style then it does not appear when the contextmenu is empty, however it appears afterwards when you left-click on another item.

The code looks for the TreeViewItem which wants to open the ContextMenu, and if it has no items, it sets the Handled property of the event to true.

protected override void OnContextMenuOpening(ContextMenuEventArgs e) {
     var item = FindTreeViewItem(e.OriginalSource as DependencyObject);
     var contextMenu = item.ContextMenu;
     if (contextMenu != null && !contextMenu.HasItems) {
         e.Handled = true;
     }
 }

 private TreeViewItem FindTreeViewItem(DependencyObject dependencyObject) {
     if (dependencyObject == null) {
         return null;
     }
     var treeViewItem = dependencyObject as TreeViewItem;
     if (treeViewItem != null) {
         return treeViewItem;
     }
     return FindTreeViewItem(VisualTreeHelper.GetParent(dependencyObject));
}