【问题标题】:Change two lists simultaneously同时更改两个列表
【发布时间】:2015-09-01 18:42:01
【问题描述】:

在我的应用程序模型中,我有一个字符串列表。在我的视图模型中,我有一个ObservableCollection<string>,它使用我的模型中的列表进行了初始化。我想将列表与我的 observable 集合同步,所以当我们更改它时,它也会更改列表。

我想出了两种方法来实现这一点:

  1. 为看起来像我的可观察集合的列表制作一个包装器。
  2. 使用模型中的列表初始化一个新的 observable 集合,并为 CollectionChanged 事件附加一个事件处理程序。

至于第一种方式:

public class ObservableWrapper<T> : IList<T>, IEnumerator<T>,
INotifyCollectionChanged, INotifyPropertyChanged
{
    IList<T> _list;
    IEnumerator<T> enumer;

    public ObservableWrapper(IList<T> list)
    {
        _list = list;
    }

    public T this[int index]
    {
        get
        {
            return _list[index];
        }
        set
        {
            CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, _list[index]));
            _list[index] = value;
        }
    }

    public int Count => _list.Count;
    public bool IsReadOnly => false;

    public T Current => enumer.Current;
    object IEnumerator.Current => Current;

    public event NotifyCollectionChangedEventHandler CollectionChanged;
    public event PropertyChangedEventHandler PropertyChanged;

    public void Add(T item)
    {
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _list.Count));
        _list.Add(item);
    }

    public void Clear()
    {
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        _list.Clear();
    }

    public bool Contains(T item) => _list.Contains(item);

    public void CopyTo(T[] array, int arrayIndex)
    {
        _list.CopyTo(array, arrayIndex);
    }

    public void Dispose() { }

    public IEnumerator<T> GetEnumerator()
    {
        enumer = _list.GetEnumerator();
        return enumer;
    }

    public int IndexOf(T item) => _list.IndexOf(item);

    public void Insert(int index, T item)
    {
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
        _list.Insert(index, item);
    }

    public bool MoveNext() => enumer.MoveNext();

    public bool Remove(T item) => _list.Remove(item);
    public void RemoveAt(int index)
    {
        CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _list[index], index));
        _list.RemoveAt(index);
    }

    public void Reset()
    {
        enumer.Reset();
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

第二个:

var list = new List<string>{ "aba", "abacaba" };
var obscol = new ObservableCollection<string>(list);
obscol.CollectionChanged += (s,e) =>
                            {
                                if (e.Action == NotifyCollectionChangedAction.Add)
                                {
                                    list.Add((string)e.NewItems[0]);
                                }
                                // etc
                            }
obscol.Add("test");
//now list has "aba", "abacaba", "test"

我认为第二种方式不好,因为新的 observable 集合创建并将列表中的所有项目复制到这个新的 observable 集合中。但在第一种情况下,列表元素不会被复制。

我应该选择哪种方式?

编辑:

在我的应用程序模型中,我有一个电子邮件列表(用于简化的字符串列表)。 View 具有列表框,该列表框与列表中的项目绑定到可观察集合。
当用户按下某个按钮时,电子邮件会从可观察的集合中删除,也会从应用程序模型的列表中删除。

编辑 2:
模型

public class Model
{
    public List<string> List { get; set; }
}

视图模型

public class VM
{
    public Model _model;
    public ObservableWrapper<string> WrapperList { get; set; }

    public VM(Model model)
    {
        _model = model;
        WrapperList = new ObservableWrapper<string>(_model.List);
        Command = new DelegateCommand<object>(CommandExecuted);
    }

    public DelegateCommand<object> Command { get; }

    private void CommandExecuted(object obj)
    {
        WrapperList.RemoveAt(0); //0 for example and simplify
    }

}

public class DelegateCommand<T> : System.Windows.Input.ICommand
{
    private readonly Predicate<T> _canExecute;
    private readonly Action<T> _execute;

    public DelegateCommand(Action<T> execute) : this(execute, null) { }

    public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute?.Invoke((T)parameter) ?? true;
    }

    public void Execute(object parameter)
    {
        _execute((T)parameter);
    }

    public event EventHandler CanExecuteChanged;
    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

查看/xaml

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <ListBox ItemsSource="{Binding WrapperList}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <Label Content="{Binding}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>        

    <Button Command="{Binding Command}"
            Grid.Row="1"/>
</Grid>

查看/代码

public partial class MainWindow : Window
{
    public MainWindow()
    {
        var model = new Model();
        model.List = new List<string> { "11", "22","33","44" };

        DataContext = new VM(model);
        InitializeComponent();
    } 
}

【问题讨论】:

  • 请说明您想要/需要维护原始列表的原因?我采用第二种方法。
  • 如果你正确实现了 mvvm 模式,从视图模型中的 observable 集合中删除项目就足够了。
  • 发布您的代码,对于模型、视图模型和视图,我相信您在模式实现中遗漏了一些要点。
  • @E-Bat 代码附加到帖子

标签: c# wpf


【解决方案1】:

这是您的代码的变体,需要注意的几点是:

  • 模型:重构为更加面向对象并更接近现实世界的情况,现在它是对象之间的父子关系
  • 视图模型:现在实现 INotifyPropertyChanged 以通知框架元素有关导致更新其绑定属性的更改
  • 查看模型:添加了属性 SelectedChild,用于存储所选项目。点击删除按钮时,此项将被删除。
  • DelegateCommand 不是通用的,因为不会将参数传递给命令。相反,它将使用 SelectedChild 属性
  • 查看:现在还没有关于模型的知识。注意从视图模型中指向 SelectedChild 的 SelectedItem 属性

代码:

    public class Model
    {
        public Model() 
        {
            ChildList = new HashSet<Child>();
        }
        public ICollection<Child> ChildList { get; set; }
    }

    public class Child
    {
        public string Name { get; set; }
    }


    //View Model, now implements INotifyPropertyChanged    
    public class VM: INotifyPropertyChanged{
        private Model _model; 

        public VM()
        {
            var model = new Model();
            model.ChildList.Add(new Child { Name = "Child 1" });
            model.ChildList.Add(new Child { Name = "Child 2" });
            model.ChildList.Add(new Child { Name = "Child 3" });
            model.ChildList.Add(new Child { Name = "Child 4" });

            _model = model;
            Command = new DelegateCommand(CommandExecuted);
        }

        public ObservableCollection<Child> ChildCollection
        {
            get
            {
                return new ObservableCollection<Child>(_model.ChildList);
            }
        }

        public DelegateCommand Command { get; set; }

        private void CommandExecuted()
        {
            _model.ChildList.Remove(SelectedChild);
            OnPropertyChanged("ChildCollection");
        }

        public Child SelectedChild { get; set; }   

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
       {
           var eventHandler = this.PropertyChanged;
           if (eventHandler != null)
           {
               eventHandler(this, new PropertyChangedEventArgs(propertyName));
           }
       }
    }


    public class DelegateCommand : ICommand
    {
        private readonly Action _execute;    
        public DelegateCommand(Action execute)
        {
            _execute = execute;
        }

        public void Execute(object parameter)
        {
            _execute();
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;    
        }

    //View    
<ListBox ItemsSource="{Binding Path = ChildCollection}" SelectedItem="{Binding SelectedChild}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Label Content="{Binding Name}"/>
         </DataTemplate>
     </ListBox.ItemTemplate>
</ListBox>

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new VM();
        }
    }

