Silverlight MVVM with multiple regions, windows, tabs

Silverlight MVVM with multiple regions, windows, tabs

Old forum URL: forums.lhotka.net/forums/t/9420.aspx


JohnFry posted on Monday, August 23, 2010

After spending a couple of months playing with MVVM and thinking that what I wanted to do was combine CSLA with an MVVM framework, I have come to the conclusion that:

1. Many available MVVM Frameworks require more plumbing code to make them work than is needed to implement the Models (Business Objects) - they are just too complex;

2. The CSLA ViewModel/Model makes the development of MVVM-Ready business objects easy, and provides so much functionalilty that it makes a lot of that provided by MVVM Frameworks redundant;

3. CSLA combined with BXF is just beautiful in its simplicity, and works a treat at least for simple scenarios;

That is not to say that the various MVVM Frameworks don't incorporate some neat stuff that's worth stealing - they do.However, I've decided that CSLA/BXF is for me and it saves learning yet another complicated Framework.

Where I am struggling a little with my decision is in handling more complex UI scenarios, particlarly those involving multiple regions on the Main Page, Child Windows or Tabs. Getting these to display is simple using a "MainPagePresenter" (as in BXF sample) - the problem comes when I want to close/clear a Window or Tab. Conventional wisdom (as expressed in this forum) is that the UI triggers a Close Method of the ViewModel along the lines of:

public void Close()

{

Presenter.Instance.ShowView(null, null, null, "Content");

}

That's fine if there is only one display region ("Content"), but as the ViewModel has no knowledge of the View or where/how it is displayed this clearly can't work for a complex scenario. Additionally, it may be that the ViewModel or the Application decides that the View(Model) should be closed - perhaps as part of a clean shutdown - and I need to cater for that.

My thought is that this issue is primarily a shortcoming of ViewModel<T>. I say shortcoming only after a lot of thought, because ViewModel<T> should obviously be Framework independent. However, I don't think that this independence would be compromised by adding as standard to ViewModel<T>:

  • A "Close" method that fires an event;
  • An interface defining a method that can be called to ask the View Model to clean itself up (e.g. ask User via pop-up if changes should be saved) and then "Close". This is similar to the MVVM Light ICleanup.

I've tried "hardcoding" this into a couple of ViewModels and implementing a crude StateManager in "MainPagePresenter" that keeps track of what is displayed where and handles the "Close" event. It seems to work, but not being an accomplished .NET programmer I'm worried that this is too simplistic to be robust in real life - ot even whether it is the best approach. (So wouldn't it be great if Rocky solved it for me?).

Would appreciate hearing any thoughts that others may have about this.

RockfordLhotka replied on Monday, August 23, 2010

My approach has been to create a subclass of ViewModel<T> in my projects, so I can add UI-specific behaviors that are still generalized across all my viewmodels.

For example, I almost always override OnError/OnRefreshed/OnSaved/Save in order to do status updates. There's no sense doing this in every viewmodel class, so I move it to this project-specific base class.

Your Close concept seems like something that would be comparable and should go in the same place.

Curelom replied on Tuesday, August 24, 2010

I've still been having troubles with window closing.  I've been able to call a Closing Method on a ViewModel using a trigger such as below, but I'm not able to Cancel the close.  How can I get into the Windows Closing method to change the cancel event arg?

<csla:TriggerAction  Name="trgWindowClosing" TriggerEvent="Closing"  Height="0" Width="0"
                     TargetControl="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window, AncestorLevel=1}}"
                     MethodName="WindowClosing"/>

RockfordLhotka replied on Tuesday, August 24, 2010

So you are talking about an actual Window? Not a usercontrol in a region?

Hmm, not something I typically do, or have thought of a whole lot.

BUT, consider that TriggerAction invokes a method, and that method can have 0 or 2 parameters. If it has 2 parameters, the second parameter is an ExecuteArgs parameter, and that has a property which should give you a reference to the original EventArgs object generated by the event.

That EventArgs object should have a Cancel property (if you cast it correctly), and it is the same object instance, so if you set Cancel = true that should work?

All speculation on my part...

And note that this technically violates the purity of your MVVM model because your viewmodel becomes aware of a UI-specific type (the eventargs sublcass). However, the only options I can think of right now involve either code-behind (bad) or what I'm suggesting here (not quite as bad).

Curelom replied on Tuesday, August 24, 2010

I threw together a quick test.  I'm able to get the arg and set it, but to no effect, the window still closes even if I set the arg to true Sad.  I've stepped through and verified that it is setting the property correctly.  I then added an actual WindowClosing method to the window.  It looks like the WindowClosing method of the window runs and completes before the WindowClosing in the Model, so I'm changing the property after the fact.

        public void WindowClosing(object sender, Csla.Xaml.ExecuteEventArgs e) {
            System.ComponentModel.CancelEventArgs arg = e.TriggerParameter as System.ComponentModel.CancelEventArgs;
            if (arg != null && Model.IsDirty) {
                //setting to true for testing
                arg.Cancel = true;

                MessageBoxResult results = MessageBox.Show("You have changes that you haven't saved.  Do you wish to continue and lose them?""Lose Changes"MessageBoxButton.YesNo);                
                if (results == MessageBoxResult.No)
                    arg.Cancel = true;
            }            
        }

Curelom replied on Wednesday, August 25, 2010

This is a head scratcher.  I run it today and it's working now without changing any code.

RockfordLhotka replied on Wednesday, August 25, 2010

Beats me, but it sure should work - that args object is the same one that's being provided and handled by the event.

Copyright (c) Marimer LLC