Sha*_*ims 6 c# wpf mvvm multibinding
我的viewmodel中有一组变量:
public ObservableCollection<ObservableVariable> Variables { get; }= new ObservableCollection<ObservableVariable>();
Run Code Online (Sandbox Code Playgroud)
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>
Run Code Online (Sandbox Code Playgroud)
LogicalOrConverter可以使用任意数量的bool; 如果有的话,返回true.
如您所见,每个复选框都绑定到viewmodel中的变量和'select all'复选框的状态.目前,一切都按预期工作除了以下内容:如果单击"全选",复选框将在视图中更新,但更改不会传播回视图模型.
注意,我的实现中的大多数事情都能正常工作 例如,如果单击某个复选框,则视图模型会正确更新.
问题更详细:
当我单击一个单独的复选框时,OnPropertyChanged事件将在其框刚刚更改的变量中触发; 转换器中的ConvertBack函数被触发; viewmodel已更新,一切正常.
但是,当我单击"全选"复选框时,视图中会更新各个复选框,但不会在任何变量中调用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;
}
}
Run Code Online (Sandbox Code Playgroud)
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));
}
}
Run Code Online (Sandbox Code Playgroud)
多绑定的问题在于它将"触发"两个数据更改,但第一个绑定(Path="Selected"
)是将更新VM中数据的绑定,因为数据绑定到该数据.第二个绑定将仅触发SelectAll复选框并更改IsChecked
属性.仅仅因为你有一个MultiBinding并不意味着其他Bindings会推动他们彼此的变化.
这就是为什么你看到点击SelectAll的行为,复选框改变而不是数据.您没有明确设置SelectAll复选框的机制来告诉ViewModel更改数据.
通过一些试验和错误,我确定没有明确和简单的方法通过单独的MultiBinding来做到这一点(如果有人有办法,我有兴趣学习).我也试过DataTriggers,它变得很乱.我发现的最好方法是将SelectAll逻辑卸载到Viewmodel并Command
在SelectAll复选框上使用a .这使您可以很好地控制逻辑并允许更强大的调试.
新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>
Run Code Online (Sandbox Code Playgroud)
我将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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
显然,您希望在参数上获得更好的验证逻辑.这主要是为了简短的回答.
并且为了完整性,包括我使用的标准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);
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
350 次 |
最近记录: |