Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Building a Docking Window Management Solution in WPF

, 1 Jan 2011
A docking window solution using WPF as part of Synergy toolkit
Synergy.zip
MixModes.Synergy.Resources
bin
Debug
MixModes.Synergy.Resources.dll
MixModes.Synergy.Resources.pdb
MixModes.Synergy.Resources.csproj.user
MixModes.Synergy.Resources.csproj.vspscc
MixModes.Synergy.Resources.vsdoc
obj
Debug
DesignTimeResolveAssemblyReferencesInput.cache
GenerateResource-ResGen.read.1.tlog
GenerateResource-ResGen.write.1.tlog
GenerateResource.read.1.tlog
MixModes.Synergy.Resources.dll
MixModes.Synergy.Resources.g.resources
MixModes.Synergy.Resources.pdb
MixModes.Synergy.Resources.Resources.Language.resources
TempPE
Properties
Resources
Close.png
DockBottom.png
DockCenter.png
DockHexagon.png
DockLeft.png
DockRight.png
DockTop.png
Error.png
Maximize.png
Minimize.png
NewProject.png
Restore.png
SplitBottom.png
SplitLeft.PNG
SplitRight.png
SplitTop.png
MixModes.Synergy.Themes
bin
Debug
MixModes.Synergy.Resources.dll
MixModes.Synergy.Resources.pdb
MixModes.Synergy.Themes.dll
MixModes.Synergy.Themes.pdb
MixModes.Synergy.Themes.csproj.vspscc
MixModes.Synergy.Themes.vsdoc
obj
Debug
Aero.NormalColor.baml
Brushes.baml
Buttons.baml
DesignTimeResolveAssemblyReferencesInput.cache
DockPane.baml
GenerateResource-ResGen.read.1.tlog
GenerateResource-ResGen.write.1.tlog
Images.baml
MixModes.Synergy.Themes.dll
MixModes.Synergy.Themes.g.resources
MixModes.Synergy.Themes.pdb
MixModes.Synergy.Themes.Properties.Resources.resources
MixModes.Synergy.Themes_MarkupCompile.cache
ResizableAdorner.baml
ResolveAssemblyReference.cache
TabItems.baml
TempPE
Text.baml
Themes.baml
ToolTips.baml
WindowParts.baml
Windows.baml
Properties
Settings.settings
MixModes.Synergy.Utilities
bin
Debug
MixModes.Synergy.Utilities.dll
MixModes.Synergy.Utilities.pdb
MixModes.Synergy.Utilities.csproj.vspscc
MixModes.Synergy.Utilities.vsdoc
obj
Debug
DesignTimeResolveAssemblyReferencesInput.cache
MixModes.Synergy.Utilities.dll
MixModes.Synergy.Utilities.pdb
TempPE
Properties
MixModes.Synergy.VisualFramework
Adorners
Behaviors
bin
Debug
de
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
en
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
es
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
fr
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
it
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
ja
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
ko
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
Microsoft.Expression.Interactions.dll
MixModes.Synergy.Resources.dll
MixModes.Synergy.Resources.pdb
MixModes.Synergy.Themes.dll
MixModes.Synergy.Themes.pdb
MixModes.Synergy.Utilities.dll
MixModes.Synergy.Utilities.pdb
MixModes.Synergy.VisualFramework.dll
MixModes.Synergy.VisualFramework.pdb
System.Windows.Interactivity.dll
WPFToolkit.dll
WPFToolkit.pdb
zh-Hans
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
zh-Hant
Microsoft.Expression.Interactions.resources.dll
System.Windows.Interactivity.resources.dll
Commands
Controls
Converters
Extensions
Framework
MixModes.Synergy.VisualFramework.csproj.user
MixModes.Synergy.VisualFramework.csproj.vspscc
MixModes.Synergy.VisualFramework.vsdoc
obj
Debug
Controls
NotificationToolTipContent.baml
DesignTimeResolveAssemblyReferencesInput.cache
MixModes.Synergy.VisualFramework.dll
MixModes.Synergy.VisualFramework.g.resources
MixModes.Synergy.VisualFramework.pdb
MixModes.Synergy.VisualFramework_MarkupCompile.cache
MixModes.Synergy.VisualFramework_MarkupCompile.lref
ResolveAssemblyReference.cache
TempPE
Themes
Generic.baml
Windows
WindowsManager.baml
Properties
Themes
ViewModels
Views
Windows
Synegy
bin
Debug
de
System.Windows.Interactivity.resources.dll
en
System.Windows.Interactivity.resources.dll
es
System.Windows.Interactivity.resources.dll
fr
System.Windows.Interactivity.resources.dll
it
System.Windows.Interactivity.resources.dll
ja
System.Windows.Interactivity.resources.dll
ko
System.Windows.Interactivity.resources.dll
MixModes.Synergy.Resources.dll
MixModes.Synergy.Resources.pdb
MixModes.Synergy.Themes.dll
MixModes.Synergy.Themes.pdb
MixModes.Synergy.Utilities.dll
MixModes.Synergy.Utilities.pdb
MixModes.Synergy.VisualFramework.dll
MixModes.Synergy.VisualFramework.pdb
Synegy.exe
Synegy.pdb
Synegy.vshost.exe
Synegy.vshost.exe.manifest
System.Windows.Interactivity.dll
zh-Hans
System.Windows.Interactivity.resources.dll
zh-Hant
System.Windows.Interactivity.resources.dll
obj
x86
Debug
App.baml
DesignTimeResolveAssemblyReferencesInput.cache
GenerateResource-ResGen.read.1.tlog
GenerateResource-ResGen.write.1.tlog
MainWindow.baml
ResolveAssemblyReference.cache
Resources
MainWindowMenus.baml
Synegy.exe
Synegy.g.resources
Synegy.pdb
Synegy.Properties.Resources.resources
Synegy_MarkupCompile.cache
TempPE
Views
NewProjectWindow.baml
Properties
Settings.settings
Resources
SplashScreen.jpg
Synegy.csproj.user
Synegy.csproj.vspscc
Synegy.vsdoc
Views
///
/// Copyright(C) MixModes Inc. 2010
/// 

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using MixModes.Synergy.Utilities;
using MixModes.Synergy.VisualFramework.Framework;

