9

假设您有一个按钮,其command属性绑定到ICommand某个集合的某些当前项。

当集合为null时,该按钮保持启用状态,单击它似乎是无操作的。我希望按钮保持禁用状态。我想出了以下方法,以便在集合为空时禁用按钮。然而,对于可以用更自然、更简单和更像 MVVM 的方式来完成的事情来说,这似乎有点太复杂了。

因此,问题是:有没有更简单的方法来禁用该按钮,理想情况下不使用代码隐藏?

.xaml:

<Button Content="Do something" >
    <Button.Command>
        <PriorityBinding>
            <Binding Path="Items/DoSomethingCmd"  />
            <Binding Path="DisabledCmd" />
        </PriorityBinding>
    </Button.Command>
</Button>

。CS:

public class ViewModel : NotificationObject
{
    ObservableCollection<Foo> _items;

    public DelegateCommand DisabledCmd { get; private set; }

    public ObservableCollection<Foo> Items { 
        get { return _items; } 
        set { _items = value; RaisePropertyChanged("Items"); } 
    }

    public ViewModel()
    {
        DisabledCmd = new DelegateCommand(DoNothing, CantDoAnything);
    }

    void DoNothing() { }
    bool CantDoAnything()
    {
        return false;
    }
}

编辑

几点注意事项:

  1. 知道我可以使用 lambda 表达式,但在这个示例代码中我没有。
  2. 知道谓词是什么。
  3. 我看不出做某事有DoSomethingCmd.CanExecute什么帮助,因为在DoSomethingCmd没有当前项目的情况下无法访问。
  4. 因此,我将重新提出我的问题:如何避免使用DisabledCmd? 我对向上移动不感兴趣,DoSomethingCmd因为它不是我想要的。否则我不会问这个问题。

另一个编辑:

所以我基本上采用了这个答案作为解决方案:WPF/MVVM: Disable a Button's state when the ViewModel behind the UserControl is not yet Initialized?

我相信,这正是 hbarck 的建议。

4

6 回答 6

6

我会像 akjoshi 那样做,只是我会使用普通触发器而不是 DataTrigger,并且我会检查 Button.Command 是否为空。由于禁用没有命令的按钮总是有意义的(尤其是在没有点击事件处理程序的 MVVM 中),将这个触发器包含在按钮的默认样式中也是一个好主意,以便有这种行为在应用程序中的所有按钮上......我看不出使用虚拟命令的理由。

于 2012-05-23T22:52:51.150 回答
4

您可以创建一个Trigger来检查 Item (按钮的数据上下文)是否为空,并将 Button 的(或者可能是 Anton 提到的父容器的)IsEnabled属性设置为false,如下所示 -

<DataTrigger
    Binding="{Binding Path=Item}"
    Value="{x:Null}">
    <Setter Property="Control.IsEnabled" Value="False" />
</DataTrigger>

我现在无法对其进行测试,但我认为这应该可行。

于 2012-05-22T13:55:26.530 回答
3

查看 PresentationFramework.dll 中的代码,我没有看到任何直接的方法(请参阅 参考资料ButtonBase.UpdateCanExecute)。您可能会很幸运地从元数据中派生出一个类Button并覆盖元数据,CommandProperty以便自己处理更改。但是你可以很容易地避免在你的视图模型中包含那个什么都不做的命令代码:创建一个特殊的转换器,它将一个null命令转换为一个共享的始终禁用的后备ICommand。如果您有很多需要这种行为的按钮,则可能需要附加属性和样式。

于 2012-05-18T09:18:40.307 回答
1

如果你看一下委托命令,第二个参数是一个函数,它使你能够做到这一点,我不太确定,你为什么让它这么复杂。例如,如果您这样做:

DoSomethingCommand = new DelegateCommand(() => SampleAction, () => Items != null);

当您简单地将其 Command 属性绑定到此命令时,该按钮将被禁用,如下所示:

<Button Command={Binding DoSomethingCommand} />

当委托命令中的条件变为假时,该按钮将自动禁用。您还应该DoSomethingCommand.RaiseCanExecuteChanged()在条件的结果可能发生变化时调用,而不是按钮的 IsEnabled 更新以反映当前状态。

于 2012-05-18T07:40:51.973 回答
1

我使用了 RelayCommands,它有一个构造函数,您可以在其中创建一个 canExecute 谓词,然后如果它返回 false,则绑定按钮将被自动禁用。

在委托命令上,您应该重写 CantDoAnything() 方法来表示您的启用和禁用逻辑。您应该简单地绑定到命令的绑定。

MSDN 上的 DelegateCommand 构造函数

DelegateCommand 可以执行 BugFix

于 2012-05-18T07:46:44.373 回答
1

您可以简单地将按钮的 IsEnabled 属性绑定到当前项目并使用转换器。

这是一个完整的演示:

<Page.Resources>
    <Converters:NullToBoolConverter x:Key="NullToBoolConverter" 
                                    IsNullValue="False" IsNotNullValue="True" />
</Page.Resources>

<Page.DataContext>
    <Samples:NoCurrentItemViewModel/>
</Page.DataContext>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <ListBox 
        IsSynchronizedWithCurrentItem="True" Grid.Row="0" 
        ItemsSource="{Binding Items}" 
        DisplayMemberPath="Name"/>

    <Button 
        Grid.Row="1" 
        Content="Do something" 
        IsEnabled="{Binding Items/, Converter={StaticResource NullToBoolConverter}}"
        Command="{Binding Items/DoSomethingCommand}"/>

    <Button Grid.Row="2" Content="Clear" Command="{Binding ClearCommand}"/>
</Grid>

查看模型 - 来自 MVVM Light 的 RelayCommand

public class NoCurrentItemViewModel
{
    public NoCurrentItemViewModel()
    {
        _items = new ObservableCollection<NoCurrentItemDetail>
                    {
                        new NoCurrentItemDetail{Name = "one"},
                        new NoCurrentItemDetail{Name = "two"},
                    };

        ClearCommand = new RelayCommand(Clear);
    }

    public ICommand ClearCommand { get; private set; }

    private void Clear()
    {
        _items.Clear();
    }

    private readonly ObservableCollection<NoCurrentItemDetail> _items;

    public IEnumerable<NoCurrentItemDetail> Items
    {
        get { return _items; }
    }
}

public class NoCurrentItemDetail
{
    public NoCurrentItemDetail()
    {
        DoSomethingCommand = new RelayCommand(DoSomething);
    }

    private void DoSomething()
    {
        Debug.WriteLine("Do something: " + Name);
    }

    public ICommand DoSomethingCommand { get; private set; }

    public string Name { get; set; }
}

转换器

public class NullToBoolConverter : IValueConverter
{
    public NullToBoolConverter()
    {
        IsNullValue = true;
        IsNotNullValue = false;
    }

    public bool IsNullValue { get; set; }
    public bool IsNotNullValue { get; set; }

    #region Implementation of IValueConverter

    public object Convert(object value, 
        Type targetType, object parameter, CultureInfo culture)
    {
        return value == null ? IsNullValue : IsNotNullValue;
    }

    public object ConvertBack(object value, 
        Type targetType, object parameter, CultureInfo culture)
    {
        return Binding.DoNothing;
    }

    #endregion
}
于 2012-05-21T12:27:55.697 回答