WPF MVVM 通过事件将 UserControl 从一个 ObservableCollection 移动到另一个
Posted
技术标签:
【中文标题】WPF MVVM 通过事件将 UserControl 从一个 ObservableCollection 移动到另一个【英文标题】:WPF MVVM moving a UserControl from one ObservableCollection to another by event 【发布时间】:2015-03-25 18:50:31 【问题描述】:我有一个包含 2 个 ScrollViewer 的清单视图。一个清单是针对不完整的项目,另一个是针对完整的项目。它们由 2 个独立的可观察集合填充,并由 ItemsControls 绑定。
UserControl 有一个按钮,单击时会将“检查”移动到另一个集合。
目前我进行此设置的方式是在 ViewModel 中,它是 UserControl 的 DataContext 有一个公共事件由主窗口的 VM 订阅:
((CheckItemVM) ((CheckListItem) cli).DataContext).CompleteChanged += OnCompleteChanged;
其中 cli 是清单项。
然后 OnCompleteChanged 使用以下方法找到合适的 View 对象:
foreach (object aCheck in Checks)
if (aCheck.GetType() != typeof (CheckListItem)) continue;
if (((CheckListItem) aCheck).DataContext == (CheckItemVM) sender)
cliToMove = (CheckListItem) aCheck;
break;
很明显,这会破坏 MVVM,我正在寻找解决方法(CheckListItem 是 View,CheckItemVM 是 DataContext ViewModel)。盒装类型的原因是我有另一个 UserControl 将在两者内部都有实例,它们基本上是部分标签,我需要能够对我的可观察集合进行排序,其中清单项与特定部分之间存在关联按名称.
【问题讨论】:
我很困惑,如果你有两个列表,每个列表都绑定到一个单独的ObservableCollection
,那么为什么不将你的按钮绑定到 ViewModel 上的一个 ICommand
,它会从 collection1 中删除任何项目IsSelected=true 并将它们放在 collection2 中?或者,如果您没有以某种方式将选中的值绑定到对象模型,则将 CommandParameter 中的选定项传递给 ICommand
。
主窗口的 VM 包含 ObservableCollection,它是 CheckListItem 用户控件的集合。按钮的绑定绑定到 CheckListItem VM 中的一个属性。属性设置器调用 CompleteChanged 事件以向 CheckListItem VM 报告,以便它知道完成状态已更改。
您需要将数据与 UI 完全分离。在数据方面(DataContext),您将拥有两个自定义类的 ObservableCollections,以及一个用于将项目从一个移动到另一个的 ICommand。从 XAML 中,您可以将两个 ItemsControls 绑定到您的两个集合,并告诉它们使用 CheckBox 绘制每个项目。您还可以将CheckBox.Checked
绑定到数据项上的属性,例如IsSelected
。 ICommand 会将位于 IsSelected=true
的项目从一个列表移动到另一个列表,并重置标志。
如果您在努力理解 MVVM 和 DataContext,您可能还想查看我的 this answer。我喜欢写关于 WPF 初学者主题的博客,所以链接的文章也可能对您有所帮助。
我认为此时我强烈建议阅读 Rachel 的一些建议 - 她的博客非常有用。我对您的程序结构不是 100% 清楚,但看起来您对 MVVM 基础知识的理解可能有点误入歧途。
【参考方案1】:
这可以在 MVVM 中使用命令和绑定来完成......
我在这里提出的想法是在 Windows 视图模型中创建一个命令,用于管理检查命令,该命令在参数中接收项目视图模型,然后管理命令中的内容。我将向您展示一个使用 MvvmLight 库的简单示例:
型号:
public class ItemViewModel : ViewModelBase
#region Name
public const string NamePropertyName = "Name";
private string _name = null;
public string Name
get
return _name;
set
if (_name == value)
return;
RaisePropertyChanging(NamePropertyName);
_name = value;
RaisePropertyChanged(NamePropertyName);
#endregion
#region IsChecked
public const string IsCheckedPropertyName = "IsChecked";
private bool _myIsChecked = false;
public bool IsChecked
get
return _myIsChecked;
set
if (_myIsChecked == value)
return;
RaisePropertyChanging(IsCheckedPropertyName);
_myIsChecked = value;
RaisePropertyChanged(IsCheckedPropertyName);
#endregion
具有两个属性的简单模型,一个用于名称(标识符),另一个用于检查状态。
现在在主视图模型中,(或您想要的 Windows 视图模型)....
首先是集合,一个用于选中项,另一个用于未选中项:
#region UncheckedItems
private ObservableCollection<ItemViewModel> _UncheckedItems;
public ObservableCollection<ItemViewModel> UncheckedItems
get return _UncheckedItems ?? (_UncheckedItems = GetAllUncheckedItems());
private ObservableCollection<ItemViewModel> GetAllUncheckedItems()
var toRet = new ObservableCollection<ItemViewModel>();
foreach (var i in Enumerable.Range(1,10))
toRet.Add(new ItemViewModel Name = string.Format("Name-0", i), IsChecked = false);
return toRet;
#endregion
#region CheckedItems
private ObservableCollection<ItemViewModel> _CheckedItems;
public ObservableCollection<ItemViewModel> CheckedItems
get return _CheckedItems ?? (_CheckedItems = GetAllCheckedItems());
private ObservableCollection<ItemViewModel> GetAllCheckedItems()
var toRet = new ObservableCollection<ItemViewModel>();
foreach (var i in Enumerable.Range(11, 20))
toRet.Add(new ItemViewModel Name = string.Format("Name-0", i), IsChecked = true );
return toRet;
#endregion
还有命令:
#region CheckItem
private RelayCommand<ItemViewModel> _CheckItemCommand;
public RelayCommand<ItemViewModel> CheckItemCommand
get return _CheckItemCommand ?? (_CheckItemCommand = new RelayCommand<ItemViewModel>(ExecuteCheckItemCommand, CanExecuteCheckItemCommand));
private void ExecuteCheckItemCommand(ItemViewModel item)
//ComandCode
item.IsChecked = true;
UncheckedItems.Remove(item);
CheckedItems.Add(item);
private bool CanExecuteCheckItemCommand(ItemViewModel item)
return true;
#endregion
这里的神奇之处在于数据绑定,在这种情况下,我使用了命令参数和 FindAncestor 绑定,检查数据模板:
<DataTemplate x:Key="UncheckedItemDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Binding Name" VerticalAlignment="Top"/>
<CheckBox HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="Binding IsChecked" IsEnabled="False"/>
<Button Content="Check" Width="75" Command="Binding DataContext.CheckItemCommand, RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type local:MainWindow" CommandParameter="Binding Mode=OneWay"/>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="CheckedItemDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="Binding Name" VerticalAlignment="Top"/>
<CheckBox HorizontalAlignment="Left" VerticalAlignment="Top" IsChecked="Binding IsChecked" IsEnabled="False"/>
</StackPanel>
</Grid>
</DataTemplate>
一个用于选中项目的数据模板,另一个用于未选中项目的数据模板。现在的用法,这个比较简单:
<ListBox Grid.Row="2" Margin="5" ItemsSource="Binding UncheckedItems" ItemTemplate="DynamicResource UncheckedItemDataTemplate"/>
<ListBox Grid.Row="2" Margin="5" Grid.Column="1" ItemsSource="Binding CheckedItems" ItemTemplate="DynamicResource CheckedItemDataTemplate"/>
这是一个更清洁的解决方案,希望对您有所帮助。
【讨论】:
对此我确实有一个问题。 ItemTemplate 可以不是静态定义的,而是一个绑定并且仍然遵循 MVVM 吗? ItemTemplate 的使用是可变的,我相信您可以为此使用绑定。我最喜欢的方式是使用 x:Type: 这样您可以在任何父资源字典中定义数据模板,并且它将应用于该类型的每个项目... 完美,我用ItemsControl.Resources定义了多个DataTemplates,然后通过DataType="x:Type VMName"设置DataContext关联以上是关于WPF MVVM 通过事件将 UserControl 从一个 ObservableCollection 移动到另一个的主要内容,如果未能解决你的问题,请参考以下文章
使用 MVVM 从 WPF ListView 项触发双击事件
C# WPF MVVM模式Caliburn.Micro框架下事件发布与订阅