This project is read-only.

Bxf and Modal Dialogs

Dec 21, 2010 at 11:08 PM

Hi,

I was wondering if anybody has looked at adding support for Modal Dialog boxes in Bxf?  I know this is a common issue that many people need to deal with with doing MVVM, and there are numerous solutions out there on the web.  I was just wondering if anybody has thought about providing this service through Bxf, and if so, what would this solution look like?

Coordinator
Feb 1, 2011 at 7:11 PM

The sample app for Silverlight demonstrates displaying a modal dialog.

Basically you just pick a region name for the modal dialog, and in the OnShowView handler when you see a request to display content in that region you show the dialog with the requested content inside.

Feb 16, 2011 at 7:20 AM

mmm I'm having an interesting problem trying to implement this.  

Using the SLDemo solution from the "Deeper Dive into collections" video series, I've added a standard ChildWindow to the solution in which I would like to display a view.

I have changed the code in EmployeeItemViewModel as follows:

public void EditItem()
    {
	EmployeeViewModel vm = new EmployViewModel(Model);
      Bxf.Shell.Instance.ShowView(
        typeof(EditEmployee).AssemblyQualifiedName,
        "employeeViewModelViewSource",
        vm,
        "Modal");

      MessageBox.Show("Show Done!");
	
    }
Also, I have changed the code in MainPagePresenter as follows:
presenter.OnShowView += (view, region) =>
        {
            switch (region)
            {
                case "MainContent":
                    MainContent = view.ViewInstance;
                    break;

                case "Modal":
                    ChildWindow1 cw = new ChildWindow1();
                    cw.LayoutRoot.Children.Add(view.ViewInstance);
                    cw.Show();
                    break;

                default:
                    throw new ArgumentException("Invalid region name: " + region);
            }
        };

What is unexpected is that the "Show Done" messagebox appears before the ChildWindow1 is displayed.
The reason this is a problem for me is because I'd like to make a further change in which I want to check the state of vm
to see whether or not to trigger a refresh of the ListBox.
Any advise on how to achieve this?
--Shawn.
Coordinator
Feb 16, 2011 at 1:40 PM

This occurs because showing a window is not a blocking operation. In the XAML world a "modal" dialog isn't quite the same thing as in Windows Forms.

So you'll need to create an event driven model to run your refresh code based on when the application is at the correct state to do the refresh.

Mar 2, 2011 at 8:57 AM

OK thanks Rocky, I understand.

So I'm thinking of adding the following method to the Shell.cs of my Bxf.Wpf project: 

public void ShowModalView(IView view, EventHandler WindowClosedCallback)
    {
        System.Windows.Window childWindow = new  System.Windows.Window();
        childWindow.Content = view;
        childWindow.Closed += WindowClosedCallback;
        childWindow.ShowDialog();
    }

Problem is that I get compilation error saying 'System.Windows.Window' does not contain a definition for 'Content' (same problem for Closed and ShowDialog) and that I may be missing an assembly reference.  The weird thing though is that all three methods do appear in the intellisense.

This question is making me feel like a noob, but does anyone have any idea what assembly reference I need to add to this project?

Mar 3, 2011 at 6:32 PM

I think that creating the child window withing the ShowModalView would violate MVVM principals and not allow for testing.  Perhaps taking a cue from the original ShowView something like below.  You could then put the code that creates the child window in the presenter itself.  It might also be advantagous to put this ShowViewModal into an extended shell simplyfing upgrading when new changes for BXF come out.  Ultimately I'd like to see something like this in bxf natively.

    /// <summary>
    /// Initializes the binding resource and raises
    /// displays the view.
    /// </summary>
    /// <param name="view">View to show in Modal format.</param>
    /// <param name="region">UI region where view should be displayed.</param>
    public virtual void ShowViewModal(IView view, string region, EventHandler closedCallback)
    {
      if (view == null)
        view = ViewFactory.GetView(nullnullnull);
 
      InitializeBindingResource(view);
 
      if (OnShowViewModal != null)
        OnShowViewModal(view, region, closedCallback);
    }
Mar 3, 2011 at 8:58 PM

Ok, I'm trying to create the child window but am running into the following.  I'm wondering if my experience might clue an idea that I'm missing from somebody.

 

