实现自己的“工厂”以重用WPF中的视图

我目前正在使用WAF(WPF应用程序框架)在WPF中进行编程。 我非常喜欢为我的应用程序中的每个小视图单元创建一个自己的ViewModel,我随后以这种方式实现了它。

在我的项目中,我得到了一个复杂的列表,其中每个list-element也包含一个列表。 由于复杂性,每个list和list-list元素都是一个自己的ViewModel。 “最坏情况”场景总共包含60-90个视图模型,仅用于列表视图。

(这是一个问题列表,其中每个问题都有一个带有评级和其他ui元素的答案列表)。

这种实现效果很好,但性能非常糟糕。 在分析后,我发现当我在一组问题之间切换时,错误导致创建我的ViewModel(因为必须再次生成整个列表)。

当我在问题集之间切换时,我无法重复我的观点1:1,因为问题的数量不同。

但是,我认为我可以重用给定的视图模型并在必要时添加(如果新集合需要更多视图)更多视图模型。

因此我写了以下工厂:

[Export] public class ViewModelPerformanceFactory where T : IPerformanceFactoryViewModel { private List _collection; private int _index; private readonly ExportFactory _exportFactory; [ImportingConstructor] public ViewModelPerformanceFactory(ExportFactory exportFactory) { _exportFactory = exportFactory; _index = 0; } public void Reset() { _index = 0; if (_collection == null) { return; } foreach (var elem in _collection) { elem.Reset(); } } public T Get() { if (_collection == null) { _collection = new List(); } if (_collection.Count <= _index) { _collection.Add(_exportFactory.CreateExport().Value); } return _collection[_index++]; } } 

其中IPerformanceViewModel只提供一个Reset -Method来清除ViewModel和View。

所以每次加载一个新的问题集时,我都会调用我的ViewModelPerformanceFactory的reset-function来清除所有模型并将索引设置回0(所以如果有人需要一个新的viewmodel实例,它将获得第一个创建的实例) 。

从理论上讲,这很有效。

现在我的问题/问题:我在问题集之间切换的频率越高,我的应用程序就越慢……这不是viewmodel-objects的加载 – 这很好。 我的列表看起来非常非常慢 – 有时甚至卡住了几秒钟然后继续积累……

我认为这是一个WAF问题,因为每个ViewModel实例化View都会看到:

 protected ViewModel(TView view) : base(view) { this.view = view; } } 

而且似乎我不能像在WAF中的ViewModels一样轻松重用Views。

有没有人对我有建议或者可能采用其他方法来加速我的申请? 或者有人认为我的整个方法是愚蠢的,我关闭停止编程? ;)

编辑:有时似乎存在内存/性能泄漏,但不是每次都可重现.. 🙁

如果没有看到整个代码,就很难说出你的问题是什么。 但是从您提供的描述中猜测:

  1. 绝对切换到DataTemplates和HierarchicalDataTemplates。 如果您每次更改数据时都在创建新控件,那么您将永远不会看到出色的性能。 这样您也可以使用虚拟化。

  2. 应用程序变慢的事实确实表明存在内存泄漏。 最常见的原因是未被​​取消订阅的事件。

  3. 实例化ViewModels不应占用任何重要时间,因为它们少于100个。 如果是这种情况,你应该找出他们为什么要这么长时间的原因。 您不应该重复使用ViewModels包装模型对象。 如果你这样做,你需要大量的书籍来“重置”它们,或者它们必须是无国籍的,这半就失败了将它们放在第一位的目的。

  4. ViewModel引用View的事实是关于MVVM的主要禁忌。 在我的大多数解决方案中,我甚至没有视图类,我将DataTemplates用于除DateTimeBoxes和自定义ComboBox之类的自定义控件之外的所有内容。

我不确定您的方法或为什么存在性能问题,但这是我对类似问题的解决方案。

完整的解决方案可以在https://github.com/steinborge/ProxyTypeHelper/wiki找到

我想要实现的是创建“通用”视图模型的能力,然后可以将其分配给数据模板。 数据模板只是一个用户控件。 在您拥有大量简单数据的情况下,维护屏幕可以节省大量重复代码。

但有几个问题。 Datatemplates与XAML中的generics不兼容,如果你有很多数据模板,那么你创建了很多XAML – 特别是如果你想在不同的视图中使用它。 在你的情况下,你提到了多达90个视图 – 这将是很多XAML。

解决方案是将模板存储在查找中,并使用内容控件和DataTemplateSelector填充,具体取决于DataContext。 所以首先需要注册数据模板/视图:

  manager.RegisterDataTemplate(typeof(GenericViewModel), typeof(WPFEventInter.UserControls.CarTypesView)); manager.RegisterDataTemplate(typeof(GenericViewModel), typeof(WPFEventInter.UserControls.ColourView)); 

RegisterDataTemplate只是将datatemplate添加到字典:

  public void RegisterDataTemplate(Type viewModelType, Type dataTemplateType, string Tag="") { var template = BuildDataTemplate(viewModelType, dataTemplateType) ; templates.Add(viewModelType.ToString() + Tag, template); } private DataTemplate BuildDataTemplate(Type viewModelType, Type viewType) { var template = new DataTemplate() { DataType = viewModelType, VisualTree = new FrameworkElementFactory(viewType) }; return template; } 

现在使用ContentPresenter控件创建一个视图。这将根据视图的Datacontext显示视图。

DataTemplateSelector如下所示。 这将根据datacontext返回适当的视图:

 public class ContentControlGenericTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { DataTemplate retVal = null; try { retVal = Core.WPF.Infrastructure.DataTemplateManager.templates[item.GetType().ToString()]; } catch //empty catch to prevent design time errors.. { } return retVal; } 

很难提供解决方案没有看到您的整个代码以及您的问题是什么:

你可以用你的代码代替你的代码。

 public class MyICommand: ICommand { Action _TargetExecuteMethod; Func _TargetCanExecuteMethod; public MyICommand(Action executeMethod) { _TargetExecuteMethod = executeMethod; } public MyICommand(Action executeMethod, Func canExecuteMethod){ _TargetExecuteMethod = executeMethod; _TargetCanExecuteMethod = canExecuteMethod; } public void RaiseCanExecuteChanged() { CanExecuteChanged(this, EventArgs.Empty); } bool ICommand.CanExecute(object parameter) { if (_TargetCanExecuteMethod != null) { return _TargetCanExecuteMethod(); } if (_TargetExecuteMethod != null) { return true; } return false; } // Beware - should use weak references if command instance lifetime is longer than lifetime of UI objects that get hooked up to command // Prism commands solve this in their implementation public event EventHandler CanExecuteChanged = delegate { }; void ICommand.Execute(object parameter) { if (_TargetExecuteMethod != null) { _TargetExecuteMethod(); } } 

}