namespace MixModes.Synergy.VisualFramework.Windows
{
    /// <summary>
    /// Interaction logic for WindowsManager.xaml
    /// </summary>
    [StyleTypedProperty(Property = "DockIllustrationContentStyleProperty", StyleTargetType = typeof(TabItem))]
    [StyleTypedProperty(Property = "DockPaneIllustrationStyle", StyleTargetType = typeof(Panel))]       
    public partial class WindowsManager
    {        
        /// <summary>
        /// Initializes a new instance of the <see cref="WindowsManager"/> class.
        /// </summary>
        public WindowsManager()
        {
            InitializeComponent();
            _dockPaneStateMonitorList = new ObservableDependencyPropertyCollection<DockPane>(DockPane.DockPaneStateProperty);
            _dockPaneStateMonitorList.DependencyPropertyChanged += OnDockPaneStateChanged;
        }
        
        /// <summary>
        /// Adds the dock pane.
        /// </summary>
        /// <param name="pane">The pane.</param>
        /// <param name="dock">The dock.</param>
        public void AddPinnedWindow(DockPane pane, Dock dock)
        {
            AddPinnedWindowInner(pane, dock);
            
            DetachDockPaneEvents(pane);            
            AttachDockPaneEvents(pane);
        }

        /// <summary>
        /// Adds the window in auto hide fashion
        /// </summary>
        /// <param name="pane">The pane.</param>
        /// <param name="dock">The dock.</param>
        public void AddAutoHideWindow(DockPane pane, Dock dock)
        {
            CondenceDockPanel(pane, dock);

            DetachDockPaneEvents(pane);
            AttachDockPaneEvents(pane);
        }

        /// <summary>
        /// Adds the floating window.
        /// </summary>
        /// <param name="pane">The pane.</param>
        public void AddFloatingWindow(DockPane pane)
        {
            DetachDockPaneEvents(pane);
            AttachDockPaneEvents(pane);

            if (Mouse.LeftButton == MouseButtonState.Pressed)
            {
                // Setting state to non-floating so that OnPaneDragStared can 
                // set it to floating and execute related effects
                pane.DockPaneState = DockPaneState.Content;
                OnPaneDragStarted(pane, null);
            }
            else
            {                
                pane.DockPaneState = DockPaneState.Floating;
                FloatingPanel.Children.Add(pane);
                MonitorStateChangeForDockPane(pane);
            }
        }

        /// <summary>
        /// Removes the dock pane from windows manager alltogether and unsubscribes from all events
        /// </summary>
        /// <param name="pane">The pane.</param>
        public void RemoveDockPane(DockPane pane)
        {
            DetachDockPaneEvents(pane);
            RemoveCondencedDockPanel(DraggedPane.CondencedDockPanel);
            RemovePinnedWindow(DraggedPane);
            FloatingPanel.Children.Remove(pane);
        }

        /// <summary>
        /// Clears the windows manager
        /// </summary>
        public void Clear()
        {
            Action<Panel> clearAction = panel => panel.Children.Clear();
            clearAction(TopPinnedWindows);
            clearAction(TopWindowHeaders);
            clearAction(BottomPinnedWindows);
            clearAction(BottomWindowHeaders);
            clearAction(LeftPinnedWindows);
            clearAction(LeftWindowHeaders);
            clearAction(RightPinnedWindows);
            clearAction(RightWindowHeaders);
            clearAction(FloatingPanel);
            clearAction(PopupArea);
            DocumentContainer.Content = null;
            DocumentContainer.Clear();
            _dockPaneStateMonitorList.Clear();
            _popupTimer.Stop();
        }

        /// <summary>
        /// Starts the dock pane state change detection
        /// </summary>
        public void StartDockPaneStateChangeDetection()
        {
            MonitorStateChangeForDockPane(DraggedPane);
        }        

        /// <summary>
        /// Stops the dock pane state change detection
        /// </summary>
        public void StopDockPaneStateChangeDetection()
        {
            IgnoreStateChangeForDockPane(DraggedPane);
        }
        
        /// <summary>
        /// Style property for illustrating how a docked pane would look like in tab control
        /// </summary>
        public static readonly DependencyProperty DockPaneIllustrationStyleProperty = DependencyProperty.Register("DockPaneIllustrationStyle",
                                                                                                                  typeof(Style),
                                                                                                                  typeof(WindowsManager));

        /// <summary>
        /// Gets the dock pane illustration style
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static Style GetDockPaneIllustrationStyle(DependencyObject obj)
        {
            return (Style)obj.GetValue(DockPaneIllustrationStyleProperty);
        }

        /// <summary>
        /// Sets the dock pane illustration style
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetDockPaneIllustrationStyle(DependencyObject obj, Style value)
        {
            obj.SetValue(DockPaneIllustrationStyleProperty, value);
        }

        /// <summary>
        /// Style property for illustrating how a docked content would look like in tab control
        /// </summary>
        public static readonly DependencyProperty DockIllustrationContentStyleProperty = DependencyProperty.Register("DockIllustrationContentStyle",
                                                                                                                     typeof(Style),
                                                                                                                     typeof(WindowsManager));

        /// <summary>
        /// Gets the dock content illustration style
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static Style GetDockIllustrationContentStyle(DependencyObject obj)
        {
            return (Style)obj.GetValue(DockIllustrationContentStyleProperty);
        }

        /// <summary>
        /// Sets the dock content illustration style.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">The value.</param>
        public static void SetDockIllustrationContentStyle(DependencyObject obj, Style value)
        {
            obj.SetValue(DockIllustrationContentStyleProperty, value);
        }

        /// <summary>
        /// Active windows manager
        /// </summary>
        public static WindowsManager ActiveWindowsManager
        {
            get;
            private set;
        }
        
        /// <summary>
        /// Dragged pane
        /// </summary>
        /// <remarks>Needs to be static since there can be only one dragged pane at any time
        /// and also since DraggedPanes can change ownership from one WindowsManager instance to another</remarks>
        public DockPane DraggedPane
        {
            get; 
            private set;
        }

