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 项触发双击事件

MVVM设计模式和在WPF中的实现 事件绑定

C# WPF MVVM模式Prism框架下事件发布与订阅

C# WPF MVVM模式Caliburn.Micro框架下事件发布与订阅

C# WPF MVVM开发框架Caliburn.Micro常用功能指南②

WPF MVVM--事件绑定