2

I'm trying to implement the MVVM-pattern in a new WPF-application. I'm using the MVVM Community Toolkit to reduce boilerplate code that comes with the pattern.

In my application, I'd like to have a MainWindow that shows a list of records. Additionally, I'd like to have another Window that acts like an "import-wizard".

My question is: how can I return the list of imported records from the import-wizard Window to the MainWindow, so it can update the list of records shown?

I came up with the following possibilities:

  1. Pass the MainViewModel to the ImportWizardViewModel (but this approach would impose a hard-coupling between both ViewModels 😕)
  2. Let the Child-viewmodel send a message when the import is done. The MainViewModel can register itself for this kind of messages, so it can update its list with the imported records.

What is the preferred way to pass data from the child-window to the parent window? Or are there any other alternatives?

Because MVVM in WPF is pretty new to me, a code-example would be greatly appreciated.

Thank you in advance!

2 Answers 2

4

My prefered approach is to have a single top level view model which you bind to everything.

ie, and i'm just going to use pseudocode, sorry WPF is too long winded

<MainWindow>
   <Title>@title</Title>
   <BigList Items=@items />
   <Popup IsVisible=@showPopup>
      <BigList Items=@newItems />
      <Button OnClick=@getNewItems />
      <Button OnClick=@importNewItems />
   </Popup>
</MainWindow>

ICommand getNewItems()
{
   this.newItems = //load items from wherever
}

ICommand importNewItems()
{
   this.items.AddRange(this.newItems)
}

Your various custom controls do have their own view models, but they are purely for exposing properties, you don't have any (non view) logic in them, they are designed to be generic.

This approach keeps things simple and on a single level, it avoids event subscriptions and unsubscriptions or passing in functions.

A popular alternative is the mediator pattern.

//mainwindow
<MainWindow>
   <Title>@title</Title>
   <BigList Items=@items />
   <Popup IsVisible=@showPopup/>
</MainWindow>

MainVM
{
   OnLoad()
   {
      this.mediator.Listen("importItems", i => { this.items = i});
   }
}

//popup usercontrol
<UserControl IsVisible=@showPopup>
  <BigList Items=@items/>
  <Button OnClick=@getNewItems />
  <Button OnClick=@importNewItems />
</UserControl>

PopUpVM
{

    ICommand getNewItems()
    {
       var items = //load items from wherever
       this.mediator.Send("newItems", items);
    }
    
    ICommand importNewItems()
    {
       this.mediator.Send("importItems", items);
    }
}

The benefit here is the mediator is a global object which everything can subscribe to and send events. This breaks the need to link all the different potentially unrelated controls you might have in your application. Say for example you have some side bar or menu which is at a higher level than your MainVM but has some sub component that pops up notifications "Congrats on your import!" whenever you do X or Y


Note in my first example the Popup usercontrol takes content, perhaps a more comparable version would be something like:

//popup usercontrol
<UserControl IsVisible=@showPopup>
  <BigList Items=@items/>
  <Button OnClick=@OnGetNewItems/>
  <Button OnClick=@OnImportNewItems/>
</UserControl>
PopUp //codebehind
{
    public List<Item> items
    public ICommand OnGetNewItems
    public ICommand OnImportNewItems
}
1
  • Thank you for your insight, your clear explanation and your example-code. This seems like a very nice solution to the problem!
    – Sam
    Commented Jul 26, 2023 at 16:10
1

So there's this other M in MVVM that stands for "model". I rarely see it in use these days, but it might be the right solution to your problem.

You could create a single model object that represents the list of records. You'd then pass this instance to whatever viewmodels need it in your application. This way, main window and import wizard can share the data without tight coupling.

I don't see a necessity for a separate message for "doneness" of the import from your description. Just add newly imported records to the model, through the viewmodel of the import wizard. These additions will be propagated to those listening for the change events of the model, e.g. viewmodel(s) of the main window.

2
  • Thank you for your comment. I think that this would not be possible, because my model should have a List<> of records and the ViewModel an ObservableCollection of RecordViewModels. When I pass a reference to the List<>, the ObservableCollection won't be updated automatically.
    – Sam
    Commented Jul 26, 2023 at 16:13
  • Why would you use a List<> then? Use an ObservableColelction in your model as well, so that changes to its list of records can be observed by viewmodels interested in them.
    – null
    Commented Aug 7, 2023 at 23:08

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.