【问题标题】:How do I disable a button bound to a current item's ICommand when there is no current item?当没有当前项目时,如何禁用绑定到当前项目的 ICommand 的按钮?
【发布时间】:2012-05-25 18:52:32
【问题描述】:

假设您有一个按钮,其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 的建议。

【问题讨论】:

    标签: c# .net wpf binding mvvm


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      您可以简单地将按钮的 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
      }
      

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • 它不会禁用标准 WPF 控件中的所有按钮吗?他们可以使用事件处理程序而不是命令。
        【解决方案4】:

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

        【讨论】:

        • +1 是的,我只是想出了一个NullToFalseConverter 方法,如我的编辑所示。
        • 这与我的建议有点不同:除了Command之外,您还绑定了IsEnabled,但是您可以编写一个转换器并将Command与此转换器绑定。不过,你的方法更简单——直到你需要IsEnabled 做别的事情:)
        • 哦,对了,您的意思是将静态资源定义为备用命令以使其可重用。是的,这听起来像是一种改进,如果你说我需要将 IsEnabled 绑定到其他东西上,我会记住它作为一种解决方法。
        • 您还可以启用/禁用按钮的容器,这样按钮的IsEnabled 仍可免费用于其他用途。如果你有多个按钮绑定到当前项,禁用按钮的公共容器是好的。
        【解决方案5】:

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

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

        DelegateCommand constructor on MSDN

        DelegateCommand CanExecute BugFix

        【讨论】:

        • 如果没有当前项,如何访问谓词?
        • @Ludo 谓词告诉命令是否可以执行,它基本上是当命令绑定到按钮时执行的第二种方法。如果集合发生变化,您可以调用命令的 raisecanexecutechange,或者我认为您也可以使用 NotifyPropertyChanged。
        【解决方案6】:

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

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

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

        <Button Command={Binding DoSomethingCommand} />
        

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

        【讨论】:

        • 是的,我知道我可以将命令提升一级,但我必须在我的视图模型中跟踪当前项目。
        • 我会说在 ViewModel 中提供当前项目是一个加号!
        • @pickles: 是的,我使用IsSelected 间接跟踪当前项目,但如果我不需要像 WPF UI 那样做的话,我不想让它成为一个特定的属性它已经在视图级别。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-06-22
        • 2021-12-25
        • 2014-09-07
        • 1970-01-01
        • 2010-12-15
        • 1970-01-01
        • 2021-04-24
        相关资源
        最近更新 更多