DocumentWindow events and MVVM

Docking/MDI for WPF Forum

Posted 5 years ago by Claus Topholt
Version: 13.2.0592
Avatar

In the documentation under "MVVM Features" the section "Naming/Positioning the Document and Tool Windows" show how to hook up events and respond appropriately. I would expect this sort of logic to be handled by the ViewModel and not the View, since it may involve logic such as removing ViewModels from collections on window close, etc.

What I currently do is have a bit of codebehind wired up to send a command:

private void myDockSite_WindowClosed(object sender, DockingWindowEventArgs e)
{
    // Close the window by sending out the CloseCommand so the ViewModel can take care of it.
    DockingWindow dockingWindow = e.Window;
    MyViewModel myViewModel = this.DataContext as MainViewModel;
    myViewModel.CloseCommand.Execute(dockingWindow.Name);
}

Is it somehow possible to have the events trigger commands directly instead of doing this? Depending on who you ask, having (almost) zero codebehind on the Views is preferable -- and I would certainly prefer that, since this isn't easy to test.

Comments (7)

Posted 5 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Claus,

I agree that having as little code in the view as possible is ideal.  Although in this case, I'm not sure what the best thing to do would be since the CloseCommand on your VM is somewhat specific to your scenario, and what you are doing seems to be a decent way to handle it.  In the window closed scenario, it generally is an event that reported that an action already occurred, versus something more like a command where an action needs to be taken.


Actipro Software Support

Posted 5 years ago by Claus Topholt
Avatar

Hi,

Well in a window close scenario, I think it's fairly reasonable to assume an action may need to be taken. The ViewModel for instance could need to update its list of open sub-ViewModels, call services that then update Models, etc. (and in fact all of this happens in my scenario).

My experience with MVVM is limited, and so I'm simply trying to find a good pattern for working with Actipro controls -- I'm by no means a purist. But would having the ability to link up events to ICommands not allow for a cleaner MVVM implementation in your opinion?

Answer - Posted 5 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Claus,

It's hard to say since it seems like adding commands for actions like close (that typically are events only) might make the API confusing for more general people.  We'll try and think about it a bit more and watch what other controls do.  For now, I'd stick with what you are doing.


Actipro Software Support

Posted 5 years ago by Claus Topholt
Avatar

Great, thanks for the help/input. Will soldier on :-)

Posted 3 years ago by Chris Klepeis
Avatar

I have to second this feature. I know you can bind CloseCommand in AvalonDock, and this is a critical thing in line of business applications:

- Prompting for close in a view model

- Ensuring that the user can close the window (E.G. its not currently performing something)

Essentially any operation that needs to cancel the event so that the window doesn't close.

Sure, we could handle it with the event, but its a pain, especially with asynchronous operations and trying to get it working with prism.

 

A simple example:

- A user selects to close a window

- The window closing event is fired and handled in the view code-behind

- The code-behind calls a view models ICommand's Execute, which prompts if the user wants to close. Since the prompt is asynchronous, e.Cancel and e.Handled are set to true in the view code-behind

- The user select "yes" they want to close the window, so now we have to set a flag in our view model then call someting to close the window

- The window closing event is fired again and hits the view code-behind, this time it recognizes that the flag on the view model is set, and does not set e.cancel or e.handled and it closes

 

This example is even more complicated when taking into account prism regions.

 

I'm trying to get this working by handling it with the events... but when a document is floatig in a floating docksite, and I click the close button on the floating docksite (not the document)... the documents are closed but the floating docksite remains... so now I'm trying to figure out how to determine if there's an open floating docksite with no documents so that I can manually close it.

 

        private void Docksite_WindowsClosing(object sender, ActiproSoftware.Windows.Controls.Docking.DockingWindowsEventArgs e)
        {
            foreach(var i in e.Windows.ToList())
            {
                if(i.DataContext is DockingItemViewModelBase)
                {
                    var viewModel = i.DataContext as DockingItemViewModelBase;
                    if (!viewModel.IsClosing)
                    {
                        // Perform whatever, then if the window should still close, IsClosing is set to true and the window Close()
                        // function is called
                        viewModel.CloseTabCommand.Execute();
                        e.Cancel = true;
                        e.Handled = true;
                    }
                }
                else
                {
                    throw new Exception("Can't handle tab close. Not of type DockingItemViewModelBase");
                }
            }
        }

[Modified 3 years ago]

Posted 3 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chris,

We've run into the same async issue in some tests of a UWP port of our Docking/MDI vNext.  When the WindowsClosing event fires, we need to show a prompt to the user in some cases, but prompts in UWP are only async and everything leading up to the WindowsClosing is synchronous.  Thus we effectively had to do what you described.  I'm not sure that using commands would improve much in this case though because even commands wouldn't be fired async due to everything above it being synchronous code.  There's a lot going on behind the scenes in closing a window because of the number of places and scenarios it can possibly be in, and the number of ways that process can be invoked in general.

In regards to the floating dock host issue, if you think there is a bug with our code, please try and repro this scenario in as simple of code as you can and email a ZIP of the sample to our support address.  Reference this thread in your email and rename the .zip file extension so it doesn't get spam blocked.  Then we'll debug what you send.

We tried a few things here including programmatically closing all documents in response to a menu item click, and that properly closed the floating dock host once all of its documents were closed.  There could be a scenario we're missing though, so the sample requested above would help with that.


Actipro Software Support

Posted 3 years ago by Chris Klepeis
Avatar

Of note in my example code above, the CloseTabCommand sets IsClosing to true and then removes the viewmodel from the prism region, which removes the document or tool window from the DockSite, but the floating window remains. I'm using the exact same prism region adapter and behavior class from the example code.

 

To duplicate the issue, using the PrismIntegration.Unity-CSharp example project:

Add code to DockingItemViewModelBase.cs

        public bool IsClosing { get; set; } = false;
        public ICommand CloseCommand { get; set; }

        public DockingItemViewModelBase()
        {
            CloseCommand = new DelegateCommand(() =>
            {
                // I would perform any verification checks, etc. here.

                IsClosing = true;
                var regionMgr = ServiceLocator.Current.GetInstance();
                // Remove this view model from the region
                regionMgr.Regions[ShellViewModel.MainRegionName].Deactivate(this);
                regionMgr.Regions[ShellViewModel.MainRegionName].Remove(this);
            });
        }



Add event handler for DockSite WindowsClosing:

        private void DockSite_WindowsClosing(object sender, Windows.Controls.Docking.DockingWindowsEventArgs e)
        {
            foreach (var i in e.Windows.ToList())
            {
                if (i.DataContext is DockingItemViewModelBase)
                {
                    var viewModel = i.DataContext as DockingItemViewModelBase;
                    if (!viewModel.IsClosing)
                    {
                        // Perform whatever, then if the window should still close, IsClosing is set to true and the window Close()
                        // function is called
                        viewModel.CloseCommand.Execute(null);
                        e.Cancel = true;
                        e.Handled = true;
                    }
                }
                else
                {
                    throw new Exception("Can't handle tab close. Not of type DockingItemViewModelBase");
                }
            }
        }

Run the sample project, and drag the two Document windows to a new window, then click the close button on the window (not the close button on the tab). The floating DockSite should remain visible, with no content. 

Also, when this happens and the floating window is visible with no contents, and the main application is closed, the floating window remains.

I will email this project (zipped) to your support email address as well.

The latest build of this product (v2018.1 build 0675) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.