I created my presenter.OnShowView += (view, region)... block like above.  However, in my "modal" case, I get a null reference exception on the cw.LayoutRoot.Children.Add(view.ViewInstance).  If I comment out this line, the child window loads and my ViewModel populates (the default constructor runs).  I see data being pulled from my WCF Data Service load after the service returns and I set a parameter in my view model equal to one of the returned data fields.  This triggers my ViewModel parameter set method which sets the model value and then calls the OnPropertyChanged(Parameter Name) method.  However, the PropertyChanged value is null so the event does not fire.  My view is not updated (no displayed data) when it loads.

My theory is that because this PropertyChanged event is not firing, my view is not properly updating and that's why the data that appears to be in my ViewModel is not displayed through the binding to the form.

 

My click triggeraction code for the button I click to load the child form looks like:

	UserViewModel vm = new UserViewModel();
        Bxf.Shell.Instance.ShowView("CWUsers", "manageUserViewSource", vm, "Modal");

 

UserViewModel is, obviously, my ViewModel.  CWUsers is the name of the class that contains my child window.  manageUserViewSource is a CollectionViewSource as defined in the child window XAML file and the region "Modal" works as defined in the above code, but I get the Null reference exception as already described.

 

Any ideas?

Thanks

Mar 4, 2011 at 2:20 AM

@brenchld: I agree that I don't have the architecture on my approach right yet, and subsequent to the post I realized the problem that you mentioned.  Let's say I'm spiking an idea.  However, I was rather hoping that someone could point me in the right direction re the compilation errors that I mentioned.

Mar 4, 2011 at 2:28 AM

@chridmeadows:

mmm, my gut feel is that "CWUsers" should include a namespace?

If you put a breakpoint on the line causing the nullrefexception, then use the watch window, what variable is it that is null?  cw.LayoutRoot? or view.ViewInstance?  If you change

Bxf.Shell.Instance.ShowView("CWUsers", "manageUserViewSource", vm, "Modal");

to

Bxf.Shell.Instance.ShowView("CWUsers", "manageUserViewSource", vm, "MainContent");

does your CWUsers view then load correctly?  If not, then my gut feel is right.

 

Coordinator
Mar 4, 2011 at 4:33 AM
shawndewet wrote:

OK thanks Rocky, I understand.

So I'm thinking of adding the following method to the Shell.cs of my Bxf.Wpf project: 

public void ShowModalView(IView view, EventHandler WindowClosedCallback)
    {
        System.Windows.Window childWindow = new  System.Windows.Window();
        childWindow.Content = view;
        childWindow.Closed += WindowClosedCallback;
        childWindow.ShowDialog();
    }

Problem is that I get compilation error saying 'System.Windows.Window' does not contain a definition for 'Content' (same problem for Closed and ShowDialog) and that I may be missing an assembly reference.  The weird thing though is that all three methods do appear in the intellisense.

This question is making me feel like a noob, but does anyone have any idea what assembly reference I need to add to this project?


This is really not the right direction. As has been noted, this will block testing or UI portability. Also, you shouldn't have to alter the Bxf source to extend it - you should be able (for example) to subclass Shell to add methods, and then use a custom shell instance in your app - but that doesn't solve the reality that this is not a good approach :)

The compile error is probably because of the UI portability issue. The code files in Bxf are shared across all three projects (SL, WPF, WP7). The compile error is probably because this code won't compile for SL and/or WP7.

Mar 4, 2011 at 9:28 AM

yeah you're right Rocky...the compilation errors were exactly because of that reason.

Anyway, I think I am going down the right path now with the following changes:

In IShell, I added: 

  void ShowModalView(string viewName, string bindingResourceKey, object model, Action<objectEventArgs> closedCallback);

and in IPresenter I added:

event Action<IViewAction<objectEventArgs>> OnShowModalView;


The implementation in Shell.cs is:

public void ShowModalView(string viewName, string bindingResourceKey, object model, Action<objectEventArgs> closedCallback)
    {
        ShowModalView(ViewFactory.GetView(viewName, bindingResourceKey, model), closedCallback);
    }
