Click here to Skip to main content
15,892,298 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 303K   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 DocumentFloatingWindow : FloatingWindow  
    {
        static DocumentFloatingWindow()
        {
            //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(DocumentFloatingWindow), new FrameworkPropertyMetadata(typeof(DocumentFloatingWindow)));

            Window.AllowsTransparencyProperty.OverrideMetadata(typeof(DocumentFloatingWindow), new FrameworkPropertyMetadata(true));
            Window.WindowStyleProperty.OverrideMetadata(typeof(DocumentFloatingWindow), new FrameworkPropertyMetadata(WindowStyle.None));
            Window.ShowInTaskbarProperty.OverrideMetadata(typeof(DocumentFloatingWindow), new FrameworkPropertyMetadata(false));
        }


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

        Pane _previousPane = null;

        int _arrayIndexPreviousPane = -1;

        public DocumentFloatingWindow(DockingManager manager, DocumentContent content)
            : this(manager)
        {
            //create a new temporary pane
            FloatingDockablePane pane = new FloatingDockablePane(this);

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

            //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;

            if (IsDocumentFloatingAllowed)
            {
                AllowsTransparency = false;
                WindowStyle = WindowStyle.ToolWindow;
                NotifyPropertyChanged("IsDocumentFloatingAllowed");
            }
        }

        public bool IsDocumentFloatingAllowed
        {
            get
            { 
                if (HostedPane != null && 
                    HostedPane.Items.Count > 0)
                    return ((DocumentContent)HostedPane.Items[0]).IsFloatingAllowed;

                return false;
            }
        }


        internal override void OnEndDrag()
        {
            if (HostedPane.Items.Count > 0)
            {
                DocumentContent content = HostedPane.Items[0] as DocumentContent;
                if (!content.IsFloatingAllowed)
                {
                    HostedPane.Items.RemoveAt(0);
                    _previousPane.Items.Insert(_arrayIndexPreviousPane, content);
                    _previousPane.SelectedItem = content;
                    Close();
                }
                else
                {
                    DocumentPane originalDocumentPane = _previousPane as DocumentPane;
                    originalDocumentPane.CheckContentsEmpty();
                }
            }
            else
            {
                DocumentPane originalDocumentPane = _previousPane as DocumentPane;
                originalDocumentPane.CheckContentsEmpty();
                Close();
            }


            base.OnEndDrag();
        }


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

            ////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));

            //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));
            }

            return paneToAnchor;
        }


        protected override void OnInitialized(EventArgs e)
        {

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

            base.OnInitialized(e);
        }

        internal override void OnShowSelectionBox()
        {
            this.Visibility = Visibility.Hidden;
            base.OnShowSelectionBox();
        }

        internal override void OnHideSelectionBox()
        {
            this.Visibility = Visibility.Visible;
            base.OnHideSelectionBox();
        }
        #region Commands

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

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

                if (HostedPane.Items.Count == 0)
                    this.Close();
                e.Handled = true;
            }
            else if (e.Command == CloseCommand)
            {
                DocumentContent docContent = this.HostedPane.Items[0] as DocumentContent;
                e.Handled = docContent.Close();
            }

            base.OnExecuteCommand(sender, e);
        }

        
        #endregion
        //#region Drag
        //protected override IntPtr FilterMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        //{
        //    handled = false;

        //    if (!IsDocumentFloatingAllowed)
        //        return IntPtr.Zero;

        //    switch (msg)
        //    {
        //        case WM_SIZE:
        //        case WM_MOVE:
        //            //HostedPane.ReferencedPane.SaveFloatingWindowSizeAndPosition(this);
        //            break;
        //        case WM_NCLBUTTONDOWN:
        //            if (IsDockableWindow && wParam.ToInt32() == HTCAPTION)
        //            {
        //                short x = (short)((lParam.ToInt32() & 0xFFFF));
        //                short y = (short)((lParam.ToInt32() >> 16));

        //                Point clickPoint = this.TransformToDeviceDPI(new Point(x, y));
        //                Manager.Drag(this, clickPoint, new Point(clickPoint.X - Left, clickPoint.Y - Top));

        //                handled = true;
        //            }
        //            break;
        //        case WM_NCLBUTTONDBLCLK:
        //            if (IsDockableWindow && wParam.ToInt32() == HTCAPTION)
        //            {
        //                if (IsDockableWindow)
        //                {
        //                    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();
        //                    }

        //                    handled = true;
        //                }
        //            }
        //            break;
        //        case WM_NCRBUTTONDOWN:
        //            if (wParam.ToInt32() == HTCAPTION)
        //            {
        //                short x = (short)((lParam.ToInt32() & 0xFFFF));
        //                short y = (short)((lParam.ToInt32() >> 16));

        //                ContextMenu cxMenu = FindResource(new ComponentResourceKey(typeof(DockingManager), ContextMenuElement.FloatingWindow)) as ContextMenu;
        //                if (cxMenu != null)
        //                {
        //                    foreach (MenuItem menuItem in cxMenu.Items)
        //                        menuItem.CommandTarget = this;

        //                    cxMenu.Placement = PlacementMode.AbsolutePoint;
        //                    cxMenu.PlacementRectangle = new Rect(new Point(x, y), new Size(0, 0));
        //                    cxMenu.PlacementTarget = this;
        //                    cxMenu.IsOpen = true;
        //                }

        //                handled = true;
        //            }
        //            break;
        //        case WM_NCRBUTTONUP:
        //            if (wParam.ToInt32() == HTCAPTION)
        //            {

        //                handled = true;
        //            }
        //            break;

        //    }


        //    return IntPtr.Zero;
        //}
        //#endregion

        protected override void Redock()
        {
            if (_previousPane != null)
            {
                if (_previousPane.GetManager() == null)
                {
                     Manager.MainDocumentPane.Items.Insert(0, HostedPane.RemoveContent(0));
                }
                else
                {
                    if (_arrayIndexPreviousPane > _previousPane.Items.Count)
                        _arrayIndexPreviousPane = _previousPane.Items.Count;

                    _previousPane.Items.Insert(_arrayIndexPreviousPane, HostedPane.RemoveContent(0));
                    _previousPane.SelectedIndex = _arrayIndexPreviousPane;
                }
                this.Close();
            }

            base.Redock();
        }


        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);

            if (e.Cancel)
                return;
            
            if (this.HostedPane.Items.Count > 0)
            {
                DocumentContent docContent = this.HostedPane.Items[0] as DocumentContent;
                if (!docContent.Close())
                    e.Cancel = true;
                else
                    this.HostedPane.Items.Remove(docContent);
            }
            
        }
    }
}

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