【讨论】:

  • 不击中列表中大量项目的表现?来自 msdn:ObservableCollection 构造函数 (IEnumerable) “初始化包含从指定集合复制的元素的 ObservableCollection 类的新实例。”
  • 对于大型集合,您不想直接公开 ObserbableCollection,而是可以使用 ICollectionView 和 CollectionViewSource 来分页结果。 (见stackoverflow.com/questions/5305883/mvvm-paging-sorting)。您也可以使用 UI 虚拟化(来自 msdn:这意味着项目容器的生成和关联的布局计算被推迟到项目可见。),Listbox 支持 UI 虚拟化(msdn.microsoft.com/en-us/library/cc716879.aspx
【解决方案2】:

你确实意识到了

  • ObservableCollection&lt;T&gt;Collection&lt;T&gt; 的子类型,它[明确] 实现IList&lt;T&gt;,并且

  • List&lt;T&gt; 同样实现IList&lt;T&gt;?

这意味着ObservableCollection&lt;T&gt; 几乎可以与IList&lt;T&gt; 的任何其他实现互换:在两个地方都使用ObservableCollection&lt;T&gt;,并在必要时将其转换为IList&lt;T&gt;

【讨论】:

    【解决方案3】:

    能否请您说明为什么需要模型中的列表和 ViewModel 中的 ObservableCollection?

    您可以在模型和视图模型中使用 ObservableCollection 吗?

    在这种情况下,您可以要求 viewmodel 可观察集合返回模型可观察集合。

    示例:

    class Model
    {
        //model's property. Instead of your list
        public ObservableCollection<string> ModelCol { get; set; }
    }
    
    class ViewModel
    {
        Model model;
    
        public ViewModel ()
        {
            model = new Model();
        }
    
        //view model property returning model property. If you want, you can do
        //the customization here.
        public ObservableCollection<string> ViewModelCol {
        get 
        {
            return model.ModelCol; 
        }
        set 
        {
            //return collection from your model.
            model.ModelCol = value; 
        }
    }
    

    在 Model 中也可以使用 ObservableCollection。如果它不会对您的要求造成任何问题,您可以使用相同的概念。

    【讨论】:

    • 不同意,Model 必须尽可能 POCO。我宁愿使用一个接口,可能是 ICollection
    • 我看到人们在模型中大多使用列表而不是 observablecollection。
    • in dot Net 4.0 ObservableCollection 已从 WindowsBase 移至 System Dll,这清楚地表明 MS ObservableCollection 适合您的模型。
    • @Adeeb Arangodan。谢谢你。我会记住未来模型中的ObservableCollection
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-12
    • 1970-01-01
    • 1970-01-01
    • 2013-08-02
    • 1970-01-01
    相关资源
    最近更新 更多