AvalonDock and Loading Views/ViewModels in code

Oct 21, 2010 at 2:10 PM

I'm still new to WPF/CM, so if I'm heading down the wrong path here please let me know.

 

I'm building an app that will make heavy use of AvalonDock.  My initial plan was to setup the DockablePanels, DocumentPanes, etc. in the view, with ContentControls inside pointing to each panel's respective ViewModel/View.  The problem is that there is an issue with AvalonDock's ItemsSource binding, so if you try to bind a BindableCollection to a DocumentPane's ItemsSource and then change anything in that BindableCollection then you get an exception having to do with ItemsSource being in use.  From looking at their forums, that seems like a common issue when using AvalonDock in an MVVM app.

 

Instead my new plan is to build a Layout Manager that manages the AvalonDock DockingManager (handles creating all tabs/panels).  This isn't too difficult, my main issue is that I need to load Views/ViewModels in code and insert them as the Content for the generated Panels.  What is the best way to do that in CM?

 

Thanks!

Coordinator
Oct 21, 2010 at 2:38 PM

First, let me say that your approach is a good one. Typically in these advanced MDI scenarios with dockable windows, the standard databinding approach won't work. This is not a problem unique to AvalonDock.  In this case, I normally switch to a Supervising Controller pattern at the shell level, while continuing to use MVVM for the individual screens, tool windows, menus, etc.  To start putting these pieces together in code, have a look at the  ViewLocator and the ViewModelBinder. You may also want to look at how the WindowManager and the View.Model attached property are implemented to get some ideas. I think you can probably port some code from those places into your DockManager service. Let me know if you have any questions along the way.

Nov 12, 2010 at 8:23 PM

I am trying to setup an application using a dockmanager and attempting to use a supervising controller as suggested. I am struggling a little on how to hook everything up. The supervising controller needs a reference to the dock layout manager and also to the view model. Is there a way to accomplish this through Caliburn/MEF?

 

Regards,

Magnus

Nov 14, 2010 at 4:58 PM
Edited Nov 16, 2010 at 9:14 AM

I am facing the problem of a dock layout too, even if using a different dock manager (Divelements' SandDock).

I would like to share my toughts on the matter with Rob and others interested in this topic, since I already started coding something but I am still unsure if this approach makes sense in a MVVM environment .

First of all, instead of creating a specific controller outside the Caliburn.Micro framework, I decided to define a new interface inheriting from IWindowManager (IDockAwareWindowManager), to expose the methods used to open tabbed/docked/floating windows in the same way the standard WindowManager can open a window or a dialog; in short, the inteface just extends the IWindowManager interface, adding specific functions:

namespace Test
{
	using Caliburn.Micro;
	
	public interface IDockAwareWindowManager : IWindowManager
	{
            /// <summary>
            ///   Shows a docked window.
            /// </summary>
            /// <param name = "viewModel">The view model.</param>
            /// <param name = "context">The context.</param>
            /// <param name="selectWhenShown">If set to <c>true</c> the window will be selected when shown.</param>
            /// <param name = "dockSide">The dock side (Left, Right, Top, Bottom).</param>
            void ShowDockedWindow(object viewModel,
                                  object context,
                                  bool selectWhenShown = true,
                                  DockSide dockSide = DockSide.Left);

            /// <summary>
            ///   Shows a floating window.
            /// </summary>
            /// <param name = "viewModel">The view model.</param>
            /// <param name = "context">The context.</param>
            /// <param name="selectWhenShown">If set to <c>true</c> the window will be selected when shown.</param>
            void ShowFloatingWindow(object viewModel,
                                    object context,
                                    bool selectWhenShown = true);

            /// <summary>
            ///   Shows a document window.
            /// </summary>
            /// <param name = "viewModel">The view model.</param>
            /// <param name = "context">The context.</param>
            /// <param name="selectWhenShown">If set to <c>true</c> the window will be selected when shown.</param>
            void ShowDocumentWindow(object viewModel,
                                    object context,
                                    bool selectWhenShown = true);
	}
}

of course, the functions arguments may vary depending on the docking library used (or a proper interface could be defined to leverage the use of different dock managers).

Since docking libraries use a specialized element to leverage docking features (i.e. DockingManager in AvalonDock, DockSite in the Divelements' implementation), the DockAwareWindowManager should hold a reference to such core element, which is tipically window-dependant. I decided that such reference should be optionally passed in the constructor:

        /// <summary>
        /// Initializes a new instance of the <see cref="DockAwareWindowManager"/> class.
        /// </summary>
        /// <param name="mainWindow">The main window or <see langword="null"/> to use the application main window.</param>
        /// <param name="dockSite">The dock site or <see langword="null"/> to use the main window dock site.</param>
        public DockAwareWindowManager(Window mainWindow = null, DockSite dockSite = null)
        {
            m_MainWindow = mainWindow;
            m_DockSite = dockSite;
        }

I said 'optionally', since the docking manager could be extracted automatically, using this kind of approach:

        /// <summary>
        /// Gets the dock site associated to the window.
        /// </summary>
        /// <param name="window">The window.</param>
        /// <returns>The retrieved dock site.</returns>
        /// <exception cref="InvalidOperationException">No dock site could be retrieved.</exception>
        private DockSite GetDockSite(Window window)
        {
            DockSite dockSite = m_DockSite;

            if (dockSite == null)
            {
                Window parentWindow = GetParentWindow(window);

                if (parentWindow != null)
                    dockSite = VisualTreeHelperEx.FindChild<DockSite>(parentWindow);
            }

            if (dockSite == null)
                throw new InvalidOperationException("Unable to retrieve a proper dock site");
            
            return dockSite;
        }
        /// <summary>
        /// Gets the parent window.
        /// </summary>
        /// <param name="window">The window.</param>
        /// <returns>The parent window, or <see langword="null"/> if no parent window was found.</returns>
        private Window GetParentWindow(Window window)
        {
            Window parentWindow = m_MainWindow ?? (Application.Current != null ? Application.Current.MainWindow : null);
            return parentWindow != window ? parentWindow : null;
        }

Note that the DockSite class is the equivalent of DockingManager, m_DockSite and m_MainWindow are the values passed as constructor arguments when creating the DockAwareWindowManager (and thus can be null) and that if no values are defined at constructor level, the code drills-down the visual tree of the Application's MainWindow to retrieve such object (using the VisualTreeHelperEx.FindChild<DockSite> custom function). This means that, by default, window managers of this kind would be able to retrieve the default docking manager without any prior knowledge. Of course, the retrieved reference can be cached once retrieved the first time, to speed-up the process afterwards.

Given this kind of implementation, I would register the DockAwareWindowManager in the IoC,  so that I could either:

  • retrieve it when needed inside the shell ViewModel using

IoC.GetInstance<IDockAwareWindowManager>();

  • declare the shell ViewModel constructor as
public ShellViewModel(IDockAwareWindowManager manager)
{
     ...
}

and register the ViewModel as a singleton, letting to the IoC the burden of resolving the dependency.

 

Note that to make this work, the ViewModelBinder.Bind method could have to be modified (it really depends on the DockableWindow implementation... in the Divelements case it is not a ContentControl, so GetSignificantView would fail), as long as probably some more default CM default implementations (I am unsure, since I have not yet a working sample).

I am still implementing this approach, and I don't have a working sample yet, but in my opinion it should work nicely, since it should fit in the CM framework pretty well...

What are your opinions about this?

 

Edit: I made some changes to the interface definition, since I am still developing this solution... moreover I am trying to make it generic enough to be applied to different docking libraries.

Dec 1, 2010 at 3:13 PM

I like your proposed design.  I was using a separate DockingManagerService, but it seems cleaner to integrate that into the WindowManager.  I've got a couple questions for you on your implementation:

 

What is the context object used for that you are passing into the Show* methods?

 

Could you post what one of your Show* methods looks like?  I'm curious to see how you handle loading/displaying the view.

 

Thanks!

Dec 1, 2010 at 3:30 PM
Edited Dec 1, 2010 at 8:02 PM

The context parameter is used during the view discovery (it is mainly used as a key) and it is passed to the ViewModelBinder.Bind method (have a look at the WindowManager.CreateWindow for reference).

My implementation closely matches the standard WindowManager one (I liked the design), so my DockAwareWindowManager looks like this:

 

/// <summary>
///   Shows a docked window.
/// </summary>
/// <param name = "viewModel">The view model.</param>
/// <param name = "context">The context.</param>
/// <param name = "selectWhenShown">If set to <c>true</c> the window will be selected when shown.</param>
/// <param name = "dockSide">The dock side.</param>
public void ShowDockedWindow(object viewModel,
                             object context,
                             bool selectWhenShown = true,
                             DockSide dockSide = DockSide.Left)
{
    DockableWindow dockableWindow = CreateDockable(viewModel, context);
    dockableWindow.Dock(selectWhenShown ? WindowOpenMethod.OpenSelectActivate : WindowOpenMethod.Background, (Dock)dockSide);
}
/// <summary>
///   Creates the dockable window.
/// </summary>
/// <param name = "rootModel">The root model.</param>
/// <param name = "context">The context.</param>
/// <param name = "isDocument">If set to <c>true</c>, the created window will be a document window.</param>
/// <returns>The dockable window.</returns>
private DockableWindow CreateDockable(object rootModel,
                                      object context,
                                      bool isDocument = false)
{
    var view = EnsureDockWindow(rootModel, ViewLocator.LocateForModel(rootModel, null, context), isDocument);
    ViewModelBinder.Bind(rootModel, view, context);

    var haveDisplayName = rootModel as IHaveDisplayName;
    if (haveDisplayName != null && !ConventionManager.HasBinding(view, DockableWindow.TitleProperty))
        view.SetBinding(DockableWindow.TitleProperty, "DisplayName", BindingMode.TwoWay);

    new DockableWindowConductor(rootModel, view);

    return view;
}
/// <summary>
///   Ensures that a dockable window is created and properly set-up.
/// </summary>
/// <param name = "viewModel">The view model.</param>
/// <param name = "view">The view.</param>
/// <param name = "isDocument">If set to <c>true</c>, the dockable window is a document window.</param>
/// <returns>The dockable window.</returns>
private DockableWindow EnsureDockWindow(object viewModel,
                                        object view,
                                        bool isDocument = false)
{
    const WindowCloseMethod closeMethod = WindowCloseMethod.Detach; //The window is destroyed once closed...
    var window = view as DockableWindow;

    if (window == null)
    {
        if (isDocument)
        {
            window = new DocumentWindow(GetDockSite(), string.Empty) {
                                  Child = view as UIElement,
                                  DockingRules = new DockingRules(true, true, true),
                                  CloseMethod = closeMethod };
        }
        else
        {
            window = new DockableWindow(GetDockSite(), string.Empty) {
                                  Child = view as UIElement,
                                  DockingRules = new DockingRules(true, true, true),
                                  CloseMethod = closeMethod };
        }

        window.SetValue(IsElementGeneratedProperty, true);
    }

    return window;
 }

/// <summary>
///   Gets the dock site associated to the window.
/// </summary>
/// <param name = "window">The window.</param>
/// <returns>The retrieved dock site.</returns>
/// <exception cref = "InvalidOperationException">No dock site could be retrieved.</exception>
private DockSite GetDockSite(Window window = null)
{
    DockSite dockSite = m_DockSite;

    if (dockSite == null)
    {
        Window parentWindow = GetParentWindow(window);

        if (parentWindow != null)
            dockSite = VisualTreeHelper.FindChild<DockSite>(parentWindow);
    }

    if (dockSite == null)
        throw new InvalidOperationException("Unable to retrieve a proper dock site ");

    return dockSite;
}

 

The DockableWindowConstructor is more or less the same as the Caliburn.Micro.WindowManager.WindowConductor, since the purpose is exactly the same (managing the lifecycle of the dockable windows):

 

/// <summary>
///   The dockable window conductor, used to allow for interaction between view and view model.
/// </summary>
private class DockableWindowConductor
{
    #region Fields
    /// <summary>
    ///   The view.
    /// </summary>
    private readonly DockableWindow m_View;

    /// <summary>
    ///   The view model.
    /// </summary>
    private readonly object m_ViewModel;

    /// <summary>
    ///   The flag used to identify the view as closing.
    /// </summary>
    private bool m_IsClosing;

    /// <summary>
    ///   The flag used to determine if the view requested deactivation.
    /// </summary>
    private bool m_IsDeactivatingFromView;

    /// <summary>
    ///   The flag used to determine if the view model requested deactivation.
    /// </summary>
    private bool m_IsDeactivatingFromViewModel;
    #endregion

    /// <summary>
    ///   Initializes a new instance of the <see cref = "DockableWindowConductor" /> class.
    /// </summary>
    /// <param name = "viewModel">The view model.</param>
    /// <param name = "view">The view.</param>
    public DockableWindowConductor(object viewModel, DockableWindow view)
    {
        m_ViewModel = viewModel;
        m_View = view;

        var activatable = viewModel as IActivate;
        if (activatable != null)
            activatable.Activate();

        var deactivatable = viewModel as IDeactivate;
        if (deactivatable != null)
        {
            view.Closed += OnClosed;
            deactivatable.Deactivated += OnDeactivated;
        }

        var guard = viewModel as IGuardClose;
        if (guard != null)
            view.Closing += OnClosing;
    }

    /// <summary>
    ///   Called when the view has been closed.
    /// </summary>
    /// <param name = "sender">The sender.</param>
    /// <param name = "e">The <see cref = "System.EventArgs" /> instance containing the event data.</param>
    private void OnClosed(object sender, EventArgs e)
    {
        m_View.Closed -= OnClosed;
        m_View.Closing -= OnClosing;

        if (m_IsDeactivatingFromViewModel)
            return;

        var deactivatable = (IDeactivate)m_ViewModel;

        m_IsDeactivatingFromView = true;
        deactivatable.Deactivate(true);
        m_IsDeactivatingFromView = false;
    }

    /// <summary>
    ///   Called when the view has been deactivated.
    /// </summary>
    /// <param name = "sender">The sender.</param>
    /// <param name = "e">The <see cref = "Caliburn.Micro.DeactivationEventArgs" /> instance containing the event data.</param>
    private void OnDeactivated(object sender, DeactivationEventArgs e)
    {
        ((IDeactivate)m_ViewModel).Deactivated -= OnDeactivated;

        if (!e.WasClosed || m_IsDeactivatingFromView)
            return;

        m_IsDeactivatingFromViewModel = true;
        m_IsClosing = true;
        m_View.Close();
        m_IsClosing = false;
        m_IsDeactivatingFromViewModel = false;
    }

    /// <summary>
    ///   Called when the view is about to be closed.
    /// </summary>
    /// <param name = "sender">The sender.</param>
    /// <param name = "e">The <see cref = "System.ComponentModel.CancelEventArgs" /> instance containing the event data.</param>
    private void OnClosing(object sender, CancelEventArgs e)
    {
        var guard = (IGuardClose)m_ViewModel;

        if (m_IsClosing)
        {
            m_IsClosing = false;
            return;
        }

        bool runningAsync = false, shouldEnd = false;

        bool async = runningAsync;
        guard.CanClose(canClose =>
                        {
                            if (async && canClose)
                            {
                                m_IsClosing = true;
                                m_View.Close();
                            }
                            else
                                e.Cancel = !canClose;

                            shouldEnd = true;
                        });

        if (shouldEnd)
            return;

        runningAsync = e.Cancel = true;
    }
}

Note that this code is taken from a current test I am performing (and is still not concluded), so there could be some methods (e.g. VisualTreeHelper.FindUp<T>) or extensions that are not part of Caliburn.Micro or .NET. Moreover, the test is targeting the Divelements.SandDock library, so it should be adapted to AvalonDock to be meaningful.

Dec 1, 2010 at 7:48 PM

Thanks for the quick reply!  Your dock manager fits in with the CM framework much nicer than mine.  Does DockAwareWindowManager inherit from WindowManager as well as IDockAwareWindowManager, or did you do a complete replacement of WindowManager?

Dec 1, 2010 at 7:58 PM

I had to duplicate existing WindowManager functionalities (just copy/paste from the original class, plus a slight modification to ViewModelBinder.Bind to use my custom IWindowManager instead of the default one), since the current implementation is not so friendly when it comes to customization (see this issue).

I would prefer to to inherit from WindowManager and implement IDockAwareWindowManager as you can guess, so as soon as Rob deals with the issue, I am going to get rid of the 'redundant' code and keep the default implementation.

Dec 3, 2010 at 8:20 AM
Edited Dec 3, 2010 at 8:50 AM

Hi Blade,
first of all, thank you very much for this very nice class. Just changed it in order to use it with AvalonDock and it works very well. I just have a quite stupid question regarding the IoC, Constructor and GetDockSite-Method.

You mentioned that

Given this kind of implementation, I would register the DockAwareWindowManager in the IoC,  so that I could either:

  • retrieve it when needed inside the shell ViewModel using

IoC.GetInstance<IDockAwareWindowManager>();

  • declare the shell ViewModel constructor as
public ShellViewModel(IDockAwareWindowManager manager)
{
...
}

The point is, that I have no idea how to realize this. If I'm using  batch.AddExportedValue(Of IDockAwareWindowManager)(New DockAwareWindowManager()) you see, that there are no parameters for the constructor - with the result that m_MainWindow and m_DockSite ist null.

For the time being I'm creating an Instance of the DockAwareWindowManager within the ViewAttached-Event of my ShellViewModel. But then, the VisualTreeHelper-Method does not work. So I might change this code to LogicalTreeHelper.

But generally I think I have a big lag of experience and I'm doing a lot of things wrong. So... How would you register the DockAwareWindowManager within the Bootstrapper in order to use with IoC.Get<>?

 

EDIT: Just checked again. My only problem is, that VisualTreeHelper does not count any Children of the Applicationwindow. Even in the Activated-Event of the ShellViewModel, the ShellView is not loaded. Any hints about the correct place in order to "show the first document?"

 



Dec 3, 2010 at 9:39 AM
Edited Dec 3, 2010 at 9:45 AM

Even if I put the parameters in the manager, I must admit I have no need for them (hence the fact that they are optional). Tipically the DockSite is inside the Application.MainWindow, so the fallback mechanism (i.e. retrieving at runtime a reference to the DockSite) works pretty well. In case of a really complex (akward?) UI where multiple application Windows having dockable content are present, the constructor can come in handy, and a manager should be created on a per-window basis.

Note that to use VisualTreeHelper methods, your visual tree must be fully constructed. So, if you need to open a new docked window, you need to wait until the shell view is (at least) loaded.

This is what I do in my current example (note that the method is declared inside the Sheel view-model which is a Conductor<IScreen>.Collection.AllActive):

/// <summary>
///   Called when an attached view's Loaded event fires.
/// </summary>
/// <param name = "view">The displayed view.</param>
protected override void OnViewLoaded(object view)
{
    base.OnViewLoaded(view);

    var manager = IoC.Get<IDockAwareWindowManager>();
    manager.ShowDockedWindow(Items.First(s => s is SampleViewModel), null, true, DockSide.Right);
}

Regarding the registering part, I simple decorated the DockAwareWindowManager class with proper attributes

/// <summary>
///   Class used to define a dock aware window manager.
/// </summary>
[Export(typeof(IWindowManager))]
[Export(typeof(IDockAwareWindowManager))]
public class DockAwareWindowManager : IDockAwareWindowManager
{
       //Some code...
}

In case the class is declared in a shared library and is not exported, you can simply register an instance of the manager during the Boostrapper.Configure method (which is more or less what your are doing at the moment during the ViewAttached.

So, if I am right and the only problem with your code is just that you are trying to open a docked window before the load phase. You can get a reference to the window during the attach process, but the window is probably not loaded yet, so the DockSite/DockManager is not present yet.

If this is the case, I suppose that a simple check-and-delay approach can fix everything

/// <summary>
/// Called when the view is attached.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="Caliburn.Micro.ViewAttachedEventArgs"/> instance containing the event data.</param>
private void OnViewAttached(object sender, ViewAttachedEventArgs e)
{
    System.Windows.Window window = ((System.Windows.Window)e.View);
    if(window.IsLoaded)
    {
        //Create the IDockAwareWindowManager instance, referencing the Window
        //then export it...
    }
    else
    {
        System.Windows.RoutedEventHandler handler = null;
        handler = (s, args) =>
                    {
                        ((System.Windows.Window)s).Loaded -= handler;
                        //Create the IDockAwareWindowManager instance, referencing the Window
                        //then export it...
                    };
        window.Loaded += handler;
    }
}

Note that this code requires that you define the shell view as a Window, but you could define it as a UserControl and navigate to the parent Window (or DockSite/DockManager) effortlessy.

Just to re-cap:

  1. If you want to avoid defining the DockSite/DockManager and Window, you need to call the show methods after the shell hosting the dockable window is loaded
  2. If you prefer to create an IDockAwareWindowManager targeting a specific DockSite/DockManager or Window, you need to delay the manager creation until the window itself is loaded, to ensure that you can retrieve a proper reference

Edit: By the way, I suppose I should change the specialized constructor signature to accept either a Window or a DockSite/DockManager... since the former is required only if the latter is not specified.

Dec 3, 2010 at 1:29 PM

Thank you very much for your detailed explanation. It make the whole thing now much clearer for me :)

Meantime I copied GetDockSite an used LogicalTreeHelper but I will change this back, according to your explanations.
I simply don't realized that I could use IoC.Get<> within the View itself in order to get the exakt point of time,
the VisualTree has been built.

Nevertheless, your tip with eht RoutedEventHandler in "OnViewAttached" seems to be the method of my choice.
I still have to learn a lot of things. Coming from VB6 it's quite different and allows fare more possibilties - and that's not a matter of C# or VB.

Dec 3, 2010 at 1:44 PM

Just to be precise ;) : you can of course use the IoC wherever and whenever you like (even if I prefer to avoid to use it ouside of the model or view-model), but notice that I did not use it within the view, but inside the view-model, since the OnViewAttached function is an handler over the ViewAttached event of an IViewAware view-model.

I agree that coming from VB6 there are a lot of changes, but I can assure you that even starting from C# + WinForms there is quite a steep learning curve! :)

Nov 8, 2011 at 4:40 PM

some guys in the telerik forum who wanted to see my implementation for connecting CM to RadDocking (Silverlight) thought it would be a good idea to share my code to the CM community. so, here it is...

in case you find it interesting (see below), do whatever you want with it. if you think it's useless, just ignore it. there is just one thing: if anybody finds a bug - please let me know...

cheers,
stephan

 

// A dock manager acts as a mediator between a conductor and an optionally
// given docking control which is typically found in the view of the conductor.
// Even though the interface is pretty simple and generic, it might suffice many
// if not all (?) docking libraries.
public interface IDockManager
{
    void Link(IConductor conductor, FrameworkElement dock = null);
}

// A view model may wish to implement a dedicated interface (probably dependent
// on the functionality of the concrete docking library) like this to express
// certain docking relevant preferences which can be addressed by the docking manager.
public interface IHaveDockPreferences
{
    DockingState InitialState { get; }
}

public enum DockingState
{
    DockedCenter,
    DockedLeft,
    DockedTop,
    DockedRight,
    DockedBottom
}

// A dock manager is usually injected in the according conductor view model.
// Here we omit the concrete dock control and let the dock manager find it in the
// visual tree. This of course requires that the Link-method is called after the
// conductor's view (which hopefully contains the docking control) is loaded.
public class WorkspaceViewModel : Conductor<object>.Collection.OneActive
{
    [Import]
    public IDockManager DockManager { get; set; }

    protected override void OnViewLoaded(object view)
    {
        base.OnViewLoaded(view);
		
        DockManager.Link(this);

        ActivateItem(SomePlainViewModel);
        ActivateItem(SomeOtherViewModelWhichImplementsIHaveDockPreferences);
		
	...
    }
	
    ...
}

// My implementation of IDockManager for Telerik's RadDocking (SL). It basically acts as a
// mediator between a given IConductor (IConductActiveItem in this case) and an optionally
// given docking control (RadDocking in this case) using the respective events of each one
// and creating/removing necessary stuff like panes etc. on the fly.
[Export(typeof(IDockManager))]
public class DockManager : IDockManager
{
    private IConductActiveItem _conductor;
    private RadDocking _dock;

    private bool _activatedFromViewModel;
    private bool _actuallyClosing;
    private bool _deactivatingFromView;


    public void Link(IConductor conductor, FrameworkElement dock = null)
    {
        if (_conductor != null || _dock != null)
        {
            throw new InvalidOperationException("Dock manager is already linked");
        }

        _conductor = conductor as IConductActiveItem;
        _dock = dock as RadDocking ?? FindDock();

        if (_conductor == null || _dock == null)
        {
            throw new InvalidOperationException("Invalid conductor or docking control");
        }

        _conductor.ActivationProcessed += OnActivationProcessed;
        _dock.ActivePaneChanged += OnActivePaneChanged;
        _dock.PreviewClose += OnPreviewClose;
        _dock.Close += OnClose;
    }


    protected virtual void OnActivationProcessed(object s, ActivationProcessedEventArgs e)
    {
        if (!e.Success)
        {
            return;
        }

        object viewModel = e.Item;
        RadPane pane = FindPane(viewModel);
        if (pane == null)
        {
            _activatedFromViewModel = true;

            pane = CreatePane(viewModel);
            AttachPane(viewModel, pane);

            var deactivatable = viewModel as IDeactivate;
            if (deactivatable != null)
            {
                deactivatable.Deactivated += OnDeactivated;
            }
        }

        _dock.ActivePane = pane;
    }


    protected virtual void OnActivePaneChanged(object s, ActivePangeChangedEventArgs e)
    {
        if (_activatedFromViewModel)
        {
            _activatedFromViewModel = false;
            return;
        }

        RadPane pane = e.NewPane;
        if (pane == null)
        {
            return;
        }

        _conductor.ActivateItem(pane.DataContext);
    }


    protected virtual void OnDeactivated(object s, DeactivationEventArgs e)
    {
        if (!e.WasClosed)
        {
            return;
        }

        ((IDeactivate) s).Deactivated -= OnDeactivated;

        if (_deactivatingFromView)
        {
            return;
        }

        RemovePane(FindPane(s));
    }


    protected virtual void OnPreviewClose(object s, StateChangeEventArgs e)
    {
        RadPane pane = e.Panes.FirstOrDefault();
        if (pane == null)
        {
            return;
        }

        var guard = pane.DataContext as IGuardClose;
        if (guard == null)
        {
            return;
        }

        if (e.Handled)
        {
            return;
        }

        if (_actuallyClosing)
        {
            _actuallyClosing = false;
            return;
        }

        bool runningAsync = false;
        bool shouldEnd = false;

        guard.CanClose(canClose =>
        {
            Execute.OnUIThread(() =>
            {
                if (runningAsync && canClose)
                {
                    _actuallyClosing = true;
                    pane.IsHidden = true;
                }
                else
                {
                    e.Handled = !canClose;
                }

                shouldEnd = true;
            });
        });

        if (shouldEnd)
        {
            return;
        }

        e.Handled = true;
        runningAsync = true;
    }


    protected virtual void OnClose(object s, StateChangeEventArgs e)
    {
        RadPane pane = e.Panes.FirstOrDefault();
        if (pane == null)
        {
            return;
        }

        _deactivatingFromView = true;
        _conductor.CloseItem(pane.DataContext);
        RemovePane(pane);
        _deactivatingFromView = false;
    }


    protected virtual RadDocking FindDock()
    {
        return Application.Current.RootVisual.FindChildByType<RadDocking>();
    }


    protected virtual RadPane FindPane(object viewModel)
    {
        return _dock.Panes.FirstOrDefault(pane => pane.DataContext == viewModel);
    }


    protected virtual RadPane CreatePane(object viewModel)
    {
        RadPane pane = EnsurePane(viewModel, ViewLocator.LocateForModel(viewModel, null, null));
        ViewModelBinder.Bind(viewModel, pane, null);

        var haveDisplayName = viewModel as IHaveDisplayName;
        if (haveDisplayName != null && !ConventionManager.HasBinding(pane, HeaderedContentControl.HeaderProperty))
        {
            var binding = new Binding("DisplayName") { Mode = BindingMode.TwoWay };
            pane.SetBinding(HeaderedContentControl.HeaderProperty, binding);
        }

        return pane;
    }


    protected virtual RadPane EnsurePane(object viewModel, object view)
    {
        var pane = view as RadPane;

        if (pane == null)
        {
            pane = new RadPane { DataContext = viewModel, Content = view };
            pane.SetValue(View.IsGeneratedProperty, true);
        }

        return pane;
    }


    protected virtual void AttachPane(object viewModel, RadPane pane)
    {
        DockState? dockState = GetDockState(viewModel);

        RadSplitContainer splitContainer;
        if (dockState == null)
        {
            splitContainer = _dock.DocumentHost as RadSplitContainer;
            if (splitContainer == null)
            {
                splitContainer = new RadSplitContainer();
                splitContainer.SetValue(View.IsGeneratedProperty, true);
                _dock.DocumentHost = splitContainer;
            }
        }
        else
        {
            splitContainer = _dock.Items.OfType<RadSplitContainer>().FirstOrDefault(
                container => container.GetValue(RadDocking.DockStateProperty) as DockState? == dockState);
            if (splitContainer == null)
            {
                splitContainer = new RadSplitContainer { InitialPosition = dockState.Value };
                splitContainer.SetValue(View.IsGeneratedProperty, true);
                _dock.Items.Add(splitContainer);
            }
        }

        RadPaneGroup paneGroup = splitContainer.Items.OfType<RadPaneGroup>().FirstOrDefault();
        if (paneGroup == null)
        {
            paneGroup = new RadPaneGroup { IsContentPreserved = true };
            paneGroup.SetValue(View.IsGeneratedProperty, true);
            paneGroup.SelectedItemRemoveBehaviour = SelectedItemRemoveBehaviour.SelectNone;
            splitContainer.Items.Add(paneGroup);
        }

        paneGroup.AddItem(pane, DockPosition.Center);
    }


    protected virtual void RemovePane(RadPane pane)
    {
        if (pane == null)
        {
            return;
        }

        RadPaneGroup paneGroup = pane.PaneGroup;
        pane.RemoveFromParent();
        if (paneGroup == null || paneGroup.HasItems)
        {
            return;
        }

        RadSplitContainer splitContainer = paneGroup.ParentContainer;
        paneGroup.RemoveFromParent();
        if (splitContainer == null || splitContainer.HasItems)
        {
            return;
        }

        if (splitContainer.IsInDocumentHost)
        {
            _dock.DocumentHost = null;
        }
        else
        {
            _dock.Items.Remove(splitContainer);
        }
    }


    protected virtual DockState? GetDockState(object viewModel)
    {
        if (viewModel is IHaveDockPreferences)
        {
            var dockPreferences = (IHaveDockPreferences) viewModel;

            switch (dockPreferences.InitialState)
            {
                case DockingState.DockedLeft:
                    return DockState.DockedLeft;
                case DockingState.DockedTop:
                    return DockState.DockedTop;
                case DockingState.DockedRight:
                    return DockState.DockedRight;
                case DockingState.DockedBottom:
                    return DockState.DockedBottom;
            }
        }
        return null;
    }
}

Feb 21, 2012 at 3:59 PM

Excuse me pauli7,

your implementation of dockmanager worked perfectly till last Telerik's update 2012 Q1 ... now when I drag a tab it crashes with Object not set to an istance exception... have you tried it?

Thanks

Apr 29 at 5:08 PM
Has anyone done anything more recent in terms of creating a dock manager for RadDock and the latest builds of Caliburn.Micro? The material above is getting dated as well as being incomplete given the view side of things is not shown.