最近关于View Models(VM)的问题让我感到很紧张。
就像this guy一样,我得出的结论是,我需要在VM上公开的集合通常包含与业务对象上公开的集合不同的类型。
因此,这两种类型之间必须存在双向映射或转换。 (只是为了使事情复杂化,在我的项目中,这些数据是“实时”的,这样一旦你更改了一个属性,它就会被传输到其他计算机)
我可以使用像Truss之类的框架来处理这个概念,虽然我怀疑在某处会有一个令人讨厌的惊喜。
不仅必须转换对象,还需要在这两个集合之间进行同步。 (只是为了使我能想到的事情复杂化,VM集合可能是业务对象集合的子集或联合,而不仅仅是1:1的同步)。
我可以看到如何使用复制的ObservableCollection或像CLINQ这样的单向“实时”同步。
然后问题变成:创建/删除项目的最佳方法是什么?
双向直接同步似乎没有出现在卡片上 - 我没有找到这样的例子,唯一支持远程的类就是ListCollectionView。双向同步甚至是添加回业务对象集合的合理方式吗?
我见过的所有样品似乎都没有解决任何这种“复杂”的问题。
所以我的问题是:你如何解决这个问题?是否有一些技术可以从VM更新模型集合?对此最好的一般方法是什么?
答案 0 :(得分:4)
就个人而言,我在我的模型和视图模型中使用了ObservableCollection。
class Model
{
public ObservableCollection<Foo> Foos;
}
class ViewModel
{
public Model Model;
public ObservableCollection<FooView> Foos;
public ViewModel()
{
Model.Foos.CollectionChanged += OnModelFoosCollection_CollectionChanged;
}
void OnModelFoosCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Foo re;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
re = e.NewItems[0] as Foo;
if (re != null)
AddFoo(re); //For other logic that may need to be applied
break;
case NotifyCollectionChangedAction.Remove:
re = e.OldItems[0] as Foo;
if (re != null)
RemoveFoo(re);
break;
case NotifyCollectionChangedAction.Reset:
Foos.Clear();
/* I have an AddRange in an ObservableCollection-derived class
You could do Model.Foo.ForEach(ree => AddFoo(ree));
*/
var converter =
from ree in Model.Foo
select new FooView(ree);
Reports.AddRange(converter);
break;
default:
//exercise for the reader :)
s_ILog.Error("OnModelFoosCollection_CollectionChangedDid not deal with " + e.Action.ToString());
break;
}
}
void AddFoo(Foo f)
{
Foos.Add(new FooView(f));
}
void RemoveFoo(Foo f)
{
var match = from f in Foos
where f.Model == f //if you have a unique id, that might be a faster comparison
select f;
if(match.Any())
Foos.Remove(match.First());
}
}
现在,当您从Model的Foo集合中删除某些内容时,它会自动删除相应的FooView。这与我对这种事情的看法相对应。如果我想删除某些内容,那么模型就是真正需要删除的地方。
感觉就像很多代码,但实际上并没有那么多。我确信可以构建一个通用版本,但IMO总是希望自定义逻辑处理元素的添加/删除。
答案 1 :(得分:3)
当您可能需要双向同步时,唯一的情况是,用于可视化VM集合的控件不会让您知道用户创建或删除项目的意图。即控件直接处理您的VM集合,并且您知道已添加/删除项目的唯一方法是监视VM的集合。如果不是这种情况,那么您可以实现单向同步并直接在模型的集合上添加/删除项目。
编辑:以WPF DataGrid为例 控制绑定到可观察的集合 ItemViewModels。如果它是 CanUserAddRows属性设置为true 并且用户开始输入 底部的空行,DataGrid 将使用您的默认构造函数 ItemViewModel创建一个松散的项目 然后将它添加到 采集。没有任何迹象 来自DG,它想要添加一个项目 collection.c 我什么都想不到 其他控制很复杂 足以能够添加项目 收集本身。
相反的 场景是你有ListView 绑定到您的集合和命令 表示用户的意图 添加新项 - 然后在命令处理程序中 您只需将新项添加到DataModel 让单向同步完成其余部分 工作。在这种情况下,ListView不是 能够添加到它的集合 呈现。
关于同步过程本身,请查看Bindable LINQ项目 - 它可以最小化代码量并提高可读性。例如,Tom发布的代码将转换为以下内容:
class ViewModel
{
public Model Model;
public ObservableCollection<FooView> Foos;
public ViewModel()
{
Foos = from foo in Model.Foos.AsBindable()
select new FooView(foo);
}
}
编辑2:使用B-LINQ一段时间之后我应该说你可能遇到性能问题。我用它来同步相对较大的集合(数百个元素)集合,每秒添加和删除数十个元素,我不得不放弃并按照Tom建议的方式实现同步。
我仍然使用B-LINQ,但是在项目的那些部分中,集合很小并且性能不是问题。
答案 2 :(得分:3)
我也在努力与两个集合的双向同步,以便通过MVVM与WPF一起使用。我在这个问题上写了MVVM: To Wrap or Not to Wrap? How much should the ViewModel wrap the Model? (Part 1)和MVVM: To Wrap or Not to Wrap? Should ViewModels wrap collections too? (Part 2),其中包括一些显示双向同步的示例代码。但是,如帖子所述,实施情况并不理想。我认为它可以作为概念证明。
我喜欢Alex_P发布的BLINQ,CLINQ和Obtics框架。这是一个非常好的方式来获得同步behvaior的一面。也许另一方(从VM到Model)可以通过备用路径实现?我刚刚在我的博客上发布了part 3,讨论了其中的一些内容。
从我所看到的情况来看,在LINQ语句将数据投影到新结构的情况下,不支持双向通过BLINQ和CLINQ。
但是,在LINQ查询返回与底层集合相同的数据类型的情况下,CLINQ可能支持双向同步。这更像是一个过滤场景,它与包装模型中数据的ViewModel的用例不匹配。
答案 3 :(得分:1)
我已经编写了一些帮助程序类,用于在View模型对应here中包装可观察的业务对象集合,也许它应该扩展为另一种方式。一直在寻找贡献...
答案 4 :(得分:1)
我提出了一个基于MVVM的通用撤销/重做框架,它使用了与您描述的问题相关的一些技术。它使用实现此接口的集合:
public interface MirrorCollectionConversor<V, D>
{
V GetViewItem(D modelItem, int index);
D GetModelItem(V viewItem, int index);
}
(V代表ViewModel项目,D代表模型项目)
使用此界面,它会在模型集合更改时自动同步viewmodel集合。如果更改viewmodel,则只需将更改重定向到模型集合。
GetViewItem函数为viewmodel对象与其模型对应物的相关性提供了一些灵活性。
您可以找到详细信息here。
(我承认构造非常复杂,我很乐意听取建议)。
答案 5 :(得分:0)
使用我的ObservableComputations库,您可以创建ObservableCollection,它是对另一个ObservableCollection进行计算并与之同步的结果。您在源集合上进行标准操作(添加,删除):计算出的Collection会反映所有更改:
Filtering<Order> expensiveOrders = orders.Filtering(o => o.Price > 25);
expensiveOrders.CollectionChanged += (sender, eventArgs) =>
{
// see the changes (add, remove, replace, move, reset) here
};
// Start the changing...
orders.Add(new Order(8, 30));
orders.RemoveAt(1);
orders[0].Price = 60;
orders[4].Price = 10;
orders.Move(5, 1);
orders[1] = new Order(10, 17);