在不违反MVVM

时间:2016-09-01 14:01:03

标签: c# wpf mvvm

编辑:添加了具体示例,以阐明我想要实现的目标。

这是应用方案:

enter image description here

为了使代码更简单,我将使用简单的 Messenger 类而不是Prism的事件聚合器。元组包含Id和字符串有效负载。

public static class Messenger
{
    public static event EventHandler<Tuple<int, string>> DoWork;

    public static void RaiseDoWork(int id, string path)
    {
        DoWork?.Invoke(null, new Tuple<int, string>(id, path));
    }
}

模型实例订阅信使以了解何时开始工作(如果Id正确),并在工作完成时通知视图模型。

public class Model
{
    public int id;

    public Model(int id)
    {
        this.id = id;

        Messenger.DoWork += (sender, tuple) =>
        {
            if (tuple.Item1 != this.Id)
            {
                return;
            }

            var result = tuple.Item2 + " processed with id " + this.id;
            this.OnWorkCompleted(result);
        };
    }

    public event EventHandler<string> WorkCompleted;

    private void OnWorkCompleted(string path)
    {
        this.WorkCompleted?.Invoke(null, path);
    }
}

UserControlResult 负责有效负载处理和结果输出。为了使代码更简单,只需跟踪输出而不是将其放在UI上。所以XAML将是默认的。

代码隐藏:

public partial class UserControlResult : UserControl
{    
    private ResultViewModel viewModel;

    public UserControlResult()
    {
        this.InitializeComponent();
    }

    public void Init(int id)
    {
        this.viewModel = new ResultViewModel(id);
        this.DataContext = this.viewModel;
    }
}

查看模型:

public class ResultViewModel
{
    private Model model;

    public ResultViewModel(int id)
    {
        this.model = new Model(id);

        this.model.WorkCompleted += path =>
        {
            Trace.WriteLine(path);
        };
    }
}

UserControlButtons 包含按钮,其中一个应该通过信使开始处理UserControlResult中的模型。为了使代码更简单,我们省略命令实现并只显示其处理程序。

代码隐藏:

public partial class UserControlButtons : UserControl
{    
    private ButtonsViewModel viewModel;

    public UserControlButtons()
    {
        this.InitializeComponent();
    }

    public void Init(int id)
    {
        this.viewModel = new ButtonsViewModel(id);
        this.DataContext = this.viewModel;
    }
}

查看模型:

public class ButtonsViewModel
{
    private int id;

    public ButtonsViewModel(int id)
    {
        this.id = id;
    }

    // DelegateCommand implementation...

    private void StartWorkingCommandHandler()
    {
        Messenger.RaiseDoWork(this.id, "test path");
    }
}

UserControlParent 包含UserControlResultUserControlButtons。他唯一的作用是将Id传递给他们,因此他甚至不需要视图模型。

的Xaml:

<StackPanel>
    <uc:UserControlResult x:Name="UserControlResult" />
    <uc:UserControlButtons x:Name="UserControlButtons" />
</StackPanel>

代码隐藏:

public partial class UserControlParent : UserControl
{    
    public UserControlParent()
    {
        this.InitializeComponent();
    }

    public void Init(int id)
    {
        this.UserControlResult.Init(id);
        this.UserControlButtons.Init(id);
    }
}

最后 MainWindow 包含两个UserControlParent个实例。它的作用是为它们分配不同的ID。

的Xaml:

<StackPanel>
    <uc:UserControlParent x:Name="UserControlParent1" />
    <uc:UserControlParent x:Name="UserControlParent2" />
</StackPanel>

代码隐藏:

public partial class MainWindow : Window
{    
    public MainWindow()
    {
        this.InitializeComponent();

        this.UserControlParent1.Init(111);
        this.UserControlParent2.Init(222);
    }
}

这样可行:UserControlButtons中的按下按钮将开始在UserControlResult模式中工作,并且UserControlParentInit将正常工作,感谢Id。

但我相信这一系列调用UserControl方法违反了MVVM ,因为代码隐藏(MVVM中的 View )应 了解有关Id值的任何信息(相对于MVVM中的 Model )。说到这一点,我确定Id不是视图模型的一部分,因为它在UI中没有任何演示文稿。

如何将顶部窗口中的Id值传递给&#34;最深的&#34;视图模型没有违反MVVM?

原始问题

这是由3 UserControl3 s:

组成的WPF应用程序

enter image description here

UserControl2UserControl3内容的一部分。我在开发和使用 Prism 期间保留 MVVM

