【问题标题】:WPF MVVM moving a UserControl from one ObservableCollection to another by eventWPF MVVM 通过事件将 UserControl 从一个 ObservableCollection 移动到另一个
【发布时间】: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 基础知识的理解可能有点误入歧途。

标签: c# wpf mvvm observablecollection


【解决方案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关联
猜你喜欢
  • 1970-01-01
  • 2019-08-06
  • 2017-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多