        /// <summary>
        /// Monitors the state change for dock pane and ensures that
        /// only one instance of pane is monitored to prevent redundent events
        /// </summary>
        /// <param name="pane">Pane to monitor</param>
        private void MonitorStateChangeForDockPane(DockPane pane)
        {
            if ((pane != null) && (!_dockPaneStateMonitorList.Contains(pane)))
            {
                _dockPaneStateMonitorList.Add(pane);
            }
        }

        /// <summary>
        /// Ignores the state change for dock pane.
        /// </summary>
        /// <param name="pane">Dock pane to ignore state changes from</param>
        private void IgnoreStateChangeForDockPane(DockPane pane)
        {
            if (pane != null)
            {
                _dockPaneStateMonitorList.Remove(pane);
            }
        }

        /// <summary>
        /// Adds the pinned window without manipulating events
        /// </summary>
        /// <param name="pane">The pane.</param>
        /// <param name="dock">The dock.</param>
        private void AddPinnedWindowInner(DockPane pane, Dock dock)
        {
            switch (dock)
            {
                case Dock.Bottom:
                    AddPinnedWindowToBottom(pane);
                    break;
                case Dock.Left:
                    AddPinnedWindowToLeft(pane);
                    break;
                case Dock.Right:
                    AddPinnedWindowToRight(pane);
                    break;
                case Dock.Top:
                    AddPinnedWindowToTop(pane);
                    break;
                default:
                    break;
            }

            pane.DockPaneState = DockPaneState.Docked;
        }

        /// <summary>
        /// Adds the pinned window to left of content
        /// </summary>
        /// <param name="pane">Pane to add</param>
        private void AddPinnedWindowToLeft(DockPane pane)
        {
            DockPanel.SetDock(pane, Dock.Left);
            pane.Height = double.NaN;
            LeftPinnedWindows.Children.Add(pane);

            GridSplitter sizingThumb = new GridSplitter();
            sizingThumb.Width = 4;
            sizingThumb.Background = Brushes.Transparent;
            sizingThumb.Cursor = Cursors.SizeWE;
            DockPanel.SetDock(sizingThumb, Dock.Left);
            LeftPinnedWindows.Children.Add(sizingThumb);

            sizingThumb.DragDelta += (a, b) =>
            {
                if (pane.Width.Equals(double.NaN))
                {
                    pane.Width = pane.DesiredSize.Width;
                }

                if (pane.Width + b.HorizontalChange <= 0)
                {
                    return;
                }

                pane.Width += b.HorizontalChange;                
            };
        }

        /// <summary>
        /// Adds the pinned window to right of content
        /// </summary>
        /// <param name="pane">Pane to add</param>
        private void AddPinnedWindowToRight(DockPane pane)
        {
            DockPanel.SetDock(pane, Dock.Right);
            pane.Height = double.NaN;
            RightPinnedWindows.Children.Add(pane);

            GridSplitter sizingThumb = new GridSplitter();
            sizingThumb.Width = 4;
            sizingThumb.Background = Brushes.Transparent;
            sizingThumb.Cursor = Cursors.SizeWE;
            DockPanel.SetDock(sizingThumb, Dock.Right);
            RightPinnedWindows.Children.Add(sizingThumb);
            
            sizingThumb.DragDelta += (a, b) =>
            {
                if (pane.Width.Equals(double.NaN))
                {
                    pane.Width = pane.DesiredSize.Width;
                }

                if (pane.Width - b.HorizontalChange <= 0)
                {
                    return;
                }

                pane.Width -= b.HorizontalChange;
            };
        }

