Search code examples
c#mvvmprismmefavalondock

PRISM 5 MEF AvalonDock 2.0 DataAdapter Registered Views and Parent IsSelected


I am attempting to build an MVVM Windows Application Using PRISM 5 and I have wrapped my main content window with an AvalonDock (Wrapping Code Below).

    using Microsoft.Practices.Prism.Regions;
    using Xceed.Wpf.AvalonDock;
    using System.Collections.Specialized;
    using System.Windows;
    using Xceed.Wpf.AvalonDock.Layout;

namespace Central.Adapters
{
    using System.Linq;

    public class AvalonDockRegionAdapter : RegionAdapterBase<DockingManager>
    {
        /// <summary>
        /// This ties the adapter into the base region factory.
        /// </summary>
        /// <param name="factory">The factory that determines where the modules will go.</param>
        public AvalonDockRegionAdapter(IRegionBehaviorFactory factory)
            : base(factory)
        {

        }

        /// <summary>
        /// Since PRISM does not support the Avalon DockingManager natively this adapter provides the needed support. 
        /// </summary>
        /// <param name="region">This is the region that resides in the DockingManager.</param>
        /// <param name="regionTarget">The DockingManager that needs the window added.</param>
        protected override void Adapt(IRegion region, DockingManager regionTarget)
        {
            region.Views.CollectionChanged += (sender, e) =>
            {
                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        AddAnchorableDocument(regionTarget, e);
                        break;
                    case NotifyCollectionChangedAction.Remove:                    
                        break;
                }
            };
        }

        /// <summary>
        /// This adds the window as an anchorable document to the Avalon DockingManager.
        /// </summary>
        /// <param name="regionTarget">The DockingManager instance.</param>
        /// <param name="e">The new window to be added.</param>
        private static void AddAnchorableDocument(DockingManager regionTarget, NotifyCollectionChangedEventArgs e)
        {
            foreach (FrameworkElement element in e.NewItems)
            {
                var view = element as UIElement;
                var documentPane = regionTarget.Layout.Descendents().OfType<LayoutDocumentPane>().FirstOrDefault();

                if ((view == null) || (documentPane == null))
                {
                    continue;
                }
                var newContentPane = new LayoutAnchorable
                                         {
                                             Content = view,
                                             Title = element.ToolTip.ToString(),
                                             CanHide = true,
                                             CanClose = false
                                         };

                documentPane.Children.Add(newContentPane);
            }
        }

        /// <summary>
        /// This returns the region instance populated with all of its contents.
        /// </summary>
        /// <returns>DockingManager formatted region.</returns>
        protected override IRegion CreateRegion()
        {
            return new AllActiveRegion();
        }
    }
}

I then register this adapter in the bootstrapper this way:

protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
        {
            var mappings = base.ConfigureRegionAdapterMappings();
            if (mappings == null)
            {
                return null;
            }

            mappings.RegisterMapping(typeof(DockingManager), new AvalonDockRegionAdapter(ConfigureDefaultRegionBehaviors()));
            return mappings;
        }

The problem that I am facing is that other region UI elements will require certain LayoutAnchorable windows to become the active and selected window. The content I am feeding into the LayoutAnchorable object is a ContentControl.

In my View's ViewModel I have a property that I am successfully setting using another UI element's interaction. However I am unable to make the connection from ViewModel(Property) -> ContentContro(View) -> LayoutAnchorable(View's Parent).IsSelected or ,IsActive.

I know how to bind to a parent object but that eats up the property and does not allow me to bind it to the ViewModel property as well. I also have no problem binding to a ViewModel property, but that is useless unless I can get it to set the parent property. I have also attempted View based events. Problem with this is that once the view loads it doe not like calling its own events anymore unless it is caused by user interaction directly with that view.

In short I just want to display the appropriate window when needed based on an interaction in another part of my program. Maybe I am making this more complicated than it needs to be. Any assistance on this would be greatly appreciated.

Thanks James


Solution

  • As I took a break from the problem at had I looked at it from another perspective. To solve the issue I decided to store the instance of the content panes containing the views into a singleton dictionary class:

    using System;
    using System.Collections.Generic;
    using Xceed.Wpf.AvalonDock.Layout;
    
    namespace Central.Services
    {
        public class DockinWindowChildObjectDictionary
        {
            private static Dictionary<string, LayoutAnchorable> _contentPane = new Dictionary<string, LayoutAnchorable>();
            private static readonly Lazy<DockinWindowChildObjectDictionary> _instance = 
                new Lazy<DockinWindowChildObjectDictionary>(()=> new DockinWindowChildObjectDictionary(), true);
            public static DockinWindowChildObjectDictionary Instance 
            { 
                get
                {
                    return _instance.Value;
                } 
            }
    
            /// <summary>
            /// Causes the constructor to be private allowing for proper use of the Singleton pattern.
            /// </summary>
            private DockinWindowChildObjectDictionary()
            {
    
            }
    
            /// <summary>
            /// Adds a Content Pane instance to the dictionary.
            /// </summary>
            /// <param name="title">The title given to the Pane during instantiation.</param>
            /// <param name="contentPane">The object instance.</param>
            public static void Add(string title, LayoutAnchorable contentPane)
            {
                _contentPane.Add(title, contentPane);
            }
    
            /// <summary>
            /// If a window needs to be removed from the dock this should be used 
            /// to also remove it from the dictionary.
            /// </summary>
            /// <param name="title">The title given to the Pane during instantiation.</param>
            public static void Remove(string title)
            {
                _contentPane.Remove(title);
            }
    
            /// <summary>
            /// This will return the instance of the content pane that holds the view.
            /// </summary>
            /// <param name="title">The title given to the Pane during instantiation.</param>
            /// <returns>The views Parent Instance.</returns>
            public static LayoutAnchorable GetInstance(string title)
            {
                return _contentPane[title];
            }
        }
    }
    

    In the adapter I modified this code as follows:

    private static void AddAnchorableDocument(DockingManager regionTarget, NotifyCollectionChangedEventArgs e)
            {
                foreach (FrameworkElement element in e.NewItems)
                {
                    var view = element as UIElement;
                    var documentPane = regionTarget.Layout.Descendents().OfType<LayoutDocumentPane>().FirstOrDefault();
    
                    if ((view == null) || (documentPane == null))
                    {
                        continue;
                    }
                    var newContentPane = new LayoutAnchorable
                                             {
                                                 Content = view,
                                                 Title = element.ToolTip.ToString(),
                                                 CanHide = true,
                                                 CanClose = false
                                             };
    
                    DockinWindowChildObjectDictionary.Add(element.ToolTip.ToString(),** newContentPane);
                    documentPane.Children.Add(newContentPane);
                }
            }
    

    Then I added the following to the ViewModel to gain the effect I was going after:

    public void OnNavigatedTo(NavigationContext navigationContext)
            {
                var viewParentInstance = DockinWindowChildObjectDictionary.GetInstance("Belt Plan");
                viewParentInstance.IsSelected = true;
            }
    

    One hurdle done and on to the next. For a base to all the information in this post the ViewSwitchingNavigation.sln included with the PRISM 5.0 download will get you started. If you are wondering about the ConfigureDefaultRegionBehaviors() referenced in the adapter registration I got that from the StockTraderRI_Desktop.sln in the sample downloads.

    I hope this post helps someone else that finds themselves in the same pickle this technology sandwich provides.

    Sincerely James