【问题标题】:WPF Multibinding not Updating Source when Expected; Checkboxes with 'Select All'WPF 多绑定未按预期更新源;带有“全选”的复选框
【发布时间】:2018-04-04 10:26:38
【问题描述】:

我的视图模型中有一组变量:

public ObservableCollection<ObservableVariable> Variables { get; }= new ObservableCollection<ObservableVariable>();

ObservableVariable 类有两个属性:string Name 和 bool Selected;该类实现了 INotifyPropertyChanged,

我的目标是将此集合绑定到 WPF 视图中的清单,并使用 MultiBinding 将“全选”复选框绑定到该列表。下图说明了所需的视图。

观察下面的 XAML:

<CheckBox Content="Select All" Name="SelectAllCheckbox"></CheckBox>
...
<ListBox ItemsSource="{Binding Variables}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <CheckBox Content="{Binding Name}">
                <CheckBox.IsChecked>
                    <MultiBinding Converter="{StaticResource LogicalOrConverter}" Mode="TwoWay">
                        <Binding Path="Selected"></Binding>
                        <Binding ElementName="SelectAllCheckbox" Path="IsChecked"></Binding>
                    </MultiBinding>
                </CheckBox.IsChecked>
            </CheckBox>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

LogicalOrConverter 接受任意数量的布尔值;如果有任何为真,则返回真。

正如您在上面看到的,每个复选框都绑定到视图模型中的一个变量以及“全选”复选框的状态。目前,除以下内容外,一切都按预期工作:如果我单击“全选”,则视图中的复选框会更新,但更改不会传播回视图模型。

请注意,我的实现中的大多数事情都可以正常工作。例如,如果我单击单个复选框,则视图模型会正确更新。

更详细的问题:

当我点击一个单独的复选框时,OnPropertyChanged 事件会在刚刚更改其框的变量中触发;转换器中的 ConvertBack 函数被触发;视图模型已更新,一切正常。

但是,当我单击“全选”复选框时,视图中的各个复选框都会更新,但不会在任何变量中调用 OnPropertyChanged,并且不会调用转换器中的 ConvertBack 函数。

另外,如果我取消选中“全选”,则各个检查将恢复到以前的状态。

更新视图模型的唯一方法是单击各个复选框。但是,多重绑定适用于视图。

我的问题是:

为什么对复选框的更改没有传播到视图模型中的源集合

转换器:

public class LogicalOrConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {

        foreach (object arg in values)
        {
            if ((arg is bool) && (bool)arg == true)
            {
                return true;
            }
        }

        return false;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        object[] values = new object[2] {false, false};

        if (value is bool && (bool) value == true)
            values[0] = true;

        return values;
    }
}

ObservableVariable 定义:

public class ObservableVariable : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    private bool _selected;
    public bool Selected
    {
        get { return _selected; }
        set
        {
            _selected = value;
            OnPropertyChanged(nameof(Selected));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

【问题讨论】:

  • 一个问题 - 哪个值必须有复选框,如果它们是通过“全选”选择的,然后通过“全选”取消选择。使用您的 MValConverter,如果值将被传播到 VM,它们将永远不会被取消选择。
  • @Rekshino,抱歉回复晚了。我还没有弄清楚取消选择将如何工作。可能有必要为此添加逻辑,具体取决于我想要做什么。我故意忽略了这部分问题,因为我认为取消选择后勤是一个单独的问题。
  • 如果您一定要使用多重绑定来更新视图模型,您可以通过解决方法获得它,但我怀疑它在实践中是否有用。如果您需要,我稍后会发布我的答案。
  • 你需要吗?
  • @Rekshino 谢谢你的提议,但没有。我知道几种解决方法来解决我的问题以及如何实现它们。我在这里发布以了解我是否可以在真正的 MVVM 中实现这个全选复选框。对我来说,这意味着视图模型中没有全选逻辑,理想情况下,没有代码。

标签: c# wpf mvvm multibinding


【解决方案1】:

多重绑定的问题在于它会“触发”两个数据更改,但第一个绑定 (Path="Selected") 将更新 VM 中的数据,因为这是数据绑定的内容。第二个绑定只会触发 SelectAll Checkbox 并更改 IsChecked 属性。仅仅因为您有一个 MultiBinding 并不意味着其他 Bindings 会相互传播它们的更改。

这就是为什么您会看到单击 SelectAll 的行为并且复选框更改但数据没有更改的原因。您还没有明确设置 SelectAll 复选框的机制来告诉 ViewModel 更改数据。

通过一些试验和错误,我确定仅通过 MultiBinding 没有明确而简单的方法来做到这一点(如果有人有办法做到这一点,我有兴趣学习)。我还尝试了 DataTriggers,它变得一团糟。我发现的最佳方法是将 SelectAll 逻辑卸载到 Viewmodel 并在 SelectAll 复选框上使用Command。这使您可以很好地控制逻辑并进行更强大的调试。

新 XAML:

<CheckBox Content="Select All" x:Name="SelectAllCheckbox" 
          Command="{Binding SelectAllCommand}" 
          CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}"/>


    <ListBox ItemsSource="{Binding Variables}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <CheckBox Content="{Binding Name}" 
                          IsChecked="{Binding Selected}">
                </CheckBox>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

我将 IsChecked 作为参数包含在内,以便您可以控制选择和取消选择。

我的视图模型:

public class ViewModel
{
    public ObservableCollection<ObservableVariable> Variables { get; set; }
    public ViewModel()
    {
        Variables = new ObservableCollection<ObservableVariable>();
        SelectAllCommand = new RelayCommand(SelectAll, ()=>true);
    }

    public RelayCommand SelectAllCommand { get; set; }

    public void SelectAll(object param)
    {
        foreach (var observableVariable in Variables)
        {
            observableVariable.Selected = (bool)param;
        }
    }
}

显然您希望对参数有更好的验证逻辑。这主要是为了简短的回答。

为了完整起见,我使用了标准的 RelayCommand 代码。

public class RelayCommand : ICommand
{
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
    private Action<object> methodToExecute;
    private Func<bool> canExecuteEvaluator;
    public RelayCommand(Action<object> methodToExecute, Func<bool> canExecuteEvaluator)
    {
        this.methodToExecute = methodToExecute;
        this.canExecuteEvaluator = canExecuteEvaluator;
    }
    public RelayCommand(Action<object> methodToExecute)
        : this(methodToExecute, null)
    {
    }
    public bool CanExecute(object parameter)
    {
        if (this.canExecuteEvaluator == null)
        {
            return true;
        }
        else
        {
            bool result = this.canExecuteEvaluator.Invoke();
            return result;
        }
    }
    public void Execute(object parameter)
    {
        this.methodToExecute.Invoke(parameter);
    }
}

【讨论】:

  • 有趣。我希望避免将“全选”逻辑放在视图模型中。在我看来,这违反了 MVVM 范式,但唉。我经常觉得 WPF 没有包含正确实现 MVVM 的工具。
  • @ShaneSims 它并没有真正违反 MVVM。您的视图模型可以处理数据和视图之间的任何额外逻辑。这就是你正在做的事情。您希望以更复杂的方式更改数据(选定属性)。它不是纯粹基于 UI(将放在 XAML 或 Code behind 中),因为您的数据保持该状态。
猜你喜欢
  • 2010-11-03
  • 1970-01-01
  • 2010-11-24
  • 1970-01-01
  • 2017-10-16
  • 1970-01-01
  • 1970-01-01
  • 2012-02-18
  • 2010-10-26
相关资源
最近更新 更多