        /// <summary>
        /// Adds the pinned window to top of content
        /// </summary>
        /// <param name="pane">Pane to add</param>
        private void AddPinnedWindowToTop(DockPane pane)
        {
            DockPanel.SetDock(pane, Dock.Top);
            pane.Width = double.NaN;
            TopPinnedWindows.Children.Add(pane);

            GridSplitter sizingThumb = new GridSplitter();
            sizingThumb.Height = 4;
            sizingThumb.HorizontalAlignment = HorizontalAlignment.Stretch;
            sizingThumb.Background = Brushes.Transparent;
            sizingThumb.Cursor = Cursors.SizeNS;
            DockPanel.SetDock(sizingThumb, Dock.Top);
            TopPinnedWindows.Children.Add(sizingThumb);
            
            sizingThumb.DragDelta += (a, b) =>
            {
                if (pane.Height.Equals(double.NaN))
                {
                    pane.Height = pane.DesiredSize.Height;
                }

                if (pane.Height + b.VerticalChange <= 0)
                {
                    return;
                }

                pane.Height += b.VerticalChange;
            };
        }

        /// <summary>
        /// Adds the pinned window to bottom of content
        /// </summary>
        /// <param name="pane">Pane to add</param>
        private void AddPinnedWindowToBottom(DockPane pane)
        {
            DockPanel.SetDock(pane, Dock.Bottom);
            pane.Width = double.NaN;
            BottomPinnedWindows.Children.Add(pane);

            GridSplitter sizingThumb = new GridSplitter();
            sizingThumb.Height = 4;
            sizingThumb.HorizontalAlignment = HorizontalAlignment.Stretch;
            sizingThumb.Background = Brushes.Transparent;
            sizingThumb.Cursor = Cursors.SizeNS;
            DockPanel.SetDock(sizingThumb, Dock.Bottom);
            BottomPinnedWindows.Children.Add(sizingThumb);

            sizingThumb.DragDelta += (a, b) =>
            {
                if (pane.Height.Equals(double.NaN))
                {
                    pane.Height = pane.DesiredSize.Height;
                }

                if (pane.Height - b.VerticalChange <= 0)
                {
                    return;
                }

                pane.Height -= b.VerticalChange;
            };
        }

        /// <summary>
        /// Called when dock pane's state is changed
        /// </summary>
        /// <param name="sender">Event source</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private void OnDockPaneStateChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            DockPane pane = sender as DockPane;
            Validate.Assert<ArgumentException>(pane != null);

            IgnoreStateChangeForDockPane(pane);

            DockPaneState state = (DockPaneState)e.NewValue;

            if (state == DockPaneState.AutoHide)
            {
                DockPanel logicalParentDockPanel = LogicalTreeHelper.GetParent(pane) as DockPanel;
                RemovePinnedWindow(pane);
                CondenceDockPanel(pane, DockPanel.GetDock(logicalParentDockPanel));                
            }
            else if (state == DockPaneState.Docked)
            {
                PopupArea.Children.Remove(pane);
                RemoveCondencedDockPanel(pane.CondencedDockPanel);                

                AddPinnedWindowInner(pane, DockPanel.GetDock(pane));
            }

            MonitorStateChangeForDockPane(pane);
        }

        /// <summary>
        /// Condences the dock panel
        /// </summary>
        /// <param name="pane">The pane.</param>
        /// <param name="dock">The dock.</param>
        private void CondenceDockPanel(DockPane pane, Dock dock)
        {
            FrameworkElement condencedDockPanel = pane.CondencedDockPanel;
            condencedDockPanel.LayoutTransform = (dock == Dock.Left) || (dock == Dock.Right) ? new RotateTransform(90) : null;

            switch (dock)
            {
                case Dock.Bottom:
                    BottomWindowHeaders.Children.Add(condencedDockPanel);
                    break;
                case Dock.Left:
                    LeftWindowHeaders.Children.Add(condencedDockPanel);
                    break;
                case Dock.Right:
                    RightWindowHeaders.Children.Add(condencedDockPanel);
                    break;
                case Dock.Top:
                    TopWindowHeaders.Children.Add(condencedDockPanel);
                    break;
                default:
                    break;
            }

            DockPanel.SetDock(pane, dock);
            DetachEvents(condencedDockPanel);
            pane.DockPaneState = DockPaneState.AutoHide;
            AttachEvents(condencedDockPanel);            
        }

