简体   繁体   English

MVVM应用程序中的复杂WPF UserControl

[英]Complex WPF UserControl in MVVM application

Having taken over maintenance of an existing WPF application, I was horrified to discover that two of the Views and ViewModels had large blocks of near-identical code. 接管了现有WPF应用程序的维护后,我惊异地发现,两个Views和ViewModels中有大块几乎相同的代码。 Obviously, I want to refactor this so they can both reuse a single block of functionality, but I'm not sure how best to go about it, architecturally. 显然,我想对其进行重构,以便它们都可以重用单个功能块,但是我不确定在架构上如何做到最好。

The identikit code deals with processing UI data from a tab. identikit代码用于处理选项卡中的UI数据。 However I split this, it is essential that the code in the other tabs (which is different in the two cases) has access to the properties and objects of the tab I need to split out. 但是,我对此进行了拆分,其他标签中的代码(这两种情况有所不同)至关重要,可以访问我需要拆分的标签的属性和对象。

To further complicate matters, the replicated code needs database access. 更复杂的是,复制的代码需要数据库访问。 We've got a repository object that handles this. 我们有一个存储库对象来处理这个问题。 Normally when creating new objects, I've been making them testable by passing a copy of the repository into the constructor. 通常,在创建新对象时,我通过将存储库的副本传递到构造函数中来使其可测试。 However, if I do that in this case I'll have two copies of the repository object - one in the ViewModel, one in the split out code - needing to handle the same data, which is going to cause concurrency issues. 但是,如果在这种情况下这样做,我将有两个存储库对象副本-一个在ViewModel中,一个在拆分后的代码中-需要处理同一数据,这将导致并发问题。

My first thought was to make a UserControl for this, but the more I think about this, the more problematic the two issues above seem to be. 我最初的想法是为此制作一个UserControl,但是我对它的思考越多,上述两个问题似乎就越有问题。

The other option I've considered is just to make a Helper class to do some of the identical processing. 我考虑过的另一个选择就是使一个Helper类进行一些相同的处理。 But that's only going to partially solve the problem as some identical UI code (raising property changed events, XAML, etc) is still going to be in both Views/ViewModels. 但这只能部分解决问题,因为某些相同的UI代码(引发属性更改的事件,XAML等)仍将同时存在于两个Views / ViewModels中。

What's the best approach here? 最好的方法是什么? Is there a way I can get past the repository/access issues and make a UserControl? 有没有办法可以解决存储库/访问问题并进行UserControl? Or is than an alternative based on Interfaces or Inheritance I haven't considered? 还是不是我没有考虑过的基于接口或继承的替代方法?

EDIT - Code was asked for. 编辑-要求输入代码。 It's a bit complex to give a comprehensive example, but here's a snippet from each VM: 给出一个全面的示例有点复杂,但这是每个VM的一个片段:

public void CheckOrderHist(int months)
{
    var endDate = DateTime.Today.AddMonths(months);
    Dictionary<OrderHistory, bool> orders = new Dictionary<OrderHistory, bool>();
    this.ordersToExclude.Clear();

    foreach (var kvp in rep.OrderHistories.GetRecent(months))
    {
        if (kvp.Key.MailingDate >= endDate)
        {
            orders.Add(kvp.Key, true);
            this.ordersToExclude.Add(((OrderHistory)kvp.Key).OrderID);
        }
        else
        {
            orders.Add(kvp.Key, false);
        }
    }

    BuildOrderExclusionsOnCount(); //code in this is near-identical across VM's too
    OrderHistoryMonths = Math.Abs(months); //OrderHistoryMonths is a property on the ViewModel
    OnPropertyChanged("MajorityOrderBoolean");
}

And in the other VM: 在另一个VM中:

private void CheckOrderHist(int months)
{
    var endDate = DateTime.Today.AddMonths(-months);
    ObservableCollection<Tuple<OrderHistory, bool>> orders = new ObservableCollection<Tuple<OrderHistory, bool>>();
    this.ordersToExclude.Clear();

    foreach (var tuple in rep.OrderHistories.GetRecent(-months))
    {
        if (tuple.Item1.MailingDate >= endDate)
        {
            orders.Add(new Tuple<OrderHistory,bool>(tuple.Item1, true));
            this.ordersToExclude.Add(tuple.Item1.OrderID);
        }
        else
        {
            orders.Add(new Tuple<OrderHistory, bool>(tuple.Item1, false));
        }
    }

    BuildOrderExclusionsOnCount(); //code in this is near-identical across VM's too
    OrderHistoryMonths = months; //OrderHistoryMonths is a property on the ViewModel
    OnPropertyChanged("OrderHistories");
    OnPropertyChanged("GroupedOrders");
}

This illustrates the problem nicely - the function is essentially the same, but one uses a Dictionary and the other a Tuple (there's no good reason for this - they both need a Tuple really, for ease of ordering). 这很好地说明了这个问题-函数本质上是相同的,但是一个使用了Dictionary,另一个使用了一个Tuple(没有充分的理由-它们都确实需要一个Tuple,以便于订购)。 And one arbitrarily takes a negative int parameter, and the other a positive. 一个任意取负的int参数,另一个取正数。

Both contain different OnPropertyChanged events, and will use different copies of the repository object, making it hard to properly separate them using a Helper class. 两者都包含不同的OnPropertyChanged事件,并且将使用存储库对象的不同副本,这使得很难使用Helper类正确地将它们分开。 Yet putting it in a UserControl would isolate them from OrderHistoryMonths on the main ViewModel. 但是,将其放入UserControl会将其与主ViewModel上的OrderHistoryMonths隔离。

If I'm hearing the current comments right, the best solution here is to farm out the main ForEach loop to a helper class, and just put up with the rest of the duplication? 如果我正确地听到了当前的评论,那么最好的解决方案是将主要的ForEach循环分配给帮助程序类,然后忍受其余的重复操作?

By all means, extract common logic where possible to a new 'helper' class that each ViewModel can construct; 尽可能将公共逻辑提取到每个ViewModel可以构造的新“帮助程序”类中; this is the standard pattern of re-use through composition. 这是通过组合重复使用的标准模式。 The code you've shown in your question is a good candidate for this kind of refactoring. 您在问题中显示的代码是这种重构的理想选择。

As far as boilerplate, though, it's a bit trickier. 就样板而言,这有点棘手。 This is something that is difficult to address in general and must be examined on a case-by-case basis. 这是通常很难解决的问题,必须逐案检查。 There are various ways to simplify property changed notification, for instance (helper methods encapsulating property updates, AOP, etc.) but these are generally part of your MVVM framework and embraced application-wide. 有多种方法可以简化属性更改通知,例如(封装属性更新,AOP等的辅助方法),但是这些通常是MVVM框架的一部分,并包含在整个应用程序范围内。 As far as XAML duplication, you can often use Styles, Data Templates and Value Converters to improve things, but again, it requires a careful analysis of your particular code base to identify the patterns that may merit this treatment. 至于XAML复制,您通常可以使用样式,数据模板和值转换器来改进,但是再次,它需要仔细分析您的特定代码库以识别可能值得这种处理的模式。 If you have more specific examples that you think are clear duplicates, but aren't sure how to refactor, those may make good questions. 如果您有更具体的示例,您认为这些示例很明显,但是不确定如何重构,那么这些示例可能会成为一个很好的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM