我的 View 模型中有一组变量:
public ObservableCollection<ObservableVariable> Variables { get; }= new ObservableCollection<ObservableVariable>();
ObservableVariable类有两个属性:string Name和bool Selected;该类实现 INotifyPropertyChanged,
我的目标是将此集合绑定(bind)到 WPF View 中的 list ,并使用 MultiBinding 将“全选”复选框绑定(bind)到该列表。下图说明了所需的 View 。
观察下面的 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 接受任意数量的 bool 值;如果有任何为真,则返回真。
如您在上方所见,每个复选框都绑定(bind)到 View 模型中的一个变量和“全选”复选框的状态。目前,除以下情况外,一切都按预期工作:如果我单击“全选”, View 中的复选框会更新,但更改不会传播回 View 模型。
请注意,我的实现中的大部分内容都可以正常工作。例如,如果我单击单个复选框,则 View 模型会正确更新。
问题更详细:
当我单击一个单独的复选框时,OnPropertyChanged 事件在其复选框刚刚更改的变量中被触发;转换器中的 ConvertBack 函数被触发; View 模型已更新,一切正常。
但是,当我单击“全选”复选框时, View 中的各个复选框都会更新,但不会在任何变量中调用 OnPropertyChanged,也不会调用转换器中的 ConvertBack 函数。
此外,如果我取消选中“全选”,则各个检查将恢复为之前的状态。
更新 View 模型的唯一方法是单击各个复选框。但是,多重绑定(bind)是为了 View 的目的而工作的。
我的问题是:
为什么对复选框的更改没有传播到 View 模型中的源集合
转换器:
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));
}
}
最佳答案
您的多重绑定(bind)的问题是它会“触发”两个数据更改,但第一个绑定(bind) (Path="Selected"
) 将更新您 VM 中的数据这就是数据所绑定(bind)的。第二个绑定(bind)只会触发 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 作为参数包含在内,以便您可以控制选择和取消选择。
我的 View 模型:
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);
}
}
关于c# - WPF Multibinding 未按预期更新源;带有 'Select All' 的复选框,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46896704/