        /// <summary>
        /// Removes the condenced dock panel
        /// </summary>
        /// <param name="condencedDockPanel">The condenced dock panel</param>
        private void RemoveCondencedDockPanel(FrameworkElement condencedDockPanel)
        {
            BottomWindowHeaders.Children.Remove(condencedDockPanel);
            LeftWindowHeaders.Children.Remove(condencedDockPanel);
            RightWindowHeaders.Children.Remove(condencedDockPanel);
            TopWindowHeaders.Children.Remove(condencedDockPanel);
            DetachEvents(condencedDockPanel);
        }

        /// <summary>
        /// Attaches the events.
        /// </summary>
        /// <param name="condencedDockPanel">The condenced dock panel.</param>
        private void AttachEvents(FrameworkElement condencedDockPanel)
        {
            condencedDockPanel.MouseEnter += OnCondencedDockPanelMouseEnter;
            condencedDockPanel.MouseLeave += OnPopupAreaMouseLeave;
        }

        /// <summary>
        /// Detaches the events.
        /// </summary>
        /// <param name="condencedDockPanel">The condenced dock panel.</param>
        private void DetachEvents(FrameworkElement condencedDockPanel)
        {
            condencedDockPanel.MouseEnter -= OnCondencedDockPanelMouseEnter;
            condencedDockPanel.MouseLeave -= OnPopupAreaMouseLeave;
        }
        
        /// <summary>
        /// Called when mouse enters condenced dock panel
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="args">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void OnCondencedDockPanelMouseEnter(object source, RoutedEventArgs args)
        {
            FrameworkElement element = source as FrameworkElement;
            Validate.Assert<ArgumentException>(element != null);

            DockPane pane = element.DataContext as DockPane;
            Validate.Assert<ArgumentException>(pane != null);

            RemovePopupEvents();
            PopupArea.Children.Clear();
            Dock dock = DockPanel.GetDock(pane);

            PopupArea.Children.Add(pane);

            bool isLeftOrRightDock = (dock == Dock.Left) || (dock == Dock.Right);

            GridSplitter sizingThumb = new GridSplitter();

            if (isLeftOrRightDock)
            {
                sizingThumb.Width = 4;
                sizingThumb.VerticalAlignment = VerticalAlignment.Stretch;
            }
            else
            {
                sizingThumb.Height = 4;
                sizingThumb.HorizontalAlignment = HorizontalAlignment.Stretch;
            }

            sizingThumb.Background = Brushes.Transparent;
            sizingThumb.Cursor = isLeftOrRightDock ? Cursors.SizeWE : Cursors.SizeNS;
            DockPanel.SetDock(sizingThumb, dock);
            PopupArea.Children.Add(sizingThumb);

            sizingThumb.DragDelta += (c, d) =>
            {
                if (isLeftOrRightDock && (pane.Width.Equals(double.NaN)))
                {
                    pane.Width = pane.DesiredSize.Width;
                }
                else if ((!(isLeftOrRightDock))&&(pane.Height.Equals(double.NaN)))
                {
                    pane.Height = pane.DesiredSize.Height;
                }

                double result = 0;
                switch (dock)
                {
                    case Dock.Bottom:
                        result = pane.Height - d.VerticalChange;
                        break;
                    case Dock.Left:
                        result = pane.Width + d.HorizontalChange;
                        break;
                    case Dock.Right:
                        result = pane.Width - d.HorizontalChange;
                        break;
                    case Dock.Top:
                        result = pane.Height + d.VerticalChange;
                        break;
                }

                if (result <= 0)
                {
                    return;
                }

                if (isLeftOrRightDock)
                {
                    pane.Width = result;
                }
                else
                {
                    pane.Height = result;
                }
            };

            pane.MouseLeave += OnPopupAreaMouseLeave;
            sizingThumb.MouseLeave += OnPopupAreaMouseLeave;
            pane.MouseEnter += OnPopupAreaMouseEnter;
            sizingThumb.MouseEnter += OnPopupAreaMouseEnter;

            _mouseOverPopupPane = true;

            EnablePopupTimer();
        }

        /// <summary>
        /// Enables the popup timer
        /// </summary>
        private void EnablePopupTimer()
        {
            _popupTimer.Stop();
            _popupTimer.Interval = TimeSpan.FromMilliseconds(200);
            _popupTimer.Tick += OnPopupTimerElapsed;
            _popupTimer.Start();
        }

        /// <summary>
        /// Removes the popup events
        /// </summary>
        private void RemovePopupEvents()
        {
            if (PopupArea.Children.Count == 0)
            {
                return;
            }

            DockPane pane = PopupArea.Children[0] as DockPane;

            if (pane != null)
            {
                pane.MouseEnter -= OnPopupAreaMouseEnter;
                pane.MouseLeave -= OnPopupAreaMouseLeave;
            }
        }

        /// <summary>
        /// Called when mouse enters in current popup area (condenced dock panel or dock panel)
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        private void OnPopupAreaMouseEnter(object sender, MouseEventArgs e)
        {
            _mouseOverPopupPane = true;
        }

        /// <summary>
        /// Called when mouse leaves current popup area (condenced dock panel or dock panel)
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        private void OnPopupAreaMouseLeave(object sender, MouseEventArgs e)
        {
            _mouseOverPopupPane = false;
        }

        /// <summary>
        /// Called when popup timer has elapsed
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnPopupTimerElapsed(object sender, EventArgs e)
        {
            _popupTimer.Tick -= OnPopupTimerElapsed;
            _popupTimer.Stop();

            if (!_mouseOverPopupPane)
            {
                PopupArea.Children.Clear();
            }
            else
            {
                EnablePopupTimer();
            }
        }

        /// <summary>
        /// Called when dock pane is closed
        /// </summary>
        /// <param name="sender">Event source</param>
        /// <param name="args">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void OnDockPaneClosed(object sender, RoutedEventArgs args)
        {
            DockPane pane = args.OriginalSource as DockPane;
            Validate.Assert<InvalidOperationException>(pane != null);
            
            DetachDockPaneEvents(pane);

            // Pane is pinned, parent must be a dock panel)
            if (pane.DockPaneState == DockPaneState.Docked)
            {
                RemovePinnedWindow(pane);
            }
            else if (pane.DockPaneState == DockPaneState.AutoHide)
            {
                PopupArea.Children.Remove(pane);
                RemoveCondencedDockPanel(pane.CondencedDockPanel);                
            }
            else if (pane.DockPaneState == DockPaneState.Floating)
            {
                FloatingPanel.Children.Remove(pane);
            }
        }

        /// <summary>
        /// Removes the pinned window
        /// </summary>
        /// <param name="pane">Dock pane</param>
        private void RemovePinnedWindow(DockPane pane)
        {
            DockPanel logicalParentDockPanel = LogicalTreeHelper.GetParent(pane) as DockPanel;

            if (logicalParentDockPanel == null)
            {
                return;
            }

            int indexOfPane = logicalParentDockPanel.Children.IndexOf(pane);

            GridSplitter splitter = logicalParentDockPanel.Children[indexOfPane + 1] as GridSplitter;
            logicalParentDockPanel.Children.Remove(pane);
            logicalParentDockPanel.Children.Remove(splitter);
        }
        
        /// <summary>
        /// Called when dock pane drag has started
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        private void OnPaneDragStarted(object sender, MouseButtonEventArgs e)
        {
            ActiveWindowsManager = this;
            DraggedPane = sender as DockPane;
            _dockPaneDragging = true;
            _dragStartPointOffset = (e != null) ? e.GetPosition(DraggedPane) : Mouse.GetPosition(this);
            
            // Show the visibility
            DockingPanel.Visibility = Visibility.Visible;
            
            RemoveCondencedDockPanel(DraggedPane.CondencedDockPanel);
            RemovePinnedWindow(DraggedPane);

            // Dim the dragged window
            DraggedPane.Opacity = 0.25;

            if (DraggedPane.DockPaneState != DockPaneState.Floating)
            {
                IgnoreStateChangeForDockPane(DraggedPane);

                Point panePosition = e != null ? e.GetPosition(this) : Mouse.GetPosition(this);

                Canvas.SetTop(DraggedPane, panePosition.Y);
                Canvas.SetLeft(DraggedPane, panePosition.X);
                FloatingPanel.Children.Add(DraggedPane);
                MonitorStateChangeForDockPane(DraggedPane);

                // Set this last since until the DraggedPane is not added
                // to the FloatingPanel it will not be in the visual tree
                // and hence things like adorner layer will not be present
                DraggedPane.DockPaneState = DockPaneState.Floating;
            }

            DraggedPane.IsHitTestVisible = false;

            // This is necessary or when tab items are floated again
            // mouse up event never fires
            Mouse.Capture(this, CaptureMode.SubTree);
        }
        
