Click here to Skip to main content
15,891,976 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 302.9K   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;
using System.Runtime.InteropServices;

namespace AvalonDock
{
    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Assert, Unrestricted = true)]
    public abstract class FloatingWindow : Window, INotifyPropertyChanged
    {
        static FloatingWindow()
        {
            Window.ShowInTaskbarProperty.OverrideMetadata(typeof(FloatingWindow), new FrameworkPropertyMetadata(false));
            Window.WindowStyleProperty.OverrideMetadata(typeof(FloatingWindow), new FrameworkPropertyMetadata(WindowStyle.ToolWindow));
        }


        public FloatingWindow()
        {
            this.Loaded += new RoutedEventHandler(OnLoaded);
            this.Unloaded += new RoutedEventHandler(OnUnloaded);

            this.SizeChanged += new SizeChangedEventHandler(FloatingWindow_SizeChanged);

            this.CommandBindings.Add(new CommandBinding(SetAsDockableWindowCommand, OnExecuteCommand, OnCanExecuteCommand));
            this.CommandBindings.Add(new CommandBinding(SetAsFloatingWindowCommand, OnExecuteCommand, OnCanExecuteCommand));
            this.CommandBindings.Add(new CommandBinding(TabbedDocumentCommand, OnExecuteCommand, OnCanExecuteCommand));
            this.CommandBindings.Add(new CommandBinding(CloseCommand, OnExecuteCommand, OnCanExecuteCommand));
        }

        void FloatingWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (HostedPane != null)
            {
                foreach (ManagedContent c in HostedPane.Items)
                    c.FloatingWindowSize = new Size(Width, Height);

                ResizingPanel.SetEffectiveSize(HostedPane, new Size(Width, Height));
            }
        }

        public FloatingWindow(DockingManager manager)
            : this()
        {
            //save manager ref
            _manager = manager;
        }

        DockingManager _manager = null;

        internal DockingManager Manager
        {
            get { return _manager; }
        }


        public FloatingDockablePane HostedPane
        {
            get { return (FloatingDockablePane)GetValue(ReferencedPaneProperty); }
            set { SetValue(ReferencedPaneProperty, value); }
        }

        // Using a DependencyProperty as the backing store for HostedPane.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ReferencedPaneProperty =
            DependencyProperty.Register("HostedPane", typeof(FloatingDockablePane), typeof(FlyoutPaneWindow));


        internal virtual void OnEndDrag()
        {
        }

        internal virtual void OnShowSelectionBox()
        {

        }

        internal virtual void OnHideSelectionBox()
        {

        }

        #region Active Content Management
        ManagedContent lastActiveContent = null;

        protected override void OnActivated(EventArgs e)
        {
            if (Manager != null)
            {
                lastActiveContent = Manager.ActiveContent;
                Manager.ActiveContent = HostedPane.SelectedItem as ManagedContent;
            }

            base.OnActivated(e);
        }

        protected override void OnDeactivated(EventArgs e)
        {
            if (Manager != null && lastActiveContent != null)
            {
                Manager.ActiveContent = lastActiveContent;
            }
            base.OnDeactivated(e);
        } 
        #endregion

        #region IsClosing Flag Management
        public void Close(bool force)
        {
            ForcedClosing = force;
            base.Close();
        }

        protected bool ForcedClosing { get; private set; }


        internal bool IsClosing { get; private set; }

        protected override void OnClosing(CancelEventArgs e)
        {
            if (HostedPane.Items.Count > 0 && !ForcedClosing)
            {
                ManagedContent cntToClose = HostedPane.Items[0] as ManagedContent;
                if (!cntToClose.IsCloseable)
                {
                    e.Cancel = true;
                    base.OnClosing(e);
                    return;
                }
            }

            IsClosing = true;
            base.OnClosing(e);
        }

        protected override void OnClosed(EventArgs e)
        {
            IsClosing = false;
            base.OnClosed(e);
        }
        #endregion

        public abstract Pane ClonePane();


        #region Enable/Disable window Close Button
        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr GetSystemMenu(
            IntPtr hWnd,
            Int32 bRevert
        );

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern int GetMenuItemCount(
            IntPtr hMenu
        );

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern int DrawMenuBar(
            IntPtr hWnd
        );

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern bool EnableMenuItem(
            IntPtr hMenu,
            Int32 uIDEnableItem,
            Int32 uEnable
        );

        private const Int32 MF_BYPOSITION = 0x400;
        private const Int32 MF_ENABLED = 0x0000;
        private const Int32 MF_GRAYED = 0x0001;
        private const Int32 MF_DISABLED = 0x0002;

        void EnableXButton()
        {
            WindowInteropHelper helper = new WindowInteropHelper(this);
            IntPtr hMenu = GetSystemMenu(helper.Handle, 0);

            int menuItemCount = GetMenuItemCount(hMenu);

            EnableMenuItem(hMenu, menuItemCount - 1, MF_BYPOSITION | MF_ENABLED);
            DrawMenuBar(helper.Handle);
        }

        void DisableXButton()
        {
            WindowInteropHelper helper = new WindowInteropHelper(this);
            IntPtr hMenu = GetSystemMenu(helper.Handle, 0);

            int menuItemCount = GetMenuItemCount(hMenu);

            EnableMenuItem(hMenu, menuItemCount - 1, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);
            DrawMenuBar(helper.Handle);
        }

        #endregion

        #region Non-Client area management

        protected const int WM_MOVE = 0x0003;
        protected const int WM_SIZE = 0x0005;
        protected const int WM_NCMOUSEMOVE = 0xa0;
        protected const int WM_NCLBUTTONDOWN = 0xA1;
        protected const int WM_NCLBUTTONUP = 0xA2;
        protected const int WM_NCLBUTTONDBLCLK = 0xA3;
        protected const int WM_NCRBUTTONDOWN = 0xA4;
        protected const int WM_NCRBUTTONUP = 0xA5;
        protected const int HTCAPTION = 2;
        protected const int SC_MOVE = 0xF010;
        protected const int WM_SYSCOMMAND = 0x0112;



        #region Load/Unload window events
        HwndSource _hwndSource;
        HwndSourceHook _wndProcHandler;

        protected void OnLoaded(object sender, EventArgs e)
        {
            WindowInteropHelper helper = new WindowInteropHelper(this);
            _hwndSource = HwndSource.FromHwnd(helper.Handle);
            _wndProcHandler = new HwndSourceHook(FilterMessage);
            _hwndSource.AddHook(_wndProcHandler);

            if (HostedPane.Items.Count > 0)
            {
                ManagedContent cntHosted = HostedPane.Items[0] as ManagedContent;
                if (!cntHosted.IsCloseable)
                {
                    DisableXButton();
                }
            }
        }
        protected void OnUnloaded(object sender, EventArgs e)
        {
            if (_hwndSource != null)
                _hwndSource.RemoveHook(_wndProcHandler);
        }
        #endregion


        protected virtual IntPtr FilterMessage(
            IntPtr hwnd,
            int msg,
            IntPtr wParam,
            IntPtr lParam,
            ref bool handled
        )
        {
            handled = false;

            if (Manager == null)
                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)
                        {
                            Redock();
                            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

        #region Floating/dockable window state
        bool _dockableWindow = true;

        public bool IsDockableWindow
        {
            get { return _dockableWindow; }
            set
            {
                _dockableWindow = value;

                if (_dockableWindow)
                {
                    foreach (ManagedContent content in HostedPane.Items)
                        if (content is DockableContent)
                            ((DockableContent)content).SetStateToDockableWindow();
                }
                else
                {
                    foreach (ManagedContent content in HostedPane.Items)
                        if (content is DockableContent)
                            ((DockableContent)content).SetStateToFloatingWindow();
                }
            }
        }

        public bool IsFloatingWindow
        {
            get { return !IsDockableWindow; }
            set { IsDockableWindow = !value; }
        }

        protected virtual void Redock()
        { 
            
        }
        #endregion

        #region Commands
        private static object syncRoot = new object();

        private static RoutedUICommand tabbedDocumentCommand = null;
        public static RoutedUICommand TabbedDocumentCommand
        {
            get
            {
                lock (syncRoot)
                {
                    if (null == tabbedDocumentCommand)
                    {
                        tabbedDocumentCommand = new RoutedUICommand("T_abbed Document", "TabbedDocument", typeof(FloatingWindow));
                    }

                }
                return tabbedDocumentCommand;
            }
        }

        private static RoutedUICommand dockableCommand = null;
        public static RoutedUICommand SetAsDockableWindowCommand
        {
            get
            {
                lock (syncRoot)
                {
                    if (null == dockableCommand)
                    {
                        dockableCommand = new RoutedUICommand("D_ockable", "Dockable", typeof(FloatingWindow));
                    }

                }
                return dockableCommand;
            }
        }

        private static RoutedUICommand floatingCommand = null;
        public static RoutedUICommand SetAsFloatingWindowCommand
        {
            get
            {
                lock (syncRoot)
                {
                    if (null == floatingCommand)
                    {
                        floatingCommand = new RoutedUICommand("F_loating", "Floating", typeof(FloatingWindow));
                    }

                }
                return floatingCommand;
            }
        }

        private static RoutedUICommand closeCommand = null;
        public static RoutedUICommand CloseCommand
        {
            get
            {
                lock (syncRoot)
                {
                    if (null == closeCommand)
                    {
                        closeCommand = new RoutedUICommand("Close", "Close", typeof(FloatingWindow));
                    }

                }
                return closeCommand;
            }
        }

        protected virtual void OnExecuteCommand(object sender, ExecutedRoutedEventArgs e)
        {
            if (e.Command == SetAsDockableWindowCommand)
            {
                IsDockableWindow = true;
                e.Handled = true;
            }
            else if (e.Command == SetAsFloatingWindowCommand)
            {
                IsFloatingWindow = true;
                e.Handled = true;
            }
        }

        protected virtual void OnCanExecuteCommand(object sender, CanExecuteRoutedEventArgs e)
        {
            if (e.Command == SetAsDockableWindowCommand)
                e.CanExecute = IsFloatingWindow;
            else if (e.Command == SetAsFloatingWindowCommand)
                e.CanExecute = IsDockableWindow;
            else
                e.CanExecute = true;
        }
        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

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