public virtual void ShowModalView(IView view, Action<objectEventArgs> closedCallback)
    {
        if (view == null)
            view = ViewFactory.GetView(nullnullnull);

        InitializeBindingResource(view);

        if (OnShowModalView != null)
            OnShowModalView(view, closedCallback);
    }

Then in my presenter I have reverted OnShowView back to it's original code as per the SLDemo:

presenter.OnShowView += (view, region) =>
        {
          if (region == "MainContent")
          {
            MainContent = view.ViewInstance;
          }
          else
          {
            throw new ArgumentException("Invalid region name: " + region);
          }
        };

What I have added though is the following method:

presenter.OnShowModalView += (view, closedCallback) =>
          {
              ChildWindow1 cw = new ChildWindow1();
              cw.Closed += new EventHandler(closedCallback);
              cw.LayoutRoot.Children.Add(view.ViewInstance);
          };

While in my EmployeeItemViewModel.cs I changed the code as follows:

EmployeeViewModel m_editViewModel;
    public void EditItem()
    {
        m_editViewModel = new EmployeeViewModel(Model);
      Bxf.Shell.Instance.ShowModalView(
        typeof(EditEmployee).AssemblyQualifiedName,
        "employeeViewModelViewSource",
        m_editViewModel,
        cw_Closed); 
    }

    void cw_Closed(object sender, EventArgs e)
    {
        //something must happen here to cause refreshing of the screen;
        Model.FirstName = m_editViewModel.Model.FirstName;
        Model.LastName = m_editViewModel.Model.LastName;
    }

This works well, but I have the following challenges to overcome:

1) the cw_Closed method should be doing some kind of a check to know whether or not the "modal" form was accepted or cancelled.  I suppose I could cast the sender argument of the cw_Closed method to ChildWindow1 and check the dialogresult property.  Alternatively I guess I could add appropriate properties to the EmployeeViewModel, set these properties based on what the user does in the view, and then read them in this cw_Closed method.  Any pointers as to the way to go?

2) the ChildWindow1 control that I am hosting the view in is just the standard ChildWindow that Visual Studio creates when you go Add New Item.  It has an OK and a Cancel button which sets the dialogresult of the childwindow and subsequently closes it.  So I'm at a point where I have the view with it's Save and Cancel buttons, and the ChildWindow with it's OK and Cancel buttons.  I've gotta get rid of one of those pairs of buttons.  I guess I could add properties to the ViewModel to indicatate that the Save and Cancel buttons should not be shown.  Then the problem I am left with is how to have the ChildWindow's OK and Cancel buttons interact with the ViewModel...I don't see this being possible because they don't form part of the databinding between the View and the ViewModel.  So if I use Visibility properties on the ViewModel to make the view's Save and Cancel buttons go away, I will be unable to set the properties I am referring to in point 1.  And if I get rid of the OK and Cancel buttons on my ChildWindow, I am left without the ability to close this child window and have a dialogresult of ok or cancel.  Any thoughts/advice?

It's as I said in my Facebook profile...I can't believe the hoops one has to jump through in order to implement the VERY common scenario of a Modal window using the MVVM pattern!

Mar 4, 2011 at 11:50 AM

OK I went with this approach in and it works fine:

EmployeeViewModel m_editViewModel;
    public void EditItem()
    {
        m_editViewModel = new EmployeeViewModel(Model);
        m_editViewModel.ShowSaveAndCancelButtons = false;        

        Bxf.Shell.Instance.ShowModalView(
        typeof(EditEmployee).AssemblyQualifiedName,
        "employeeViewModelViewSource",
        m_editViewModel,
        cw_Closed); 
    }
void
 cw_Closed(object sender, EventArgs e)
    {
        var cw = sender as ChildWindow1;
        if (cw.DialogResult == true)
        {
            Csla.Xaml.ExecuteEventArgs ee = new Csla.Xaml.ExecuteEventArgs();
            m_editViewModel.Save(sender, ee);
         //something must happen to cause refreshing of the screen...
            Model.FirstName = m_editViewModel.Model.FirstName;
            Model.LastName = m_editViewModel.Model.LastName;
        }
    }

Rocky, is there any problem you see with my use of ExecuteEventArgs like this? 

Mar 4, 2011 at 3:25 PM

@shawndewet