        /// <summary>
        /// Invoked when an unhandled <see cref="E:System.Windows.Input.Mouse.MouseMove"/> 
        /// attached event reaches an element in its route that is derived from this class. 
        /// Implement this method to add class handling for this event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.Input.MouseEventArgs"/> that contains the event data.</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (!_dockPaneDragging)
            {
                return;
            }            

            Point currentMousePosition = e.GetPosition(this);
            double currentPaneXPos = Canvas.GetLeft(DraggedPane);
            double currentPaneYPos = Canvas.GetTop(DraggedPane);

            Canvas.SetTop(DraggedPane, currentMousePosition.Y - _dragStartPointOffset.Y);
            Canvas.SetLeft(DraggedPane, currentMousePosition.X - _dragStartPointOffset.X);            
        }

        /// <summary>
        /// Invoked when an unhandled <see cref="E:System.Windows.Input.Mouse.MouseUp"/> routed event 
        /// reaches an element in its route that is derived from this class. Implement this method to add class 
        /// handling for this event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs"/> that contains the event data. 
        /// The event data reports that the mouse button was released.</param>
        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            base.OnMouseUp(e);

            if (!_dockPaneDragging)
            {
                return;
            }

            ReleaseMouseCapture();
            
            DraggedPane.Opacity = 1;
            _dockPaneDragging = false;
            DockingPanel.Visibility = Visibility.Collapsed;

            DraggedPane.IsHitTestVisible = true;

            DraggedPane = null;
            ActiveWindowsManager = null;
        }               
        
        /// <summary>
        /// Attaches the events to dock pane
        /// </summary>
        /// <param name="pane">Dock pane</param>
        private void AttachDockPaneEvents(DockPane pane)
        {
            pane.Close += OnDockPaneClosed;

            MonitorStateChangeForDockPane(pane);

            pane.HeaderDrag += OnPaneDragStarted;
        }        

        /// <summary>
        /// Detaches the events from the dock pane
        /// </summary>
        /// <param name="pane">The pane.</param>
        private void DetachDockPaneEvents(DockPane pane)
        {
            pane.Close -= OnDockPaneClosed;

            IgnoreStateChangeForDockPane(pane);

            pane.HeaderDrag -= OnPaneDragStarted;
        }        

        // Private members
        private Point _dragStartPointOffset;        
        private bool _dockPaneDragging;
        private bool _mouseOverPopupPane = false;
        private DispatcherTimer _popupTimer = new DispatcherTimer();
        private ObservableDependencyPropertyCollection<DockPane> _dockPaneStateMonitorList;
    }
}

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 Code Project Open License (CPOL)

About the Author

Ashish Kaila
Software Developer (Senior) MixModes Inc. | Research In Motion
Canada Canada
Ashish worked for Microsoft for a number of years in Microsoft Visual Studio (Architect edition) and Windows Live division as a developer. Before that he was a developer consultant mainly involved in distributed service development / architecture. His main interests are distributed software architecture, patterns and practices and mobile device development.
 
Currently Ashish serves as a Technical Lead at RIM leading next generation BlackBerry media experience and also runs his own company MixModes Inc. specializing in .NET / WPF / Silverlight technologies. You can visit MixModes at http://mixmodes.com or follow it on Twitter @MixModes
 
In his free time he is an avid painter, hockey player and enjoys travelling. His blog is at: http://ashishkaila.serveblog.net
Follow on   Twitter

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 1 Jan 2011
Article Copyright 2010 by Ashish Kaila
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid