WPF - how to hide menu item if command's CanExecute is false?

34,463

Solution 1

You can simply bind Visibility to IsEnabled (set to false on CanExecute == false). You still would need an IValueConverter to convert the bool to visible/collapsed.

    public class BooleanToCollapsedVisibilityConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            //reverse conversion (false=>Visible, true=>collapsed) on any given parameter
            bool input = (null == parameter) ? (bool)value : !((bool)value);
            return (input) ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

Solution 2

Thanks for the solution. For those wanting explicit XAML this might help:

<Window.Resources>
        <BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
</Window.Resources>

<ContextMenu x:Key="innerResultsContextMenu">
    <MenuItem Header="Open"
              Command="{x:Static local:Commands.AccountOpened}"
              CommandParameter="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" 
              CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
              Visibility="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Mode=OneWay, Converter={StaticResource booleanToVisibilityConverter}}" 
              />
</ContextMenu>

In my case, the context menu is a resource, so the binding for the visibility must use the RelativeSource Self binding setup.

As a side, for the CommandParameter, you might also pass the DataContext of the item whom was clicked to open the context menu. And in order to route the command bindings to the parent window, you will need to set the CommandTarget accordingly also.

Solution 3

<Style.Triggers>
    <Trigger Property="IsEnabled" Value="False">
        <Setter Property="Visibility" Value="Collapsed"/>
    </Trigger>
</Style.Triggers>

CanExecute toggles the IsEnabled property so just watch this and keep everything in the UI. Create a separate style if you want to reuse this.

Solution 4

Microsoft provides a BooleanToVisibilityConverter.
http://msdn.microsoft.com/en-us/library/system.windows.controls.booleantovisibilityconverter.aspx

Solution 5

Binding Visibility to IsEnabled does the trick, but the required XAML is unpleasantly long and complicated:

Visibility="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Mode=OneWay, Converter={StaticResource booleanToVisibilityConverter}}"

You can use an attached property to hide all the binding details and clearly convey your intent.

Here is the attached property:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace MyNamespace
{
    public static class Bindings
    {
        public static bool GetVisibilityToEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(VisibilityToEnabledProperty);
        }

        public static void SetVisibilityToEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(VisibilityToEnabledProperty, value);
        }
        public static readonly DependencyProperty VisibilityToEnabledProperty =
            DependencyProperty.RegisterAttached("VisibilityToEnabled", typeof(bool), typeof(Bindings), new PropertyMetadata(false, OnVisibilityToEnabledChanged));

        private static void OnVisibilityToEnabledChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            if (sender is FrameworkElement element)
            {
                if ((bool)args.NewValue)
                {
                    Binding b = new Binding
                    {
                        Source = element,
                        Path = new PropertyPath(nameof(FrameworkElement.IsEnabled)),
                        Converter = new BooleanToVisibilityConverter()
                    };
                    element.SetBinding(UIElement.VisibilityProperty, b);
                }
                else
                {
                    BindingOperations.ClearBinding(element, UIElement.VisibilityProperty);
                }
            }
        }
    }
}

And here is how you would use it:

<Window x:Class="MyNamespace.SomeClass"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MyNamespace">

    <ContextMenu x:Key="bazContextMenu">
        <MenuItem Header="Open"
                  Command="{x:Static local:FooCommand}"
                  local:Bindings.VisibilityToEnabled="True"/>
    </ContextMenu>
</Window>
Share:
34,463
Christopher Mann
Author by

Christopher Mann

Veteran software engineer and database architect. With more than 15 years of experience I have built solutions for a wide variety of organizations; from federal government agencies in Brazil to one of the largest health care systems in the United States. My specialties are .Net, SQL Server, Business Analytics, and user experience in general. Occasional speaker in code camps and user groups, member of the International .Net Association, president/founder of the Central California .Net User Group, Central California Give Camp, board member of the Central Valley Software Partnership in Central California, member of the International Institute of Business Analysis. I have a Bachelor's Degree in Computer Science and an MBA with emphasis in Information Systems and Entrepreneurship from California State University, Fresno. Microsoft Certified IT Professional (MCITP) in SQL Server Programming and Administration, Microsoft Certified Professional in C#, Certified Qlikview Developer and Designer.

Updated on May 19, 2020

Comments

  • Christopher Mann
    Christopher Mann about 4 years

    By default menu items become disabled when its command cannot be executed (CanExecute = false). What is the easiest way to make the menu item visible/collapsed based on the CanExecute method?

  • quetzalcoatl
    quetzalcoatl over 12 years
    This answer does not help much, but I'm giving it +1 to level those negative points that I completely do not understood why someone has given. While this answer is not too helpful, ALL things mentioned in it are VALID and moreover, all other positively-marked answers DO USE the things mentioned. The least pointvalue this answer deserves is zero, not negatives!
  • Matt
    Matt over 12 years
    This was my initial thought, but how would you gain access to the (object param) parameter from within this new property, and pass it to CanExecute()?
  • rjarmstrong
    rjarmstrong about 12 years
    This is a bit more effort than necessary you can just use a trigger
  • pipelinecache
    pipelinecache about 12 years
    This is perfect - worked like a charm (although I used a direct binding with a bool to visibility converter instead of a trigger, the idea is the same)
  • Roman Reiner
    Roman Reiner almost 10 years
    The visibility should be set to Collapsed as otherwise the hidden menu item will still occupy space.
  • Gareth
    Gareth over 7 years
    Yes, this is a better solution although as per Roman's suggestion, visibility should be set to Collapsed
  • rjarmstrong
    rjarmstrong over 7 years
    Changed visibility to 'Collapsed'.
  • MikeT
    MikeT about 6 years
    changing the visibility is a change to style so using a style makes more sense than a direct binding
  • Martin Braun
    Martin Braun over 2 years
    @MikeT I agree. It's easier to read and re-usable on several menu items or buttons without copy-paste.