【问题标题】:How to enable/disable a button in WPF?如何启用/禁用 WPF 中的按钮?
【发布时间】:2011-12-16 19:15:36
【问题描述】:

我有一个相对简单的 WPF 应用程序。这个想法是向用户展示一个项目列表;每个项目都有一个复选框来选择/取消选择项目。

我的代码,简化后看起来有点像这样:

 class Thing { /* ... */ };
 public static int SelectedCount { get; set; }
 public class SelectableThing
 {
        private bool _selected;
        public bool Selected { 
            get { return _selected; }
            set { _selected = value; if (value) { SelectedCount++; } else { SelectedCount--; } } 
        }
        public Thing thing { get; set; }
 };
 private ObservableCollection<SelectableThing> _selectableThings;
 public Collection<SelectableThing> { get { return _selectableThings; } }
 <DataGrid ItemSource="{Binding Path=SelectableThings}">
      <DataGridCheckBoxColumn Binding="{Binding Selected}"/>
      <DataGridTextColumn Binding="{Binding Thing.name}"/>
 </DataGrid>
 <Button Content="{Binding Path=SelectedTestCount}" Click="someFunc" />

所以想法是按钮内容应该显示所选测试的计数。这应该完成,因为无论何时设置 SelectableThing.Selected,它都应该酌情增加/减少 SelectedCount。

但是,据我所知,这种行为不起作用。无论选择/取消选择列表中的项目,按钮文本都显示“0”。

有什么想法吗?

【问题讨论】:

  • 您使用的是视图模型还是只是代码隐藏?

标签: wpf


【解决方案1】:

将按钮的内容绑定到视图模型中的字段,并在每次选择或取消选择另一个项目时为该字段触发 OnChanged 方法。将 IsEnabled 绑定到视图模型中的布尔字段,并根据需要将其设置为 true/false 以启用或禁用按钮。

【讨论】:

  • 我已经修改了原来的问题来试试这个,但可能弄错了(!)...
【解决方案2】:

这个问题有点棘手,因为您涉及多个视图模型类。这是解决此问题的代码。我唯一缺少的是 DataGrid 在您离开该行之前似乎不会更新您的项目。

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <DataGrid AutoGenerateColumns="False"
                  ItemsSource="{Binding Path=SelectableThings}"
                  Grid.Row="0" Margin="6">
            <DataGrid.Columns>
                <DataGridCheckBoxColumn Header="IsSelected"
                                        Binding="{Binding IsSelected}"/>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
        <Button Content="{Binding Path=SelectedTestCount}"
                Command="{Binding SaveCommand}"
                Grid.Row="1" Width="75" Height="23"
                HorizontalAlignment="Right" Margin="0,0,6,6"/>
    </Grid>
</Window>

事物类:

public class Thing : INotifyPropertyChanged
{
    private readonly List<SelectableThing> selectableThings;
    private DelegateCommand saveCommand;

    public Thing(IEnumerable<SelectableThing> selectableThings)
    {
        this.selectableThings = new List<SelectableThing>(selectableThings);
        this.SelectableThings =
            new ObservableCollection<SelectableThing>(this.selectableThings);

        this.SaveCommand = this.saveCommand = new DelegateCommand(
            o => Save(),
            o => SelectableThings.Any(t => t.IsSelected)
            );

        // Bind children to change event
        foreach (var selectableThing in this.selectableThings)
        {
            selectableThing.PropertyChanged += SelectableThingChanged;
        }

        SelectableThings.CollectionChanged += SelectableThingsChanged;
    }

    public ObservableCollection<SelectableThing> SelectableThings
    {
        get;
        private set;
    }

    public int SelectedTestCount
    {
        get { return SelectableThings.Where(t => t.IsSelected).Count(); }
    }

    public ICommand SaveCommand { get; private set; }

    private void Save()
    {
        // Todo: Implement
    }

    private void SelectableThingChanged(object sender,
        PropertyChangedEventArgs args)
    {
        if (args.PropertyName == "IsSelected")
        {
            RaisePropertyChanged("SelectedTestCount");
            saveCommand.RaiseCanExecuteChanged();
        }
    }

    private void SelectableThingsChanged(object sender,
        NotifyCollectionChangedEventArgs e)
    {
        foreach (SelectableThing selectableThing in
            e.OldItems ?? new List<SelectableThing>())
        {
            selectableThing.PropertyChanged -= SelectableThingChanged;
            RaisePropertyChanged("SelectedTestCount");
        }

        foreach (SelectableThing selectableThing in
            e.NewItems ?? new List<SelectableThing>())
        {
            selectableThing.PropertyChanged += SelectableThingChanged;
            RaisePropertyChanged("SelectedTestCount");
        }
    }

    public void RaisePropertyChanged(string propertyName)
    {
        if(PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

SelectableThing 类:

public class SelectableThing : INotifyPropertyChanged
{
    private string name;
    private bool isSelected;

    public SelectableThing(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            RaisePropertyChanged("Name");
        }
    }

    public bool IsSelected
    {
        get { return isSelected; }
        set
        {
            isSelected = value;
            RaisePropertyChanged("IsSelected");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

原答案:

Command 绑定到ICommand。将您的CanExecute 设置在ICommand 上以在您的条件不满足时返回false

在该IsSelected 属性的设置器中,当值更改时,引发CanExecuteChanged 事件。

Button 上的 Command 绑定会根据 CanExecute 的结果自动启用/禁用按钮。

有关如何执行此操作的更多信息,包括您可以在此处使用的ICommand 的实现,请参阅this mini-MVVM tutorial 我写了另一个问题。

要填写CanExecute 的实现,我会使用类似于Linq 的.Any 方法。那么你就不用费心去检查Count了,如果你发现有任何一项被检查了,可以提前终止循环。

例如:

this.SaveCommand = new DelegateCommand(Save, CanSave);

// ...

private void Save(object unusedArg)
{
    // Todo: Implement
}

private bool CanSave(object unusedArg)
{
    return SelectableThings.Any(t => t.IsSelected);
}

或者因为它很短,所以使用 lambda inline:

this.SaveCommand = new DelegateCommand(Save,
    o => SelectableThings.Any(t => t.IsSelected)
    );

【讨论】:

  • IsSelected 更改的基础上提高 CanExecuteChanged 可能很棘手,因为看起来您正在使用一组子视图模型。您可以订阅每个INotifyPropertyChanged,并将事件转发到this.SaveCommand.RaiseCanExecuteChanged()。如果您在弄清楚这部分时遇到问题,请告诉我。
  • 另请注意,我建议您将属性命名为IsSelected。在命名布尔值(Is、Can、Has 等)时应遵循此约定。
  • 我已经修改了原始问题以反映您的意见;你能告诉我你的想法吗?
  • @StephenGross:虽然使用static 成员可能会起作用,但它是一种黑客行为。您不能支持拥有该属性的任何类的多个实例 (Thing?)。为这段代码编写单元测试也将更加困难。我也不确定 WPF 是否可以绑定到静态属性,或者 INotifyPropertyChanged 是否可以使用它。
  • 是的,我使用静态 int 的唯一原因是,否则您无法从 SelectableThing.Selected.set() 内部更改它。
猜你喜欢
  • 2021-05-18
  • 2015-04-19
  • 2012-12-16
  • 1970-01-01
  • 2015-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多