MVVM Light & WPF - Binding Multiple instances of a Window to a ViewModel
Asked Answered
R

2

6

I am working on my first project in MVVM and I've chosen to use the MVVM Light Toolkit. I have a GameViewModel that handles business on the main screen of my game. I need to find out how to open a new window (AdventurerView) with an instance of Adventurer as a parameter when a command is executed, have it bound to AdventurerViewModel, and display and return data. Instances of this window will be opened and closed frequently. I have been stuck on this for a couple of days now and it's driving me crazy. I would like to learn how to do this in an MVVM-friendly way, preferably with the tools provided by MVVM Light or pure XAML.

I've tried using MVVM Light's ViewModelLocator but since AdventurerView is a window it won't work; it says "Can't put a Window in a Style", though the program still compiles and runs. Could there be something I could change to make that work? Or is there another way to bind them in XAML? Or another approach entirely? I would really love to be able to move on from this. I have also tried using MVVM Light's messenger to no avail (which still doesn't tackle the View/ViewModel issue).

I just need to be able to create a window that is bound to AdventurerViewModel and display/return the appropriate data.

AdventurerView.xaml is in its default state at the moment, but I feel that if I could bind the appropriate data that might help (DataContext).

AdventurerViewModel is pretty bare-bones as well

class AdventurerViewModel : ViewModelBase
{
    #region Members

    private Adventurer _adv;

    #endregion

    #region Properties

    public Adventurer Adv
    {
        get { return _adv; }
        set { _adv = value; }
    }

    #endregion

    #region Construction

    public AdventurerViewModel(Adventurer adv)
    {
        this._adv = adv;
    }

    #endregion
}

App.xaml with the non-working DataTemplate at the bottom:

<Application StartupUri="MainWindow.xaml"
         xmlns:views="clr-namespace:AoW.Views"
         xmlns:vm="clr-namespace:AoW.ViewModels" 
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:Class="AoW.App" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         mc:Ignorable="d">

<Application.Resources>
    <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />

    <DataTemplate DataType="{x:Type vm:GameViewModel}">
        <views:GameView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:TitleViewModel}">
        <views:TitleView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:AdventurerViewModel}">
        <views:AdventurerView />
    </DataTemplate>

</Application.Resources>
</Application>

The command in GameViewModel that will hopefully make this all happen (the messagebox just confirms that the command is firing):

    private void ExecuteShowAdvCommand(Adventurer adv)
    {
        System.Windows.MessageBox.Show(adv.Name);
    }

I don't really know what else to include.

Raised answered 7/6, 2013 at 21:43 Comment(8)
Have you used the Messenger that comes with mvvm-light? In simple terms you firstly do NOT declare a new window as a DataTemplate in your MainWindow's xaml. What you do is in the GameViewModel when you need to create your AdventureView send a message to MainWindow which should register and receive this message in the code-behind, create the AdventureView and call Show() or ShowDialog() on that appropriately. SimpleIoC should be handling the creation of the VM for AdventureViewModel. Does this make sense to you?Numeration
#3386849 shows an example of opening a new window using MVVM Light. There are couple other options of doing the same thing, but this should be a good start. Now when sending these "messages" you can pass whatever argument's you choose making it simple to pass your parameter infoNumeration
as for getting the response that depends on how you get the data back if it's a dialog you can call ShowDialog which will be modal and check it's return value or again use Messenger to send the results back to the GameViewModelNumeration
Thanks. I'll check this out. I'm currently exploring Messenger, though I've never successfully used it.Raised
I'm still unsure of how to pass an object to the new view vie messenger. I haven't yet seen a good example regarding that.Raised
Message objects are nothing special. Messenger.Default.Send<T>() where T can be any type you want. so you can create a class call it MyMessage and to send a message with this object you'd just do Messenger.Default.Send<MyMessage>(new MyMessage(){ SomeProp = "Some Val"});Numeration
receiving end will be Messenger.Default.Register<MyMessage>(this, (args) => /* Do something with args parameter */); In this example I'm assuming MyMessage class has a string property called SomeProp. MVVM Light comes with a few message types built in. Have a look in this post galasoft.ch/mvvm under Messenger sectionNumeration
I've put together a sample that should show these features in action. Hope that's of a bit more help.Numeration
N
23

Ok I put together a demo that should make this hopefully easier for you Download Link

Functionality:

  • 3 Windows in Total (MainWindow, ModalWindow, NonModalWindow)
  • MainWindow has a TextBox you can type whatever you want into.
  • 2 buttons on the top will open the Modal / NonModal Window accordingly
  • Each window when opened will display the message that was in MainWindow's TextBox in a TextBlock inside them.
  • In each window you can tick a CheckBox to update the value in result's textblock in MainWindow (For the Modal Window this will kick in when modal window is closed. For NonModal changes can be seen asap)

That's it for functionality,

Concepts:

  • Registering Multiple VM's with the SimpleIoC and using GetInstance(...) to request them out.
  • Messenger class usage with a custom message type OpenWindowMessage
  • Opening Modal / Non Modal Windows from a parent VM staying true to the MVVM principles
  • Passing data between windows(just shown in NonModal)

Important Note: - The method used in this example to set the non DP DialogResult from the modal window is not MVVM friendly cos it uses code-behind to set the DialogResult property on a Window.Closing event which should be avoided(If needing to be "testable"). My preferred approach is a bit long and is very well documented HERE(Mixture of question and answer). Hence why I ignored it for the sake of this sample.

Numeration answered 7/6, 2013 at 23:50 Comment(14)
Thank you for such an incredible reply. I think I almost have it working now. I'm getting an error when I attempt to open the new window - "XamlParseExcption occurred" pointing to the line in the modal window regarding DataContext and I have no idea why it's doing this.Raised
Now I have no idea how those lines got there. I removed that and I don't get any errors now, but the window doesn't display.Raised
All I had to do was call .Show() - It's functional now. It's a shame I can only upvote this answer once. One last thing: if I open a second window, it changes the information in the first to that of the second. Is there any way to prevent this? Thanks once again for such a great answer!Raised
My bad, calling Show() wasn't necessary. I neglected to add the result in. Either way, you have helped me tremendously and it certainly hasn't gone unappreciated.Raised
@JasonD Your welcome :) glad you can put that to some use. I have updated the download link to now give a new VM for every Non-Modal Window(left the same as before for Modal, since you cant really have two of those at a time in this setup). I've posted the explanation for that on your other thread asking for info about getting New VM on every Window.Numeration
Thanks once again. I accepted your answer on the other question. The issue I have now is that I need aspects of both types you described. I need to be able to open multiple windows but only have the data sent back when I click a button. I'll keep experimenting with the sample you've provided me, but I was hoping you could tell me if this is is possible or not without excessive modification.Raised
@JasonD well that's pretty simple tbh. In the example for NonModalWindow the message is sent in the property setter(thus sending it on update instantly). If you just want to "send" the message on a button click, send the same message which is in the property setter now from the RelayCommand's Execute Method or lambda.Numeration
Yea, I managed to get that all figured out. Thanks for all of your help.Raised
You can also put the stuff from the code behind in a static method on ModelViewLocator. I'm posting an updated project in a differnet answer to show this. Better? I don't know, but I dislike code behind files.Threnode
This is one of the best answers I've ever seen on SO, great job!Deflection
Darn, the download link is broken, are there any mirrors?Kahler
@Kahler Actually the current link seems to work fine for me. You could maybe try this alt link. Am planning on moving all the dropbox links to a git repo soon anyways. Dropbox being a pain in keeping links valid after a certain amount of timeNumeration
Actually, the original DL link works now. Thanks for adding the mirror!Kahler
Wow, this demo is amazing! :)Jackstraws
T
1

Follow up to Viv, I modified the sample to include an example of opening the window without using a code behind.

Sample project is here.

I'm utilizing the ViewModelLocator singleton with a static method that news up the viewmodel and window and Data Context instead of the code behind.

Blog Post with Details. Let me know which method is preferable. I dislike using code behind, but there could be pro's and con's I'm missing.

Threnode answered 5/9, 2013 at 20:38 Comment(5)
Firstly "disliking" code-behind(compltely without a specific reason) is a very weak argument. You can find countless examples of people saying "not all code-behind is wrong in MVVM", so I won't start that topic again here. Ignoring that bit, This approach pretty much offers not much "added advantage" and introduces a whole load of indirection for nothing. So if I have to elaborate and justify that claim, firstly the process of creating a new window and showing it is not "unit-testable".Numeration
..cont'd. Secondly now what you got is adding a strong dependency to ViewModelLocator in the MainViewModel(to call the static function). Thus unit tests for MainViewModel, now have to provide this class, but cannot provide the same one as the live code cos that involves creating UI elements. Thus you need a mock to ViewModelLocator and probably make it a interface-service to pass a different instance for unit-tests and one for live-codeNumeration
..cont'd. while on the other side you got a message being sent out that you could subscribe from the Unit-tests and are done with it. Thus this whole moving the code from code-behind to ViewModelLocator thereby serves the purpose of "My code-behind class is empty" when it did not have to be in the first place and thinking it should be is wrong to begin-with. Also the ViewModelLocator as the name suggests is a IoC helper. having it perform opening/closing window's is pretty weird to begin-with. I'd rather know which Window chains to which other and have my dependencies derived from that.Numeration
Sorry din't mean to bash out your idea, it just ain't something I'd personally opt for and would also advise ppl not to be "scared" of "code-behind files in MVVM". Know which code belongs to what and you could save yourself some headache.Numeration
Just because I dislike code behind doesn't make it wrong. But if there is one thing I have found to be true with .NET: There is always many right ways to do it. If you using a MVVM framework, then my example is one way to do it utilizing the framework.Threnode

© 2022 - 2024 — McMap. All rights reserved.