Thanks for your suggestions.

My null reference is ocuring in view.ViewInstance.  I'm not using the same example so I don't have a  MainContent region defined.  The sample I'm using to build my application is the UsingBxfNav sample that comes with the Bxf source code download.  My hunch is that I'm using a sample application that is developed around the Silverlight navigation system and I'm trying to load a view (my child window) that is not included in the navigation definition in the source.  When I call my trigger action method (openChildWindow), it calls Bxf.Shell.Instance.ShowView("UsingBxfNav.View.CWUsers", "manageUserViewSource", vm, "Modal");  (namespace added) that in turn calls an override UserControl CreateUserControl(string viewName).

In the sample, the comment is:  // don't create a view - the navigation engine does that.

However, as mentioned, the view is not part of the navigation engine.  So, here is where I need to create the view that, I think, will set the correct view.ViewInstance value which, I think, will solve the problem.

The challenge is I don't know how to create the view.  I'm trying to research but not getting too far.  Any suggestions would be much appreciated.

Thanks.

Coordinator
Mar 4, 2011 at 3:52 PM

The basic design of Bxf places responsibility for creating the view in the view factory, and for displaying the view within a "container" in the code that handles the IPresenter events (i.e. your shell).

For a modal dialog, or any other dialog, your shell code (the IPresenter handlers) needs to supply the "container" that displays the view. In WPF this container is typically a Window that contains a ContentControl, and that is displayed modally. Literally, you should create a "ModalDialog" window .xaml file, and your OnShowView code will modally display an instance of ModalDialog.

The view is no different than any view. All views are UserControl objects that can be hosted in a container. It doesn't matter if the view will be in a modal window or in a region of an existing window - the view shouldn't care how/where it is displayed.

This means that the code in OnShowView will do the following:

  1. Create an instance of the ModalDialog container window
  2. Set the ModalDialog object's Content property (or whatever you call the property used to set the content of the container's ContentControl.Content property) to the view.ViewInstance
  3. Modally display/show the ModalDialog container window

 

Coordinator
Mar 4, 2011 at 3:54 PM

I should point out that the Silverlight sample app does exactly what I just described, except that SL provides a "ModalDialog" window class so I didn't have to create it.

I don't think WPF has an equivalent pre-built class, so you need to create it - but in WPF it is pretty darn trivial to create a xaml file that is a non-resizable window, and that's all you really need for a modal dialog.

Mar 4, 2011 at 3:55 PM

You could also create a custom EventArgs and then use the generic version of EventHandler as such  EventHandler<SimpleEventArgs>

You could then get your information from the event args itself

Mar 5, 2011 at 4:15 AM

@brenchld: At one stage I was wondering about using a custom EventArgs, but how/where would I set the properties of that EventArgs variable to indicate what is happening in the ViewModel of the View that is being displayed modally?

Mar 5, 2011 at 7:00 PM

Thanks all for the replies and suggestions.

The challenge I am having is getting the data I load into my view model populated in the "modal" view.  I can trigger the display, based on a defined region name, in the OnShowView method and correctly display the view however the view does not have the necessary data loaded.  I can watch in my debugger as my view model constructor runs and populates my model data but the data is just not making the trip to the view when it is displayed.  So, it looks like my problem is found in step 2 above as defined by Rocky.  Any things I should be looking for?

I've looked at the sample that comes with the BXF Source (Bxf/Samples/Silverlight/UsingBxfNav) and do see the error message that is displayed as a "modal" window (when once clicks the "bad" link in the demo).  However, there is no data or no View Model associated with this window.  I'm assuming it is possible to associate data I just have not figured it out yet.  Is there another sample that loads data into a modal child window that I'm missing somewhere?

Coordinator
Mar 6, 2011 at 4:56 AM

Bxf hooks up the view to the viewmodel the same way for every ShowView call - it has no idea about model or non-modal.

The way Bxf connects the view to the viewmodel (by default) is this:

  1. You supply the view name
  2. You supply the name of the resource in the view that should point to the viewmodel (usually a CollectionViewSource)
  3. You supply the viewmodel instance
  4. Bxf calls a view factory to create the view instance
  5. Bxf finds the resource in the view (using the name you provided)
  6. Bxf sets the resource's Source property to the viewmodel you provided

