2
\$\begingroup\$

I am developing WPF application keeping MVVM and using Prism. Here is simplified scheme:

enter image description here

I need to invoke model logic in UserControlResult when executing command in view-model of UserControlButtons. Real case: UserControlButtons contains buttons (and one of them triggers start of data processing), while UserControlResult is responsible for actual data processing and result output.

I can achieve this with event aggregator from Prism. But to allow UserControlParent instances working independent from each other, I need to pass them different Id values, and here I have a problem with MVVM. I will describe problem with help of concrete simplified example.

To make code simpler, I will use trivial Messenger class instead of event aggregator from Prism. Tuple contains Id and string payload.

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));
    }
}

Model instance subscribe to messenger for knowing when to start work (if Id correct), and notify view-model when work finished.

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 is responsible for payload processing and result output. To make code simpler, lets just trace output instead of putting it on UI. So XAML will be default.

Code-behind:

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;
    }
}

View-model:

public class ResultViewModel
{
    private Model model;

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

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

UserControlButtons contains buttons, one of them should start processing of model in UserControlResult via messenger. To make code simpler, lets omit command implementation and just show its handler.

Code-behind:

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;
    }
}

View-model:

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 contains both UserControlResult and UserControlButtons. His only role is to pass Id to them, so he doesn't even need view-model.

Xaml:

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

Code-behind:

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

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

And finally "MainWindow" contains two instances of UserControlParent. Its role to assign them different Ids.

Xaml:

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

Code-behind:

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

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

This will work: pressing button in UserControlButtons will start working in UserControlResult model, and both UserControlParent will working correct and independend thanks to Id.

But I believe that this chain of invoking Init methods is violates MVVM because code-behind (which is View in MVVM) should not know anything about Id value (which is relative to Model in MVVM). Talking that, I'm sure that Id is not part of view-model, because it doesn't have any presentation in UI.

How can I pass Id value from top window to the "deepest" view-models without violating MVVM?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

You do not pass this value through views. There is no reason to make your views responsible for creating view models. For example this:

var child1 = new ChildViewModel(111);
var child2 = new ChildViewModel(222);
var main = new MainViewModel(child1 , child2);
var window = new MainWindow { DataContext = main ; }
window.Show();

no longer violates MVVM and works well with DI.

Also, prism uses plugin-based architecture. You define regions in your MainWindow, and then implement independent modules, which are plugged into those regions at runtime. I don't see you doing any of that. I think you should spend some time reading prism documentation. If you do it right - prism will wire your views and viewmodels automatically based on convention and you won't have those problems. Personally, I hate convention-based design just as much as I hate service locators (including ViewModelLocator). But if you chose to use prism, you should play by its rules. Fighting it or doing it wrong will only make things worse.

\$\endgroup\$
2
  • \$\begingroup\$ I should override App's OnStartup method to run this code, right? As for Prism - its true, I didn't spend much time on documentation yet... I'm actually only using DelegateCommand for now :) But I'm going to use it more heavily and as well I know we can customize this naming convention for ViewModelLocator. Thanks anyway, I'll wait for a while so see if any other opinions exists. \$\endgroup\$
    – Sam
    Commented Sep 5, 2016 at 9:30
  • \$\begingroup\$ Yes, that's right. \$\endgroup\$
    – Nikita B
    Commented Sep 5, 2016 at 13:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.