reactivate exiting window using WindowManager

Topics: Framework Services, UI Architecture
Jan 29, 2013 at 11:23 AM
Edited Jan 29, 2013 at 1:17 PM

I am using WPF with the currently latest and greatest version of Caliburn.Micro (1.4.1). I use IWindowManager.ShowWindow(...) to open an new modeless window:

private void OpenOrReactivateInfoView()
{
    if(this.subViewModel == null)
    {
        this.subViewModel = new SubViewModel();
    }

    if(!this.subViewModel.IsActive)
    {
        this.windowManager.ShowWindow(this.subViewModel);
    }
    else
    {
        // refocus the existing window somehow, if it is not focused
    }
}

Instead of opening a new window each time when OpenOrReactivateInfoView() is called, I would like to check whether the window ist still open and if it is, the existing window should just regain focus. 

What would we be a good Calibrun.Micro-way to solve this? I sure would like to avoid keeping a reference to the window (or any UIElement for that matter) itself in the viewmodel. Also note that this is a common behavior for a lot of modeless dialogs, so it is preferred solve this in a generic reusable way. 

Does Caliburn.Micro already have means for this built in?

Now I could make SubViewModel implement IViewAware (or use the Screen class) and hold a reference to the window in the  SubViewModel but as mentioned earlier I try to avoid view awareness and I also would like to have some sort of reusable generic code that is as view-agnostic as possible.

Jan 29, 2013 at 1:32 PM

You simply need to call the Activate function on the Screen view-model or, in case you use a Conductor, the ActivateItem function on the Conductor itself.

Jan 29, 2013 at 1:40 PM
Edited Jan 29, 2013 at 1:43 PM

SubViewModel is derived from Screen. Screen.IsActive is true as long as the window is not closed, so calling Activate will not refocus the existing window. It will just do nothing.

Jan 29, 2013 at 2:00 PM
Edited Jan 29, 2013 at 2:02 PM

Have you tried calling Deactivate(false) on the window view-model and then call Activate()?

If I remember correctly, as long as the window view-model is not activated, the window is re-focused upon activation.

Edit: On a side note, depending on the Conductor/pseudo-Conductor you use to manage your screens, the IsActive property could be false even if a Window is still present. If a screen has IsActive = false, it doesn't mean necessary that the attached view has been closed. It just means that it is not the currently active element (in the context of its parent Conductor or presudo-Conductor).

Jan 29, 2013 at 2:09 PM
Edited Jan 29, 2013 at 2:11 PM

The Screen derived instances I want to handle the I way I described above, are always created using the WindowManager. So their parent "pseudo-Conductor" should always be the the windowmanager itself.

Anyway I implemented it the way you suggested but the window is not refocused:

        public void OpenOrReactivateSubView()
        {
            if (this.subViewModel == null)
            {
                this.subViewModel = new SubViewModel();
            }

            if (this.subViewModel.IsActive)
            {
                ((IDeactivate)this.subViewModel).Deactivate(false);
                ((IActivate)this.subViewModel).Activate();
                return;
            }

            this.windowManager.ShowWindow(this.subViewModel);
        }
Jan 29, 2013 at 3:20 PM
Edited Jan 29, 2013 at 3:29 PM

I have come up with this extension method. It works but I am not particulary happy with it, it is still somewhat hackish. Do you see any other issues with it? 

using System;
using System.Collections.Generic;
using Caliburn.Micro;

public static class WindowManagerExtensions
{
    /// <summary>
    /// Shows a non-modal window for the specified model or refocuses the exsiting window.  
    /// </summary>
    /// <remarks>
    /// If the model is already associated with a view and the view is a window that window will just be refocused
    /// and the parameter <paramref name="settings"/> is ignored.
    /// </remarks>
    public static void FocusOrShowWindow(this IWindowManager windowManager,
                                         object model,
                                         object context = null,
                                         IDictionary<string, object> settings = null)
    {
        var activate = model as IActivate;
        if (activate == null)
        {
            throw new ArgumentException(
                string.Format("An instance of type {0} is required", typeof (IActivate)), "model");
        }

        var viewAware = model as IViewAware;
        if (viewAware == null)
        {
            throw new ArgumentException(
                string.Format("An instance of type {0} is required", typeof (IViewAware)), "model");
        }

        if (!activate.IsActive)
        {
            windowManager.ShowWindow(model, context, settings);
            return;
        }

        var view = viewAware.GetView(context);
        if (view == null)
        {
            throw new InvalidOperationException("View aware that is active must have an attached view.");
        }

        var focus = view.GetType().GetMethod("Focus");
        if (focus == null)
        {
            throw new InvalidOperationException("Attached view requires to have a Focus method");
        }

        focus.Invoke(view, null);
    }
}
Jan 29, 2013 at 3:24 PM

Should be fine, yet I still remember using view-model activation to trigger window re-focus... as soon as I have a bit of time, I'll search over my test projects to see if I'm imagining things...