It sounds like this is not occurring in your case. The single most common reason for this to fail is that the resource name is wrong - doesn't match the actual resource name.

Mar 6, 2011 at 5:49 AM

Thanks Rocky for working with me on this issue.  What you are saying makes sense and is confirmation on what I've been trying to do.  That's making me feel that I'm headed down the correct path (or directly toward a cliff and I don't know it yet).

Here is what I have run into.  I have defined my view that I want to show modal as a child window and that seems to be what is throwing off the view factory.  When I trace my code through the Bxf Framework, I attempt to get my view with GetView in the ViewFactory(.cs).  This calls CreateUserControl and is expecting an object of type UserControl returned.  However, my object (view) is a ChildWindow (System.Windows.Controls.Childwindow) so an exception is thrown here.  Do I need to override the base ViewFactory method for getting a View that is defined as a ChildWindow?

Mar 7, 2011 at 12:31 PM

Also, please it appears to me that the ChildWindow example in UsingBxfNav example is not MVVM.  The ChildWindow is used to report errors.  True, the error child window is created by the framework in presenter.OnShowError but the child window that is loaded does not bind to the view model.  Instead, it fills a text box, not bound to the view model, in the view's constructor.  So, there is no binding to between a child windows and the child's view model by way of the view factory.  Please correct me if I am mistaken.

Coordinator
Mar 7, 2011 at 3:01 PM

I think you are missing just one point.

The "shell" presenter (where you handle the IPresenter events) is responsible for creating containers for content. The ChildWindow is a container, not content.

The view factory is responsible for creating a view - and a view is content. A view (by default) is always a UserControl, and that UserControl is placed in a container (like a ChildWindow).

Mar 7, 2011 at 7:16 PM

YES!

That was the missing link in my understanding.  Thanks so much Rocky.

The baby bird has left the nest.

Mar 8, 2011 at 12:12 AM

Nice,  I got it too.

 

Thanks Rocky

Oct 9, 2011 at 9:31 AM
Edited Oct 10, 2011 at 11:04 AM

How did you manage your window to close? UPDATE: See below my initial question!

I am trying that in a WPF app.

My steps so far:

- Creating a window control "DialogWindow" with just one ContentControl "DialogContent"

- Creating a specialized DialogViewModel and DialogView

- Wireing up DialogViewModel and DialogView just as any other

- in presenter.OnShowView: I create an instance of DialogWindow and pass view.ViewInstance to the DialogContent. Call ShowDialog on the DialogWindow instance and wait for a DialogResult...

Now how do I trigger the closing of the DialogWindow instance through binding. The Close and Cancel buttons are located in my DialogView, and I somehow need to interact with the container...

How did you manage it?

UPDATE: Found a solution using an attached property. See: http://stackoverflow.com/questions/501886/wpf-mvvm-newbie-how-should-the-viewmodel-close-the-form

Dec 2, 2011 at 12:06 AM

Hi Rocky..

First I'm quoted your statement from :

/////////

The basic design of Bxf places responsibility for creating the view in the view factory, and for displaying the view within a "container" in the code that handles the IPresenter events (i.e. your shell).

For a modal dialog, or any other dialog, your shell code (the IPresenter handlers) needs to supply the "container" that displays the view. In WPF this container is typically a Window that contains a ContentControl, and that is displayed modally. Literally, you should create a "ModalDialog" window .xaml file, and your OnShowView code will modally display an instance of ModalDialog.

The view is no different than any view. All views are UserControl objects that can be hosted in a container. It doesn't matter if the view will be in a modal window or in a region of an existing window - the view shouldn't care how/where it is displayed.

This means that the code in OnShowView will do the following:

  1. Create an instance of the ModalDialog container window
  2. Set the ModalDialog object's Content property (or whatever you call the property used to set the content of the container's ContentControl.Content property) to the view.ViewInstance
  3. Modally display/show the ModalDialog container window

///////////

I'm confuse with point no 1 until 3 => Why Presenter Display a view /  concrete window ??

and How do if us want to test it ? ( It will stop automatic unit test  ? )

why viewmodel has a responsibilty to display a concrete window ( view ) ??

 

thanks a lot