WPF - 如何在命令的CanExecute属性为false时隐藏菜单项?

59

默认情况下,当菜单项的命令无法执行(CanExecute = false)时,菜单项会变为禁用状态。最简单的方法是根据CanExecute方法设置菜单项的可见性/折叠状态是什么?

6个回答

59

感谢提供解决方案。对于那些需要明确XAML的人,这可能会有所帮助:

<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>
在我的情况下,上下文菜单是一种资源,所以可见性的绑定必须使用RelativeSource Self绑定设置。另外,在CommandParameter中,您还可以将被点击打开上下文菜单的项的DataContext传递给它。为了将命令绑定路由到父窗口,您还需要相应地设置CommandTarget。

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

CanExecute切换IsEnabled属性,因此只需观察此属性并保持UI中的所有内容。如果要重复使用此功能,请创建一个单独的样式。


这太完美了 - 像魔法一样起作用(尽管我使用了直接绑定布尔到可见性转换器,而不是触发器,但思路是相同的) - 17 of 26
7
如果不将可见性设置为“Collapsed”,那么隐藏的菜单项仍将占据空间。 - Roman Reiner
是的,这是一个更好的解决方案,尽管根据Roman的建议,可见性应该设置为Collapsed。 - Gareth
更改可见性为“已折叠”。 - rjarmstrong
更改可见性是样式的更改,因此使用样式比直接绑定更有意义。 - MikeT
@MikeT 我同意。这样做可以更容易地阅读,并且可以在多个菜单项或按钮上重复使用,而无需复制粘贴。 - Martin Braun

44

你可以通过将Visibility绑定到IsEnabled(在CanExecute == false时设置为false)来简单地实现这一点。但你仍需要一个IValueConverter将bool值转换为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
    }

5
这需要的努力有点多,实际上你只需使用一个触发器即可。 - rjarmstrong


1

我不知道这是否是最简单的方法,但您可以始终创建一个属性来返回CanExecute(),然后使用IValueConverter将布尔值转换为可见性,将您元素的可见性绑定到此属性。


这个回答并不能提供太多帮助,但我会给它一个+1来弥补那些负面评价,我完全不明白为什么有人要给出这样的评价。虽然这个回答不太有用,但里面提到的所有东西都是正确的,此外,其他被标记为积极的回答也都使用了这些内容。至少这个回答应该得到零分,而不是负数! - quetzalcoatl
这是我的最初想法,但是你如何从这个新属性中访问(object param)参数,并将其传递给CanExecute()呢? - Matt

1
将IsEnabled绑定到Visibility可以解决问题,但所需的XAML过长且复杂:
Visibility="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Mode=OneWay, Converter={StaticResource booleanToVisibilityConverter}}"

你可以使用附加属性来隐藏所有绑定细节并清晰地传达你的意图。
这是附加属性:
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);
                }
            }
        }
    }
}

这是使用它的方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接