我的viewmodel中有一组变量:
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可以使用任意数量的bool;如果有的话,返回true。
如上所示,每个复选框都绑定到viewmodel中的变量和'select all'复选框的状态。目前,一切都按预期工作除了以下内容:如果单击“全选”,复选框将在视图中更新,但更改不会传播回视图模型。
注意,我的实现中的大部分内容都能正常工作。例如,如果单击某个复选框,则视图模型会正确更新。
问题更详细:
当我单击一个单独的复选框时,OnPropertyChanged事件会在其框刚刚更改的变量中触发;转换器中的ConvertBack函数被触发;视图模型已更新,一切正常。
但是,当我单击“全选”复选框时,视图中会更新各个复选框,但不会在任何变量中调用OnPropertyChanged,并且不会调用转换器中的ConvertBack函数。
同样相关,如果我取消选中“全选”,个别支票会回到之前的状态。
更新viewmodel的唯一方法是单击各个复选框。但是,多绑定适用于视图。
我的问题是:
为什么未对viewmodel中的源集合传播复选框进行更改
转换器:
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));
}
}
答案 0 :(得分:5)
多绑定的问题在于它将“触发”两个数据更改,但第一个绑定(Path="Selected"
)是将更新VM中数据的绑定,因为数据绑定的是至。第二个绑定仅触发SelectAll复选框并更改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作为参数包含在内,因此您可以控制选择和取消选择。
我的ViewModel:
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 Code。
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);
}
}