Click here to Skip to main content
15,896,512 members
Articles / Desktop Programming / WPF

Building an Extensible Application with MEF, WPF, and MVVM

Rate me:
Please Sign up or sign in to vote.
4.88/5 (45 votes)
15 Nov 2009LGPL316 min read 303.4K   7.4K   185  
An article for anyone interested in how to build an extensible application using WPF and the Model-View-ViewModel pattern.
//Copyright (c) 2007-2009, Adolfo Marinucci
//All rights reserved.

//Redistribution and use in source and binary forms, with or without modification, 
//are permitted provided that the following conditions are met:
//
//* Redistributions of source code must retain the above copyright notice, 
//  this list of conditions and the following disclaimer.
//* Redistributions in binary form must reproduce the above copyright notice, 
//  this list of conditions and the following disclaimer in the documentation 
//  and/or other materials provided with the distribution.
//* Neither the name of Adolfo Marinucci nor the names of its contributors may 
//  be used to endorse or promote products derived from this software without 
//  specific prior written permission.
//
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
//AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
//IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
//HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
//OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
//EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Windows.Markup;
using System.Diagnostics;
using System.Windows.Threading;
using System.Windows.Media.Animation;
using System.Windows.Interop;

namespace AvalonDock
{
    public class DockableFloatingWindow : FloatingWindow
    {
        static DockableFloatingWindow()
        {
            //This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
            //This style is defined in themes\generic.xaml
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DockableFloatingWindow), new FrameworkPropertyMetadata(typeof(DockableFloatingWindow)));
        }


        public DockableFloatingWindow(DockingManager manager)
            : base(manager)
        {
        }


        Pane _previousPane = null;

        int _arrayIndexPreviousPane = -1;


        public DockableFloatingWindow(DockingManager manager, DockableContent content)
            : this(manager)
        {

            //create a new temporary pane
            FloatingDockablePane pane = new FloatingDockablePane(this);

            //setup window size
            //Width = content.ContainerPane.ActualWidth;
            //Height = content.ContainerPane.ActualHeight;

            if (content.FloatingWindowSize.IsEmpty)
                content.FloatingWindowSize = new Size(content.ContainerPane.ActualWidth, content.ContainerPane.ActualHeight);
            
            Width = content.FloatingWindowSize.Width;
            Height = content.FloatingWindowSize.Height;

            //save current content position in container pane
            _previousPane = content.ContainerPane;
            _arrayIndexPreviousPane = _previousPane.Items.IndexOf(content);
            
            
            //pane.SetValue(ResizingPanel.ResizeWidthProperty, _previousPane.GetValue(ResizingPanel.ResizeWidthProperty));
            //pane.SetValue(ResizingPanel.ResizeHeightProperty, _previousPane.GetValue(ResizingPanel.ResizeHeightProperty));

            //remove content from container pane
            content.ContainerPane.RemoveContent(_arrayIndexPreviousPane);
            
            //add content to my temporary pane
            pane.Items.Add(content);

            //let templates access this pane
            HostedPane = pane;

            //Change state on contents
            IsDockableWindow = true;

            DocumentPane originalDocumentPane = _previousPane as DocumentPane;
            if (originalDocumentPane != null)
                originalDocumentPane.CheckContentsEmpty();
        }

        public DockableFloatingWindow(DockingManager manager, DockablePane dockablePane)
            : this(manager)
        {
            //create a new temporary pane
            FloatingDockablePane pane = new FloatingDockablePane(this);

            //setup window size
            ManagedContent selectedContent = dockablePane.SelectedItem as ManagedContent;

            if (selectedContent != null && selectedContent.FloatingWindowSize.IsEmpty)
                selectedContent.FloatingWindowSize = new Size(dockablePane.ActualWidth, dockablePane.ActualHeight);

            if (selectedContent != null)
            {
                Width = selectedContent.FloatingWindowSize.Width;
                Height = selectedContent.FloatingWindowSize.Height;
                this.ResizeMode = selectedContent.FloatingResizeMode;
            }
            else
            {
                Width = dockablePane.ActualWidth;
                Height = dockablePane.ActualHeight;
            }


            //Width = dockablePane.ActualWidth;
            //Height = dockablePane.ActualHeight;

            ////save current content position in container pane
            //pane.SetValue(ResizingPanel.ResizeWidthProperty, dockablePane.GetValue(ResizingPanel.ResizeWidthProperty));
            //pane.SetValue(ResizingPanel.ResizeHeightProperty, dockablePane.GetValue(ResizingPanel.ResizeHeightProperty));

            int selectedIndex = dockablePane.SelectedIndex;

            //remove contents from container pane and insert in hosted pane
            while (dockablePane.Items.Count > 0)
            {
                ManagedContent content = dockablePane.RemoveContent(0);

                //add content to my temporary pane
                pane.Items.Add(content);
            }

            //let templates access this pane
            HostedPane = pane;
            HostedPane.SelectedIndex = selectedIndex;

            //Change state on contents
            IsDockableWindow = true;
        }


        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);

            if (HostedPane == null)
                HostedPane = Content as FloatingDockablePane;

            if (HostedPane != null)
            {
                Content = HostedPane;
            }
        }

        

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            DockableContent[] cntsToClose = new DockableContent[HostedPane.Items.Count];
            HostedPane.Items.CopyTo(cntsToClose, 0);

            foreach (DockableContent cntToClose in cntsToClose)
            {
                HostedPane.CloseOrHide(HostedPane.Items[0] as DockableContent, ForcedClosing);
            }

            Manager.UnregisterFloatingWindow(this);
        }

        public override Pane ClonePane()
        {
            DockablePane paneToAnchor = new DockablePane();

            //transfer the resizing panel sizes
            //paneToAnchor.SetValue(ResizingPanel.ResizeWidthProperty,
            //    HostedPane.GetValue(ResizingPanel.ResizeWidthProperty));
            //paneToAnchor.SetValue(ResizingPanel.ResizeHeightProperty,
            //    HostedPane.GetValue(ResizingPanel.ResizeHeightProperty));
            ResizingPanel.SetEffectiveSize(paneToAnchor, new Size(Width, Height));


            int selectedIndex = HostedPane.SelectedIndex;

            //transfer contents from hosted pane in the floating window and
            //the new created dockable pane
            while (HostedPane.Items.Count > 0)
            {
                paneToAnchor.Items.Add(
                    HostedPane.RemoveContent(0));
            }

            paneToAnchor.SelectedIndex = selectedIndex;

            return paneToAnchor;
        }

        protected override void OnExecuteCommand(object sender, ExecutedRoutedEventArgs e)
        {
            if (e.Command == TabbedDocumentCommand)
            {
                DockableContent currentContent = HostedPane.SelectedItem as DockableContent;

                Manager.MainDocumentPane.Items.Insert(0, HostedPane.RemoveContent(HostedPane.SelectedIndex));
                Manager.MainDocumentPane.SelectedIndex = 0;

                currentContent.SetStateToDocument();

                if (HostedPane.Items.Count == 0)
                    this.Close();
                e.Handled = true;
            }
            else if (e.Command == CloseCommand)
            {
                //DockableContent currentContent = HostedPane.SelectedItem as DockableContent;
                //Manager.Hide(currentContent);
                HostedPane.CloseOrHide();
                if (HostedPane.Items.Count == 0)
                    this.Close();
                e.Handled = true;
            }

            base.OnExecuteCommand(sender, e);
        }

        protected override void Redock()
        {
            if (_previousPane != null)
            {
                if (_previousPane.GetManager() == null)
                {
                    DockablePane newContainerPane = new DockablePane();
                    newContainerPane.Items.Add(HostedPane.RemoveContent(0));
                    newContainerPane.SetValue(ResizingPanel.ResizeWidthProperty, _previousPane.GetValue(ResizingPanel.ResizeWidthProperty));
                    newContainerPane.SetValue(ResizingPanel.ResizeHeightProperty, _previousPane.GetValue(ResizingPanel.ResizeHeightProperty));
                    Manager.Anchor(newContainerPane, ((DockablePane)_previousPane).Anchor);
                }
                else
                {
                    if (_arrayIndexPreviousPane > _previousPane.Items.Count)
                        _arrayIndexPreviousPane = _previousPane.Items.Count;

                    DockableContent currentContent = HostedPane.Items[0] as DockableContent;
                    _previousPane.Items.Insert(_arrayIndexPreviousPane, HostedPane.RemoveContent(0));
                    _previousPane.SelectedIndex = _arrayIndexPreviousPane;
                    currentContent.SetStateToDock();

                }
                this.Close();
            } 
            
            base.Redock();
        }

        
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Engineer
Canada Canada
By day I'm a Professional Engineer, doing .NET, VB6, SQL Server, and Automation (Ladder Logic, etc.) programming.

On weekends I write and maintain an open source extensible application framework called SoapBox Core.

In the evenings I provide front line technical support for moms4mom.com and I help out with administrative tasks (like formatting stuff). I also pitch in as a moderator from time to time.

You can follow me on twitter.

Comments and Discussions