我需要在UserControl1 view-model UserControl1中调用自定义类的方法(就MVVM来说是 model ) 。自定义类不能成为单例的限制。我想以下列方式之一做到这一点:

  1. 使用Prism的事件聚合器。 UserControl3视图模型是发布者,Window模型是订阅者。为此,我需要在UserControl1中创建唯一身份证并将其传递给UserControl3Window

  2. UserControl1中创建服务实例,并将其传递给UserControl3UserControl1。然后Window将调用此实例的方法。

  3. UserControl2UserControl1个实例传递给UserControl1UserControl2中的视图模型只会调用UserControl3的方法,该方法将调用DECLARE @Moment AS VARCHAR (6) = '200901'; SELECT CONVERT(VARCHAR(3), CAST(@Moment + '01' AS DATETIME), 100) + '-' + RIGHT(CONVERT(VARCHAR(8), CAST(@Moment + '01' AS DATETIME), 1), 2); -- OUTPUT Jan-09 的方法,依此类推。

  4. 似乎有2个和3个方法违反了MVVM。你会如何解决这种情况?

2 个答案:

答案 0 :(得分:0)

我会使用选项1.我使用MVVM Light发送消息,任何接收该特定消息的人都将触发服务方法。松散耦合。

答案 1 :(得分:0)

我认为我实现了真正的MVVM实现,如下面的简化示例所示。特别感谢Ed Plunkett的comment和Nikita&#39; answer

首先,我不再需要传递唯一的ID。为了识别不同的ParentViewModel个实例,我只是传递了不同的Messenger实例(为了简单起见,它取代了 Prism&#39; EventAggregator ):

internal class Messenger
{
    public event EventHandler<string> DoWork;

    public void RaiseDoWork(string path)
    {
        this.DoWork?.Invoke(this, path);
    }
}

其次,在我的特定情况下,模型似乎不应该担心Messenger的{​​{1}}事件。只要在一个视图模型(DoWork)中引发此事件,此事件就更适合由另一个视图模型(ButtonsViewModel)而不是ResultViewModel本身使用。所以Model也简化了:

Model

下面展示了所有视图模型&#34;从上到下&#34;。

internal class Model
{
    public string Process(string input)
    {
        return input + " processed!";
    }
}

请注意,在internal class MainViewModel { private readonly Messenger eventAggregator1 = new Messenger(); private readonly Messenger eventAggregator2 = new Messenger(); public MainViewModel() { this.ParentViewModel1 = new ParentViewModel(this.eventAggregator1); this.ParentViewModel2 = new ParentViewModel(this.eventAggregator2); } public ParentViewModel ParentViewModel1 { get; } public ParentViewModel ParentViewModel2 { get; } } internal class ParentViewModel { public ParentViewModel(Messenger eventAggregator) { this.ButtonsViewModel = new ButtonsViewModel(eventAggregator); this.ResultViewModel = new ResultViewModel(eventAggregator); } public ButtonsViewModel ButtonsViewModel { get; } public ResultViewModel ResultViewModel { get; } } internal class ButtonsViewModel { private readonly Messenger eventAggregator; public ButtonsViewModel(Messenger eventAggregator) { this.eventAggregator = eventAggregator; this.StartCommand = new DelegateCommand(this.StartProcessing); } public DelegateCommand StartCommand { get; } private void StartProcessing() { this.eventAggregator.RaiseDoWork("test path"); } } internal class ResultViewModel : ViewModelBase { private readonly Model model = new Model(); private string textValue; public ResultViewModel(Messenger eventAggregator) { eventAggregator.DoWork += (sender, s) => this.DoWorkHandler(s); } public string TextValue { get { return this.textValue; } set { this.SetProperty(ref this.textValue, value); } } private void DoWorkHandler(string s) { var result = this.model.Process(s); this.TextValue = result; } } 中,我用实际屏幕输出替换了ResultViewModel(因为现在字符串没有Id,因此跟踪输出相同)。 Trace.WriteLine只是实施ViewModelBase

下面演示了所有观看的内容部分&#34;从上到下&#34;。

INotifyPropertyChanged

最后这两个世界在App.xaml.cs中连接:

<!-- MainWindow.xaml -->
<StackPanel Orientation="Horizontal">
    <views:UserControlParent DataContext="{Binding ParentViewModel1}" />
    <views:UserControlParent DataContext="{Binding ParentViewModel2}" />
</StackPanel>

<!-- UserControlParent.xaml -->
<StackPanel>
    <local:UserControlResult DataContext="{Binding ResultViewModel}" />
    <local:UserControlButtons DataContext="{Binding ButtonsViewModel}" />
</StackPanel>

<!-- UserControlButtons.xaml -->
<Grid>
    <Button Content="Test" Command="{Binding StartCommand}" />
</Grid>

<!-- UserControlResult.xaml -->
<Grid>
    <TextBlock Text="{Binding TextValue}" />
</Grid>

似乎像MVVM,但欢迎任何评论。