实施自己的"工厂"在WPF中重用视图

时间:2014-06-17 18:43:25

标签: c# wpf mvvm waf-framework-c#

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

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

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

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

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

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

因此我写了以下工厂:

[Export]
public class ViewModelPerformanceFactory<T> where T : IPerformanceFactoryViewModel
{
    private List<T> _collection;
    private int _index;
    private readonly ExportFactory<T> _exportFactory;

    [ImportingConstructor]
    public ViewModelPerformanceFactory(ExportFactory<T> 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<T>();
        }
        if (_collection.Count <= _index)
        {
            _collection.Add(_exportFactory.CreateExport().Value);
        }
        return _collection[_index++];
    }
}

其中IPerformanceViewModel只提供Reset - 清除ViewModel和View的方法。

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

从理论上讲,这很有效。

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

我认为这是一个WAF问题,因为每个ViewModel都会实现一个视图:

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

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

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

编辑:有时会出现内存/性能泄漏,但每次都无法重现...... :(

3 个答案:

答案 0 :(得分:2)

我不确定您的方法或存在性能问题的原因,但这是我解决类似问题的方法。

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

我想要达到的目标是能够创建一个通用的&#39;查看模型,然后可以将其分配给datatemplate。数据模板只是一个用户控件。在您拥有大量简单数据维护屏幕的情况下,可以节省大量重复代码。

但有几个问题。数据模板在XAML中不能与泛型一起使用,如果你有很多数据模板,那么你创建了大量的XAML - 特别是如果你想在不同的视图中使用它。在你的情况下,你提到了多达90个视图 - 这将是很多XAML。

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

       manager.RegisterDataTemplate(typeof(GenericViewModel<CarType, WPFEventInter.ViewModel.CarTypeViewModel>), typeof(WPFEventInter.UserControls.CarTypesView));
       manager.RegisterDataTemplate(typeof(GenericViewModel<Colour, WPFEventInter.ViewModel.ColourViewModel>), 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;
    }

答案 1 :(得分:1)

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

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

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

  3. 实例化ViewModels不应占用任何重要时间,因为它们少于100个。如果是这种情况,你应该找出他们为什么要这么久的原因。您不应该重复使用ViewModels包装模型对象。如果你这样做,你需要大量的簿记来重置&#39;他们,或者他们必须是无国籍的,这首先打败了他们的目的。

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

答案 2 :(得分:-1)

很难提供解决方案无需查看整个代码以及您的问题:

您可以将代码替换为您的问题。

public class MyICommand<T>: ICommand { 
      Action _TargetExecuteMethod; 
      Func<bool> _TargetCanExecuteMethod;

      public MyICommand(Action executeMethod) {
         _TargetExecuteMethod = executeMethod; 
      }

      public MyICommand(Action executeMethod, Func<bool> 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(); 
         } 
      } 

}