WPF Multibinding 未按预期更新源;带有 'Select All' 的复选框
WPF Multibinding not Updating Source when Expected; Checkboxes with 'Select All'
我的视图模型中有一组变量:
public ObservableCollection<ObservableVariable> Variables { get; }= new ObservableCollection<ObservableVariable>();
ObservableVariable class 有两个属性:string Name 和 bool Selected; class 实现 INotifyPropertyChanged,
我的目标是将此集合绑定到 WPF 视图中的清单,并使用 MultiBinding 将 'select all' 复选框绑定到该列表。下图说明了所需的视图。
观察下面的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 接受任意数量的布尔值;如果有任何为真,return 为真。
正如您在上面看到的,每个复选框都绑定到视图模型中的一个变量和 'select all' 复选框的状态。目前,除以下情况外,一切正常:如果我单击 'Select All,',视图中的复选框会更新,但更改不会传播回视图模型。
请注意,我的实现中的大部分内容都可以正常工作。例如,如果我单击单个复选框,则视图模型会正确更新。
更详细的问题:
当我点击一个单独的复选框时,OnPropertyChanged 事件在刚刚更改复选框的变量中被触发;转换器中的 ConvertBack 函数被触发;视图模型已更新,一切正常。
但是,当我单击 "Select All" 复选框时,视图中的各个复选框会更新,但不会在任何变量中调用 OnPropertyChanged,也不会调用转换器中的 ConvertBack 函数。
同样,如果我取消选中 "Select All,",则各个检查会恢复到之前的状态。
更新视图模型的唯一方法是单击各个复选框。但是,多重绑定是为了视图的目的而工作的。
我的问题是:
为什么对复选框的更改没有传播到视图模型中的源集合
转换器:
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));
}
}
您的多重绑定的问题是它会 "trigger" 两个数据更改,但第一个绑定 (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 作为参数包含在内,因此您可以控制 Select 和取消选择。
我的视图模型:
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);
}
}
我的视图模型中有一组变量:
public ObservableCollection<ObservableVariable> Variables { get; }= new ObservableCollection<ObservableVariable>();
ObservableVariable class 有两个属性:string Name 和 bool Selected; class 实现 INotifyPropertyChanged,
我的目标是将此集合绑定到 WPF 视图中的清单,并使用 MultiBinding 将 'select all' 复选框绑定到该列表。下图说明了所需的视图。
观察下面的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 接受任意数量的布尔值;如果有任何为真,return 为真。
正如您在上面看到的,每个复选框都绑定到视图模型中的一个变量和 'select all' 复选框的状态。目前,除以下情况外,一切正常:如果我单击 'Select All,',视图中的复选框会更新,但更改不会传播回视图模型。
请注意,我的实现中的大部分内容都可以正常工作。例如,如果我单击单个复选框,则视图模型会正确更新。
更详细的问题:
当我点击一个单独的复选框时,OnPropertyChanged 事件在刚刚更改复选框的变量中被触发;转换器中的 ConvertBack 函数被触发;视图模型已更新,一切正常。
但是,当我单击 "Select All" 复选框时,视图中的各个复选框会更新,但不会在任何变量中调用 OnPropertyChanged,也不会调用转换器中的 ConvertBack 函数。
同样,如果我取消选中 "Select All,",则各个检查会恢复到之前的状态。
更新视图模型的唯一方法是单击各个复选框。但是,多重绑定是为了视图的目的而工作的。
我的问题是:
为什么对复选框的更改没有传播到视图模型中的源集合
转换器:
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));
}
}
您的多重绑定的问题是它会 "trigger" 两个数据更改,但第一个绑定 (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 作为参数包含在内,因此您可以控制 Select 和取消选择。
我的视图模型:
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);
}
}