From c50e28bdb767fee5ccd365cbfe291de4ecad880b Mon Sep 17 00:00:00 2001 From: Marcus Strini Date: Wed, 6 Jul 2022 11:23:27 +0200 Subject: [PATCH 1/2] Fixed unwanted doubleclick event handling - if window is not focued (e.g. if you doubleclick on another maximized window whild easytab-window is in the background) - if multiple clicks happen on different points within the doubleclick-intervall and within the control-area (e.g. fast witching beween tabs) --- TitleBarTabsOverlay.cs | 2080 ++++++++++++++++++++-------------------- 1 file changed, 1040 insertions(+), 1040 deletions(-) diff --git a/TitleBarTabsOverlay.cs b/TitleBarTabsOverlay.cs index e069fdfd..7a1e54cf 100644 --- a/TitleBarTabsOverlay.cs +++ b/TitleBarTabsOverlay.cs @@ -17,248 +17,248 @@ namespace EasyTabs { - /// - /// Borderless overlay window that is moved with and rendered on top of the non-client area of a instance that's responsible - /// for rendering the actual tab content and responding to click events for those tabs. - /// - public class TitleBarTabsOverlay : Form - { - protected Timer showTooltipTimer; + /// + /// Borderless overlay window that is moved with and rendered on top of the non-client area of a instance that's responsible + /// for rendering the actual tab content and responding to click events for those tabs. + /// + public class TitleBarTabsOverlay : Form + { + protected Timer showTooltipTimer; - /// All of the parent forms and their overlays so that we don't create duplicate overlays across the application domain. - protected static Dictionary _parents = new Dictionary(); + /// All of the parent forms and their overlays so that we don't create duplicate overlays across the application domain. + protected static Dictionary _parents = new Dictionary(); - /// Tab that has been torn off from this window and is being dragged. - protected static TitleBarTab _tornTab; + /// Tab that has been torn off from this window and is being dragged. + protected static TitleBarTab _tornTab; - /// Thumbnail representation of used when dragging. - protected static TornTabForm _tornTabForm; + /// Thumbnail representation of used when dragging. + protected static TornTabForm _tornTabForm; - /// - /// Flag used in and to track whether the user was click/dragging when a particular event - /// occurred. - /// - protected static bool _wasDragging = false; + /// + /// Flag used in and to track whether the user was click/dragging when a particular event + /// occurred. + /// + protected static bool _wasDragging = false; - /// Flag indicating whether or not has been installed as a hook. - protected static bool _hookProcInstalled; + /// Flag indicating whether or not has been installed as a hook. + protected static bool _hookProcInstalled; - /// Semaphore to control access to . - protected static object _tornTabLock = new object(); + /// Semaphore to control access to . + protected static object _tornTabLock = new object(); protected static uint _doubleClickInterval = User32.GetDoubleClickTime(); /// Flag indicating whether or not the underlying window is active. protected bool _active = false; - /// Flag indicating whether we should draw the titlebar background (i.e. we are in a non-Aero environment). - protected bool _aeroEnabled = false; + /// Flag indicating whether we should draw the titlebar background (i.e. we are in a non-Aero environment). + protected bool _aeroEnabled = false; - /// - /// When a tab is torn from the window, this is where we store the areas on all open windows where tabs can be dropped to combine the tab with that - /// window. - /// - protected Tuple[] _dropAreas = null; + /// + /// When a tab is torn from the window, this is where we store the areas on all open windows where tabs can be dropped to combine the tab with that + /// window. + /// + protected Tuple[] _dropAreas = null; - /// Pointer to the low-level mouse hook callback (). - protected IntPtr _hookId; + /// Pointer to the low-level mouse hook callback (). + protected IntPtr _hookId; - /// Delegate of ; declared as a member variable to keep it from being garbage collected. - protected HOOKPROC _hookproc = null; + /// Delegate of ; declared as a member variable to keep it from being garbage collected. + protected HOOKPROC _hookproc = null; - /// Index of the tab, if any, whose close button is being hovered over. - protected int _isOverCloseButtonForTab = -1; + /// Index of the tab, if any, whose close button is being hovered over. + protected int _isOverCloseButtonForTab = -1; protected bool _isOverSizingBox = false; protected bool _isOverAddButton = true; - /// Queue of mouse events reported by that need to be processed. - protected BlockingCollection _mouseEvents = new BlockingCollection(); + /// Queue of mouse events reported by that need to be processed. + protected BlockingCollection _mouseEvents = new BlockingCollection(); - /// Consumer thread for processing events in . - protected Thread _mouseEventsThread = null; + /// Consumer thread for processing events in . + protected Thread _mouseEventsThread = null; - /// Parent form for the overlay. - protected TitleBarTabs _parentForm; + /// Parent form for the overlay. + protected TitleBarTabs _parentForm; protected long _lastLeftButtonClickTicks = 0; - protected bool _firstClick = true; - protected Point[] _lastTwoClickCoordinates = new Point[2]; - - protected bool _parentFormClosing = false; - - /// Blank default constructor to ensure that the overlays are only initialized through . - protected TitleBarTabsOverlay() - { - } - - /// Creates the overlay window and attaches it to . - /// Parent form that the overlay should be rendered on top of. - protected TitleBarTabsOverlay(TitleBarTabs parentForm) - { - _parentForm = parentForm; - - // We don't want this window visible in the taskbar - ShowInTaskbar = false; - FormBorderStyle = FormBorderStyle.SizableToolWindow; - MinimizeBox = false; - MaximizeBox = false; - _aeroEnabled = _parentForm.IsCompositionEnabled; - - Show(_parentForm); - AttachHandlers(); - - showTooltipTimer = new Timer - { - AutoReset = false - }; - - showTooltipTimer.Elapsed += ShowTooltipTimer_Elapsed; - } - - /// - /// Makes sure that the window is created with an flag set so that it can be alpha-blended properly with the content ( - /// ) underneath the overlay. - /// - protected override CreateParams CreateParams - { - get - { - CreateParams createParams = base.CreateParams; - createParams.ExStyle |= (int) (WS_EX.WS_EX_LAYERED | WS_EX.WS_EX_NOACTIVATE); - - return createParams; - } - } - - /// Primary color for the titlebar background. - protected Color TitleBarColor - { - get - { - if (Application.RenderWithVisualStyles && Environment.OSVersion.Version.Major >= 6) - { - return _active - ? SystemColors.GradientActiveCaption - : SystemColors.GradientInactiveCaption; - } - - return _active - ? SystemColors.ActiveCaption - : SystemColors.InactiveCaption; - } - } - - /// Type of theme being used by the OS to render the desktop. - protected DisplayType DisplayType - { - get - { - if (_aeroEnabled) - { - return DisplayType.Aero; - } - - if (Application.RenderWithVisualStyles && Environment.OSVersion.Version.Major >= 6) - { - return DisplayType.Basic; - } - - return DisplayType.Classic; - } - } - - /// Gradient color for the titlebar background. - protected Color TitleBarGradientColor - { - get - { - return _active - ? SystemInformation.IsTitleBarGradientEnabled - ? SystemColors.GradientActiveCaption - : SystemColors.ActiveCaption - : SystemInformation.IsTitleBarGradientEnabled - ? SystemColors.GradientInactiveCaption - : SystemColors.InactiveCaption; - } - } - - /// Screen area in which tabs can be dragged to and dropped for this window. - public Rectangle TabDropArea - { - get - { - RECT windowRectangle; - User32.GetWindowRect(_parentForm.Handle, out windowRectangle); - - return new Rectangle( - windowRectangle.left + SystemInformation.HorizontalResizeBorderThickness, windowRectangle.top + SystemInformation.VerticalResizeBorderThickness, - ClientRectangle.Width, _parentForm.NonClientAreaHeight - SystemInformation.VerticalResizeBorderThickness); - } - } - - /// Retrieves or creates the overlay for . - /// Parent form that we are to create the overlay for. - /// Newly-created or previously existing overlay for . - public static TitleBarTabsOverlay GetInstance(TitleBarTabs parentForm) - { - if (!_parents.ContainsKey(parentForm)) - { - _parents.Add(parentForm, new TitleBarTabsOverlay(parentForm)); - } - - return _parents[parentForm]; - } - - /// - /// Attaches the various event handlers to so that the overlay is moved in synchronization to - /// . - /// - protected void AttachHandlers() - { + protected bool _firstClick = true; + protected Point _lastLeftButtonClickPoint = Point.Empty; + + protected bool _parentFormClosing = false; + + /// Blank default constructor to ensure that the overlays are only initialized through . + protected TitleBarTabsOverlay() + { + } + + /// Creates the overlay window and attaches it to . + /// Parent form that the overlay should be rendered on top of. + protected TitleBarTabsOverlay(TitleBarTabs parentForm) + { + _parentForm = parentForm; + + // We don't want this window visible in the taskbar + ShowInTaskbar = false; + FormBorderStyle = FormBorderStyle.SizableToolWindow; + MinimizeBox = false; + MaximizeBox = false; + _aeroEnabled = _parentForm.IsCompositionEnabled; + + Show(_parentForm); + AttachHandlers(); + + showTooltipTimer = new Timer + { + AutoReset = false + }; + + showTooltipTimer.Elapsed += ShowTooltipTimer_Elapsed; + } + + /// + /// Makes sure that the window is created with an flag set so that it can be alpha-blended properly with the content ( + /// ) underneath the overlay. + /// + protected override CreateParams CreateParams + { + get + { + CreateParams createParams = base.CreateParams; + createParams.ExStyle |= (int) (WS_EX.WS_EX_LAYERED | WS_EX.WS_EX_NOACTIVATE); + + return createParams; + } + } + + /// Primary color for the titlebar background. + protected Color TitleBarColor + { + get + { + if (Application.RenderWithVisualStyles && Environment.OSVersion.Version.Major >= 6) + { + return _active + ? SystemColors.GradientActiveCaption + : SystemColors.GradientInactiveCaption; + } + + return _active + ? SystemColors.ActiveCaption + : SystemColors.InactiveCaption; + } + } + + /// Type of theme being used by the OS to render the desktop. + protected DisplayType DisplayType + { + get + { + if (_aeroEnabled) + { + return DisplayType.Aero; + } + + if (Application.RenderWithVisualStyles && Environment.OSVersion.Version.Major >= 6) + { + return DisplayType.Basic; + } + + return DisplayType.Classic; + } + } + + /// Gradient color for the titlebar background. + protected Color TitleBarGradientColor + { + get + { + return _active + ? SystemInformation.IsTitleBarGradientEnabled + ? SystemColors.GradientActiveCaption + : SystemColors.ActiveCaption + : SystemInformation.IsTitleBarGradientEnabled + ? SystemColors.GradientInactiveCaption + : SystemColors.InactiveCaption; + } + } + + /// Screen area in which tabs can be dragged to and dropped for this window. + public Rectangle TabDropArea + { + get + { + RECT windowRectangle; + User32.GetWindowRect(_parentForm.Handle, out windowRectangle); + + return new Rectangle( + windowRectangle.left + SystemInformation.HorizontalResizeBorderThickness, windowRectangle.top + SystemInformation.VerticalResizeBorderThickness, + ClientRectangle.Width, _parentForm.NonClientAreaHeight - SystemInformation.VerticalResizeBorderThickness); + } + } + + /// Retrieves or creates the overlay for . + /// Parent form that we are to create the overlay for. + /// Newly-created or previously existing overlay for . + public static TitleBarTabsOverlay GetInstance(TitleBarTabs parentForm) + { + if (!_parents.ContainsKey(parentForm)) + { + _parents.Add(parentForm, new TitleBarTabsOverlay(parentForm)); + } + + return _parents[parentForm]; + } + + /// + /// Attaches the various event handlers to so that the overlay is moved in synchronization to + /// . + /// + protected void AttachHandlers() + { FormClosing += TitleBarTabsOverlay_FormClosing; - _parentForm.FormClosing += _parentForm_FormClosing; - _parentForm.Disposed += _parentForm_Disposed; - _parentForm.Deactivate += _parentForm_Deactivate; - _parentForm.Activated += _parentForm_Activated; - _parentForm.SizeChanged += _parentForm_Refresh; - _parentForm.Shown += _parentForm_Refresh; - _parentForm.VisibleChanged += _parentForm_Refresh; - _parentForm.Move += _parentForm_Refresh; - _parentForm.SystemColorsChanged += _parentForm_SystemColorsChanged; - - if (_hookproc == null) - { - // Spin up a consumer thread to process mouse events from _mouseEvents - _mouseEventsThread = new Thread(InterpretMouseEvents) - { - Name = "Low level mouse hooks processing thread" - }; - _mouseEventsThread.Priority = ThreadPriority.Highest; - _mouseEventsThread.Start(); - - using (Process curProcess = Process.GetCurrentProcess()) - { - using (ProcessModule curModule = curProcess.MainModule) - { - // Install the low level mouse hook that will put events into _mouseEvents - _hookproc = MouseHookCallback; - _hookId = User32.SetWindowsHookEx(WH.WH_MOUSE_LL, _hookproc, Kernel32.GetModuleHandle(curModule.ModuleName), 0); - } - } - } - } + _parentForm.FormClosing += _parentForm_FormClosing; + _parentForm.Disposed += _parentForm_Disposed; + _parentForm.Deactivate += _parentForm_Deactivate; + _parentForm.Activated += _parentForm_Activated; + _parentForm.SizeChanged += _parentForm_Refresh; + _parentForm.Shown += _parentForm_Refresh; + _parentForm.VisibleChanged += _parentForm_Refresh; + _parentForm.Move += _parentForm_Refresh; + _parentForm.SystemColorsChanged += _parentForm_SystemColorsChanged; + + if (_hookproc == null) + { + // Spin up a consumer thread to process mouse events from _mouseEvents + _mouseEventsThread = new Thread(InterpretMouseEvents) + { + Name = "Low level mouse hooks processing thread" + }; + _mouseEventsThread.Priority = ThreadPriority.Highest; + _mouseEventsThread.Start(); + + using (Process curProcess = Process.GetCurrentProcess()) + { + using (ProcessModule curModule = curProcess.MainModule) + { + // Install the low level mouse hook that will put events into _mouseEvents + _hookproc = MouseHookCallback; + _hookId = User32.SetWindowsHookEx(WH.WH_MOUSE_LL, _hookproc, Kernel32.GetModuleHandle(curModule.ModuleName), 0); + } + } + } + } private void TitleBarTabsOverlay_FormClosing(object sender, FormClosingEventArgs e) { - if (!_parentFormClosing) + if (!_parentFormClosing) { - e.Cancel = true; - _parentFormClosing = true; - _parentForm.Close(); + e.Cancel = true; + _parentFormClosing = true; + _parentForm.Close(); } } @@ -269,201 +269,201 @@ private void TitleBarTabsOverlay_FormClosing(object sender, FormClosingEventArgs /// Object from which this event originated, in this case. /// Arguments associated with this event. private void _parentForm_FormClosing(object sender, CancelEventArgs e) - { - if (e.Cancel) - { - _parentFormClosing = false; - return; - } + { + if (e.Cancel) + { + _parentFormClosing = false; + return; + } TitleBarTabs form = (TitleBarTabs) sender; - if (form == null) - { - return; - } - - _parentFormClosing = true; - - if (_parents.ContainsKey(form)) - { - _parents.Remove(form); - } - - // Uninstall the mouse hook - User32.UnhookWindowsHookEx(_hookId); - - // Kill the mouse events processing thread - _mouseEvents.CompleteAdding(); - _mouseEventsThread.Abort(); - } - - private void HideTooltip() - { - showTooltipTimer.Stop(); - - if (_parentForm.InvokeRequired) - { - _parentForm.Invoke(new Action(() => - { - _parentForm.Tooltip.Hide(_parentForm); - })); - } - - else - { - _parentForm.Tooltip.Hide(_parentForm); - } - } - - private void ShowTooltip(TitleBarTabs tabsForm, string caption) - { - Point tooltipLocation = new Point(Cursor.Position.X + 7, Cursor.Position.Y + 55); - tabsForm.Tooltip.Show(caption, tabsForm, tabsForm.PointToClient(tooltipLocation), tabsForm.Tooltip.AutoPopDelay); - } - - private void ShowTooltipTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) - { - if (!_parentForm.ShowTooltips) - { - return; - } - - Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); - TitleBarTab hoverTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); - - if (hoverTab != null) - { - TitleBarTabs hoverTabForm = hoverTab.Parent; - - if (hoverTabForm.InvokeRequired) - { - hoverTabForm.Invoke(new Action(() => - { - ShowTooltip(hoverTabForm, hoverTab.Caption); - })); - } - - else - { - ShowTooltip(hoverTabForm, hoverTab.Caption); - } - } - } - - private void StartTooltipTimer() - { - if (!_parentForm.ShowTooltips) - { - return; - } - - Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); - TitleBarTab hoverTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); - - if (hoverTab != null) - { - showTooltipTimer.Interval = hoverTab.Parent.Tooltip.AutomaticDelay; - showTooltipTimer.Start(); - } - } - - /// Consumer method that processes mouse events in that are recorded by . - protected void InterpretMouseEvents() - { - foreach (MouseEvent mouseEvent in _mouseEvents.GetConsumingEnumerable()) - { - int nCode = mouseEvent.nCode; - IntPtr wParam = mouseEvent.wParam; - MSLLHOOKSTRUCT? hookStruct = mouseEvent.MouseData; - - if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) - { - HideTooltip(); - - // ReSharper disable PossibleInvalidOperationException - Point cursorPosition = new Point(hookStruct.Value.pt.x, hookStruct.Value.pt.y); - // ReSharper restore PossibleInvalidOperationException - bool reRender = false; - - if (_tornTab != null && _dropAreas != null) - { - // ReSharper disable ForCanBeConvertedToForeach - for (int i = 0; i < _dropAreas.Length; i++) - // ReSharper restore ForCanBeConvertedToForeach - { - // If the cursor is within the drop area, combine the tab for the window that belongs to that drop area - if (_dropAreas[i].Item2.Contains(cursorPosition)) - { - TitleBarTab tabToCombine = null; - - lock (_tornTabLock) - { - if (_tornTab != null) - { - tabToCombine = _tornTab; - _tornTab = null; - } - } - - if (tabToCombine != null) - { - int i1 = i; - - // In all cases where we need to affect the UI, we call Invoke so that those changes are made on the main UI thread since - // we are on a separate processing thread in this case - Invoke( - new Action( - () => - { - _dropAreas[i1].Item1.TabRenderer.CombineTab(tabToCombine, cursorPosition); - - tabToCombine = null; - _tornTabForm.Close(); - _tornTabForm = null; - - if (_parentForm.Tabs.Count == 0) - { - _parentForm.Close(); - } - })); - } - } - } - } - - else if (!_parentForm.TabRenderer.IsTabRepositioning) - { - StartTooltipTimer(); + if (form == null) + { + return; + } + + _parentFormClosing = true; + + if (_parents.ContainsKey(form)) + { + _parents.Remove(form); + } + + // Uninstall the mouse hook + User32.UnhookWindowsHookEx(_hookId); + + // Kill the mouse events processing thread + _mouseEvents.CompleteAdding(); + _mouseEventsThread.Abort(); + } + + private void HideTooltip() + { + showTooltipTimer.Stop(); + + if (_parentForm.InvokeRequired) + { + _parentForm.Invoke(new Action(() => + { + _parentForm.Tooltip.Hide(_parentForm); + })); + } + + else + { + _parentForm.Tooltip.Hide(_parentForm); + } + } + + private void ShowTooltip(TitleBarTabs tabsForm, string caption) + { + Point tooltipLocation = new Point(Cursor.Position.X + 7, Cursor.Position.Y + 55); + tabsForm.Tooltip.Show(caption, tabsForm, tabsForm.PointToClient(tooltipLocation), tabsForm.Tooltip.AutoPopDelay); + } + + private void ShowTooltipTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + if (!_parentForm.ShowTooltips) + { + return; + } + + Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); + TitleBarTab hoverTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); + + if (hoverTab != null) + { + TitleBarTabs hoverTabForm = hoverTab.Parent; + + if (hoverTabForm.InvokeRequired) + { + hoverTabForm.Invoke(new Action(() => + { + ShowTooltip(hoverTabForm, hoverTab.Caption); + })); + } + + else + { + ShowTooltip(hoverTabForm, hoverTab.Caption); + } + } + } + + private void StartTooltipTimer() + { + if (!_parentForm.ShowTooltips) + { + return; + } + + Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); + TitleBarTab hoverTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); + + if (hoverTab != null) + { + showTooltipTimer.Interval = hoverTab.Parent.Tooltip.AutomaticDelay; + showTooltipTimer.Start(); + } + } + + /// Consumer method that processes mouse events in that are recorded by . + protected void InterpretMouseEvents() + { + foreach (MouseEvent mouseEvent in _mouseEvents.GetConsumingEnumerable()) + { + int nCode = mouseEvent.nCode; + IntPtr wParam = mouseEvent.wParam; + MSLLHOOKSTRUCT? hookStruct = mouseEvent.MouseData; + + if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) + { + HideTooltip(); + + // ReSharper disable PossibleInvalidOperationException + Point cursorPosition = new Point(hookStruct.Value.pt.x, hookStruct.Value.pt.y); + // ReSharper restore PossibleInvalidOperationException + bool reRender = false; + + if (_tornTab != null && _dropAreas != null) + { + // ReSharper disable ForCanBeConvertedToForeach + for (int i = 0; i < _dropAreas.Length; i++) + // ReSharper restore ForCanBeConvertedToForeach + { + // If the cursor is within the drop area, combine the tab for the window that belongs to that drop area + if (_dropAreas[i].Item2.Contains(cursorPosition)) + { + TitleBarTab tabToCombine = null; + + lock (_tornTabLock) + { + if (_tornTab != null) + { + tabToCombine = _tornTab; + _tornTab = null; + } + } + + if (tabToCombine != null) + { + int i1 = i; + + // In all cases where we need to affect the UI, we call Invoke so that those changes are made on the main UI thread since + // we are on a separate processing thread in this case + Invoke( + new Action( + () => + { + _dropAreas[i1].Item1.TabRenderer.CombineTab(tabToCombine, cursorPosition); + + tabToCombine = null; + _tornTabForm.Close(); + _tornTabForm = null; + + if (_parentForm.Tabs.Count == 0) + { + _parentForm.Close(); + } + })); + } + } + } + } + + else if (!_parentForm.TabRenderer.IsTabRepositioning) + { + StartTooltipTimer(); Point relativeCursorPosition = GetRelativeCursorPosition(cursorPosition); // If we were over a close button previously, check to see if the cursor is still over that tab's // close button; if not, re-render if (_isOverCloseButtonForTab != -1 && - (_isOverCloseButtonForTab >= _parentForm.Tabs.Count || - !_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[_isOverCloseButtonForTab], relativeCursorPosition))) - { - reRender = true; - _isOverCloseButtonForTab = -1; - } - - // Otherwise, see if any tabs' close button is being hovered over - else - { + (_isOverCloseButtonForTab >= _parentForm.Tabs.Count || + !_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[_isOverCloseButtonForTab], relativeCursorPosition))) + { + reRender = true; + _isOverCloseButtonForTab = -1; + } + + // Otherwise, see if any tabs' close button is being hovered over + else + { // ReSharper disable ForCanBeConvertedToForeach for (int i = 0; i < _parentForm.Tabs.Count; i++) - // ReSharper restore ForCanBeConvertedToForeach - { - if (_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[i], relativeCursorPosition)) - { - _isOverCloseButtonForTab = i; - reRender = true; - - break; - } - } - } + // ReSharper restore ForCanBeConvertedToForeach + { + if (_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[i], relativeCursorPosition)) + { + _isOverCloseButtonForTab = i; + reRender = true; + + break; + } + } + } if (_isOverCloseButtonForTab == -1 && _parentForm.TabRenderer.RendersEntireTitleBar) { @@ -493,188 +493,187 @@ protected void InterpretMouseEvents() } } - else - { - Invoke( - new Action( - () => - { - _wasDragging = true; - - // When determining if a tab has been torn from the window while dragging, we take the drop area for this window and inflate it by the - // TabTearDragDistance setting - Rectangle dragArea = TabDropArea; - dragArea.Inflate(_parentForm.TabRenderer.TabTearDragDistance, _parentForm.TabRenderer.TabTearDragDistance); - - // If the cursor is outside the tear area, tear it away from the current window - if (!dragArea.Contains(cursorPosition) && _tornTab == null) - { - lock (_tornTabLock) - { - if (_tornTab == null) - { - _parentForm.TabRenderer.IsTabRepositioning = false; - - // Clear the event handler subscriptions from the tab and then create a thumbnail representation of it to use when dragging - _tornTab = _parentForm.SelectedTab; - _tornTab.ClearSubscriptions(); - _tornTabForm = new TornTabForm(_tornTab, _parentForm.TabRenderer); - } - } - - if (_tornTab != null) - { - _parentForm.SelectedTabIndex = (_parentForm.SelectedTabIndex == _parentForm.Tabs.Count - 1 - ? _parentForm.SelectedTabIndex - 1 - : _parentForm.SelectedTabIndex + 1); - _parentForm.Tabs.Remove(_tornTab); - - // If this tab was the only tab in the window, hide the parent window - if (_parentForm.Tabs.Count == 0) - { - _parentForm.Hide(); - } - - _tornTabForm.Show(); - _dropAreas = (from window in _parentForm.ApplicationContext.OpenWindows.Where(w => w.Tabs.Count > 0) - select new Tuple(window, window.TabDropArea)).ToArray(); - } - } - })); - } - - Invoke(new Action(() => OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, cursorPosition.X, cursorPosition.Y, 0)))); - - if (_parentForm.TabRenderer.IsTabRepositioning) - { - reRender = true; - } - - if (reRender) - { - Invoke(new Action(() => Render(cursorPosition, true))); - } - } + else + { + Invoke( + new Action( + () => + { + _wasDragging = true; + + // When determining if a tab has been torn from the window while dragging, we take the drop area for this window and inflate it by the + // TabTearDragDistance setting + Rectangle dragArea = TabDropArea; + dragArea.Inflate(_parentForm.TabRenderer.TabTearDragDistance, _parentForm.TabRenderer.TabTearDragDistance); + + // If the cursor is outside the tear area, tear it away from the current window + if (!dragArea.Contains(cursorPosition) && _tornTab == null) + { + lock (_tornTabLock) + { + if (_tornTab == null) + { + _parentForm.TabRenderer.IsTabRepositioning = false; + + // Clear the event handler subscriptions from the tab and then create a thumbnail representation of it to use when dragging + _tornTab = _parentForm.SelectedTab; + _tornTab.ClearSubscriptions(); + _tornTabForm = new TornTabForm(_tornTab, _parentForm.TabRenderer); + } + } + + if (_tornTab != null) + { + _parentForm.SelectedTabIndex = (_parentForm.SelectedTabIndex == _parentForm.Tabs.Count - 1 + ? _parentForm.SelectedTabIndex - 1 + : _parentForm.SelectedTabIndex + 1); + _parentForm.Tabs.Remove(_tornTab); + + // If this tab was the only tab in the window, hide the parent window + if (_parentForm.Tabs.Count == 0) + { + _parentForm.Hide(); + } + + _tornTabForm.Show(); + _dropAreas = (from window in _parentForm.ApplicationContext.OpenWindows.Where(w => w.Tabs.Count > 0) + select new Tuple(window, window.TabDropArea)).ToArray(); + } + } + })); + } + + Invoke(new Action(() => OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, cursorPosition.X, cursorPosition.Y, 0)))); + + if (_parentForm.TabRenderer.IsTabRepositioning) + { + reRender = true; + } + + if (reRender) + { + Invoke(new Action(() => Render(cursorPosition, true))); + } + } else if (nCode >= 0 && (int) WM.WM_LBUTTONDBLCLK == (int) wParam) { - if (DesktopBounds.Contains(_lastTwoClickCoordinates[0]) && DesktopBounds.Contains(_lastTwoClickCoordinates[1])) - { - Invoke(new Action(() => - { - _parentForm.WindowState = _parentForm.WindowState == FormWindowState.Maximized - ? FormWindowState.Normal - : FormWindowState.Maximized; - })); - } + if (DesktopBounds.Contains(_lastLeftButtonClickPoint)) + { + Invoke(new Action(() => + { + if (_parentForm.Focused || _parentForm.ContainsFocus) + { + _parentForm.WindowState = _parentForm.WindowState == FormWindowState.Maximized + ? FormWindowState.Normal + : FormWindowState.Maximized; + } + })); + } + } + + else if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) + { + _firstClick = false; + _wasDragging = false; } - else if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) - { - if (!_firstClick) + else if (nCode >= 0 && (int) WM.WM_LBUTTONUP == (int) wParam) + { + // If we released the mouse button while we were dragging a torn tab, put that tab into a new window + if (_tornTab != null) { - _lastTwoClickCoordinates[1] = _lastTwoClickCoordinates[0]; + TitleBarTab tabToRelease = null; + + lock (_tornTabLock) + { + if (_tornTab != null) + { + tabToRelease = _tornTab; + _tornTab = null; + } + } + + if (tabToRelease != null) + { + Invoke( + new Action( + () => + { + TitleBarTabs newWindow = (TitleBarTabs) Activator.CreateInstance(_parentForm.GetType()); + + // Set the initial window position and state properly + if (newWindow.WindowState == FormWindowState.Maximized) + { + Screen screen = Screen.AllScreens.First(s => s.WorkingArea.Contains(Cursor.Position)); + + newWindow.StartPosition = FormStartPosition.Manual; + newWindow.WindowState = FormWindowState.Normal; + newWindow.Left = screen.WorkingArea.Left; + newWindow.Top = screen.WorkingArea.Top; + newWindow.Width = screen.WorkingArea.Width; + newWindow.Height = screen.WorkingArea.Height; + } + + else + { + newWindow.Left = Cursor.Position.X; + newWindow.Top = Cursor.Position.Y; + } + + tabToRelease.Parent = newWindow; + _parentForm.ApplicationContext.OpenWindow(newWindow); + + newWindow.Show(); + newWindow.Tabs.Add(tabToRelease); + newWindow.SelectedTabIndex = 0; + newWindow.ResizeTabContents(); + + _tornTabForm.Close(); + _tornTabForm = null; + + if (_parentForm.Tabs.Count == 0) + { + _parentForm.Close(); + } + })); + } } - _lastTwoClickCoordinates[0] = Cursor.Position; - - _firstClick = false; - _wasDragging = false; - } - - else if (nCode >= 0 && (int) WM.WM_LBUTTONUP == (int) wParam) - { - // If we released the mouse button while we were dragging a torn tab, put that tab into a new window - if (_tornTab != null) - { - TitleBarTab tabToRelease = null; - - lock (_tornTabLock) - { - if (_tornTab != null) - { - tabToRelease = _tornTab; - _tornTab = null; - } - } - - if (tabToRelease != null) - { - Invoke( - new Action( - () => - { - TitleBarTabs newWindow = (TitleBarTabs) Activator.CreateInstance(_parentForm.GetType()); - - // Set the initial window position and state properly - if (newWindow.WindowState == FormWindowState.Maximized) - { - Screen screen = Screen.AllScreens.First(s => s.WorkingArea.Contains(Cursor.Position)); - - newWindow.StartPosition = FormStartPosition.Manual; - newWindow.WindowState = FormWindowState.Normal; - newWindow.Left = screen.WorkingArea.Left; - newWindow.Top = screen.WorkingArea.Top; - newWindow.Width = screen.WorkingArea.Width; - newWindow.Height = screen.WorkingArea.Height; - } - - else - { - newWindow.Left = Cursor.Position.X; - newWindow.Top = Cursor.Position.Y; - } - - tabToRelease.Parent = newWindow; - _parentForm.ApplicationContext.OpenWindow(newWindow); - - newWindow.Show(); - newWindow.Tabs.Add(tabToRelease); - newWindow.SelectedTabIndex = 0; - newWindow.ResizeTabContents(); - - _tornTabForm.Close(); - _tornTabForm = null; - - if (_parentForm.Tabs.Count == 0) - { - _parentForm.Close(); - } - })); - } - } - - Invoke(new Action(() => OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)))); - } - } - } - - /// Hook callback to process messages to highlight/un-highlight the close button on each tab. - /// The message being received. - /// Additional information about the message. - /// Additional information about the message. - /// A zero value if the procedure processes the message; a nonzero value if the procedure ignores the message. - protected IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) - { - MouseEvent mouseEvent = new MouseEvent - { - nCode = nCode, - wParam = wParam, - lParam = lParam - }; - - if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) - { - mouseEvent.MouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT)); - } - - _mouseEvents.Add(mouseEvent); + Invoke(new Action(() => OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)))); + } + } + } + + /// Hook callback to process messages to highlight/un-highlight the close button on each tab. + /// The message being received. + /// Additional information about the message. + /// Additional information about the message. + /// A zero value if the procedure processes the message; a nonzero value if the procedure ignores the message. + protected IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) + { + MouseEvent mouseEvent = new MouseEvent + { + nCode = nCode, + wParam = wParam, + lParam = lParam + }; + + if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) + { + mouseEvent.MouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT)); + } + + _mouseEvents.Add(mouseEvent); if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) { long currentTicks = DateTime.Now.Ticks; - if (_lastLeftButtonClickTicks > 0 && currentTicks - _lastLeftButtonClickTicks < _doubleClickInterval * 10000) + var mouseData = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); + var currentPoint = new Point(mouseData.pt.x, mouseData.pt.y); + var isSameClickPositionAsFirstClick = _lastLeftButtonClickPoint == currentPoint; + if (isSameClickPositionAsFirstClick && _lastLeftButtonClickTicks > 0 && currentTicks - _lastLeftButtonClickTicks < _doubleClickInterval * 10000) { _mouseEvents.Add(new MouseEvent { @@ -685,488 +684,489 @@ protected IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) } _lastLeftButtonClickTicks = currentTicks; + _lastLeftButtonClickPoint = currentPoint; } - return User32.CallNextHookEx(_hookId, nCode, wParam, lParam); - } - - /// Draws the titlebar background behind the tabs if Aero glass is not enabled. - /// Graphics context with which to draw the background. - protected virtual void DrawTitleBarBackground(Graphics graphics) - { - if (DisplayType == DisplayType.Aero) - { - return; - } - - Rectangle fillArea; - - if (DisplayType == DisplayType.Basic) - { - fillArea = new Rectangle( - new Point( - 1, Top == 0 - ? SystemInformation.CaptionHeight - 1 - : (SystemInformation.CaptionHeight + SystemInformation.VerticalResizeBorderThickness) - (Top - _parentForm.Top) - 1), - new Size(Width - 2, _parentForm.Padding.Top)); - } - - else - { - fillArea = new Rectangle(new Point(1, 0), new Size(Width - 2, Height - 1)); - } - - if (fillArea.Height <= 0) - { - return; - } - - // Adjust the margin so that the gradient stops immediately prior to the control box in the titlebar - int rightMargin = 3; - - if (_parentForm.ControlBox && _parentForm.MinimizeBox) - { - rightMargin += SystemInformation.CaptionButtonSize.Width; - } - - if (_parentForm.ControlBox && _parentForm.MaximizeBox) - { - rightMargin += SystemInformation.CaptionButtonSize.Width; - } - - if (_parentForm.ControlBox) - { - rightMargin += SystemInformation.CaptionButtonSize.Width; - } - - LinearGradientBrush gradient = new LinearGradientBrush( - new Point(24, 0), new Point(fillArea.Width - rightMargin + 1, 0), TitleBarColor, TitleBarGradientColor); - - using (BufferedGraphics bufferedGraphics = BufferedGraphicsManager.Current.Allocate(graphics, fillArea)) - { - bufferedGraphics.Graphics.FillRectangle(new SolidBrush(TitleBarColor), fillArea); - bufferedGraphics.Graphics.FillRectangle( - new SolidBrush(TitleBarGradientColor), - new Rectangle(new Point(fillArea.Location.X + fillArea.Width - rightMargin, fillArea.Location.Y), new Size(rightMargin, fillArea.Height))); - bufferedGraphics.Graphics.FillRectangle( - gradient, new Rectangle(fillArea.Location, new Size(fillArea.Width - rightMargin, fillArea.Height))); - bufferedGraphics.Graphics.FillRectangle(new SolidBrush(TitleBarColor), new Rectangle(fillArea.Location, new Size(24, fillArea.Height))); - - bufferedGraphics.Render(graphics); - } - } - - /// - /// Event handler that is called when 's event is fired which re-renders - /// the tabs. - /// - /// Object from which the event originated. - /// Arguments associated with the event. - private void _parentForm_SystemColorsChanged(object sender, EventArgs e) - { - _aeroEnabled = _parentForm.IsCompositionEnabled; - OnPosition(); - } - - /// - /// Event handler that is called when 's , , or - /// events are fired which re-renders the tabs. - /// - /// Object from which the event originated. - /// Arguments associated with the event. - private void _parentForm_Refresh(object sender, EventArgs e) - { - if (_parentForm.WindowState == FormWindowState.Minimized) - { - Visible = false; - } - - else - { - OnPosition(); - } - } - - /// Sets the position of the overlay window to match that of so that it moves in tandem with it. - protected void OnPosition() - { - if (!IsDisposed) - { - // 92 is SM_CXPADDEDBORDER, which returns the amount of extra border padding around captioned windows - int borderPadding = DisplayType == DisplayType.Classic - ? 0 - : User32.GetSystemMetrics(92); - - // If the form is in a non-maximized state, we position the tabs below the minimize/maximize/close - // buttons - Top = _parentForm.Top + (DisplayType == DisplayType.Classic - ? SystemInformation.VerticalResizeBorderThickness - : _parentForm.WindowState == FormWindowState.Maximized - ? SystemInformation.VerticalResizeBorderThickness + borderPadding - : _parentForm.TabRenderer.RendersEntireTitleBar + return User32.CallNextHookEx(_hookId, nCode, wParam, lParam); + } + + /// Draws the titlebar background behind the tabs if Aero glass is not enabled. + /// Graphics context with which to draw the background. + protected virtual void DrawTitleBarBackground(Graphics graphics) + { + if (DisplayType == DisplayType.Aero) + { + return; + } + + Rectangle fillArea; + + if (DisplayType == DisplayType.Basic) + { + fillArea = new Rectangle( + new Point( + 1, Top == 0 + ? SystemInformation.CaptionHeight - 1 + : (SystemInformation.CaptionHeight + SystemInformation.VerticalResizeBorderThickness) - (Top - _parentForm.Top) - 1), + new Size(Width - 2, _parentForm.Padding.Top)); + } + + else + { + fillArea = new Rectangle(new Point(1, 0), new Size(Width - 2, Height - 1)); + } + + if (fillArea.Height <= 0) + { + return; + } + + // Adjust the margin so that the gradient stops immediately prior to the control box in the titlebar + int rightMargin = 3; + + if (_parentForm.ControlBox && _parentForm.MinimizeBox) + { + rightMargin += SystemInformation.CaptionButtonSize.Width; + } + + if (_parentForm.ControlBox && _parentForm.MaximizeBox) + { + rightMargin += SystemInformation.CaptionButtonSize.Width; + } + + if (_parentForm.ControlBox) + { + rightMargin += SystemInformation.CaptionButtonSize.Width; + } + + LinearGradientBrush gradient = new LinearGradientBrush( + new Point(24, 0), new Point(fillArea.Width - rightMargin + 1, 0), TitleBarColor, TitleBarGradientColor); + + using (BufferedGraphics bufferedGraphics = BufferedGraphicsManager.Current.Allocate(graphics, fillArea)) + { + bufferedGraphics.Graphics.FillRectangle(new SolidBrush(TitleBarColor), fillArea); + bufferedGraphics.Graphics.FillRectangle( + new SolidBrush(TitleBarGradientColor), + new Rectangle(new Point(fillArea.Location.X + fillArea.Width - rightMargin, fillArea.Location.Y), new Size(rightMargin, fillArea.Height))); + bufferedGraphics.Graphics.FillRectangle( + gradient, new Rectangle(fillArea.Location, new Size(fillArea.Width - rightMargin, fillArea.Height))); + bufferedGraphics.Graphics.FillRectangle(new SolidBrush(TitleBarColor), new Rectangle(fillArea.Location, new Size(24, fillArea.Height))); + + bufferedGraphics.Render(graphics); + } + } + + /// + /// Event handler that is called when 's event is fired which re-renders + /// the tabs. + /// + /// Object from which the event originated. + /// Arguments associated with the event. + private void _parentForm_SystemColorsChanged(object sender, EventArgs e) + { + _aeroEnabled = _parentForm.IsCompositionEnabled; + OnPosition(); + } + + /// + /// Event handler that is called when 's , , or + /// events are fired which re-renders the tabs. + /// + /// Object from which the event originated. + /// Arguments associated with the event. + private void _parentForm_Refresh(object sender, EventArgs e) + { + if (_parentForm.WindowState == FormWindowState.Minimized) + { + Visible = false; + } + + else + { + OnPosition(); + } + } + + /// Sets the position of the overlay window to match that of so that it moves in tandem with it. + protected void OnPosition() + { + if (!IsDisposed) + { + // 92 is SM_CXPADDEDBORDER, which returns the amount of extra border padding around captioned windows + int borderPadding = DisplayType == DisplayType.Classic + ? 0 + : User32.GetSystemMetrics(92); + + // If the form is in a non-maximized state, we position the tabs below the minimize/maximize/close + // buttons + Top = _parentForm.Top + (DisplayType == DisplayType.Classic + ? SystemInformation.VerticalResizeBorderThickness + : _parentForm.WindowState == FormWindowState.Maximized + ? SystemInformation.VerticalResizeBorderThickness + borderPadding + : _parentForm.TabRenderer.RendersEntireTitleBar ? _parentForm.TabRenderer.IsWindows10 - ? SystemInformation.BorderSize.Width - : 0 - : borderPadding); - Left = _parentForm.Left + SystemInformation.HorizontalResizeBorderThickness - (_parentForm.TabRenderer.IsWindows10 ? 0 : SystemInformation.BorderSize.Width) + borderPadding; - Width = _parentForm.Width - ((SystemInformation.VerticalResizeBorderThickness + borderPadding) * 2) + (_parentForm.TabRenderer.IsWindows10 ? 0 : (SystemInformation.BorderSize.Width * 2)); - Height = _parentForm.TabRenderer.TabHeight + (DisplayType == DisplayType.Classic && _parentForm.WindowState != FormWindowState.Maximized && !_parentForm.TabRenderer.RendersEntireTitleBar - ? SystemInformation.CaptionButtonSize.Height - : _parentForm.TabRenderer.IsWindows10 - ? -1 * SystemInformation.BorderSize.Width - : _parentForm.WindowState != FormWindowState.Maximized - ? borderPadding - : 0); - - Render(); - } - } - - /// - /// Renders the tabs and then calls to blend the tab content with the underlying window ( - /// ). - /// - /// Flag indicating whether a full render should be forced. - public void Render(bool forceRedraw = false) - { - Render(Cursor.Position, forceRedraw); - } - - /// - /// Renders the tabs and then calls to blend the tab content with the underlying window ( - /// ). - /// - /// Current position of the cursor. - /// Flag indicating whether a full render should be forced. - public void Render(Point cursorPosition, bool forceRedraw = false) - { - if (!IsDisposed && _parentForm.TabRenderer != null && _parentForm.WindowState != FormWindowState.Minimized && _parentForm.ClientRectangle.Width > 0) - { - cursorPosition = GetRelativeCursorPosition(cursorPosition); - - using (Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb)) - { - using (Graphics graphics = Graphics.FromImage(bitmap)) - { - DrawTitleBarBackground(graphics); - - // Since classic mode themes draw over the *entire* titlebar, not just the area immediately behind the tabs, we have to offset the tabs - // when rendering in the window - Point offset = _parentForm.WindowState != FormWindowState.Maximized && DisplayType == DisplayType.Classic && !_parentForm.TabRenderer.RendersEntireTitleBar - ? new Point(0, SystemInformation.CaptionButtonSize.Height) - : _parentForm.WindowState != FormWindowState.Maximized && !_parentForm.TabRenderer.RendersEntireTitleBar + ? SystemInformation.BorderSize.Width + : 0 + : borderPadding); + Left = _parentForm.Left + SystemInformation.HorizontalResizeBorderThickness - (_parentForm.TabRenderer.IsWindows10 ? 0 : SystemInformation.BorderSize.Width) + borderPadding; + Width = _parentForm.Width - ((SystemInformation.VerticalResizeBorderThickness + borderPadding) * 2) + (_parentForm.TabRenderer.IsWindows10 ? 0 : (SystemInformation.BorderSize.Width * 2)); + Height = _parentForm.TabRenderer.TabHeight + (DisplayType == DisplayType.Classic && _parentForm.WindowState != FormWindowState.Maximized && !_parentForm.TabRenderer.RendersEntireTitleBar + ? SystemInformation.CaptionButtonSize.Height + : _parentForm.TabRenderer.IsWindows10 + ? -1 * SystemInformation.BorderSize.Width + : _parentForm.WindowState != FormWindowState.Maximized + ? borderPadding + : 0); + + Render(); + } + } + + /// + /// Renders the tabs and then calls to blend the tab content with the underlying window ( + /// ). + /// + /// Flag indicating whether a full render should be forced. + public void Render(bool forceRedraw = false) + { + Render(Cursor.Position, forceRedraw); + } + + /// + /// Renders the tabs and then calls to blend the tab content with the underlying window ( + /// ). + /// + /// Current position of the cursor. + /// Flag indicating whether a full render should be forced. + public void Render(Point cursorPosition, bool forceRedraw = false) + { + if (!IsDisposed && _parentForm.TabRenderer != null && _parentForm.WindowState != FormWindowState.Minimized && _parentForm.ClientRectangle.Width > 0) + { + cursorPosition = GetRelativeCursorPosition(cursorPosition); + + using (Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb)) + { + using (Graphics graphics = Graphics.FromImage(bitmap)) + { + DrawTitleBarBackground(graphics); + + // Since classic mode themes draw over the *entire* titlebar, not just the area immediately behind the tabs, we have to offset the tabs + // when rendering in the window + Point offset = _parentForm.WindowState != FormWindowState.Maximized && DisplayType == DisplayType.Classic && !_parentForm.TabRenderer.RendersEntireTitleBar + ? new Point(0, SystemInformation.CaptionButtonSize.Height) + : _parentForm.WindowState != FormWindowState.Maximized && !_parentForm.TabRenderer.RendersEntireTitleBar ? new Point(0, SystemInformation.VerticalResizeBorderThickness - SystemInformation.BorderSize.Height) - : new Point(0, 0); - - // Render the tabs into the bitmap - _parentForm.TabRenderer.Render(_parentForm.Tabs, graphics, offset, cursorPosition, forceRedraw); - - // Cut out a hole in the background so that the control box on the underlying window can be shown - if (DisplayType == DisplayType.Classic && (_parentForm.ControlBox || _parentForm.MaximizeBox || _parentForm.MinimizeBox)) - { - int boxWidth = 0; - - if (_parentForm.ControlBox) - { - boxWidth += SystemInformation.CaptionButtonSize.Width; - } - - if (_parentForm.MinimizeBox) - { - boxWidth += SystemInformation.CaptionButtonSize.Width; - } - - if (_parentForm.MaximizeBox) - { - boxWidth += SystemInformation.CaptionButtonSize.Width; - } - - CompositingMode oldCompositingMode = graphics.CompositingMode; - - graphics.CompositingMode = CompositingMode.SourceCopy; - graphics.FillRectangle( - new SolidBrush(Color.Transparent), Width - boxWidth, 0, boxWidth, SystemInformation.CaptionButtonSize.Height); - graphics.CompositingMode = oldCompositingMode; - } - - IntPtr screenDc = User32.GetDC(IntPtr.Zero); - IntPtr memDc = Gdi32.CreateCompatibleDC(screenDc); - IntPtr oldBitmap = IntPtr.Zero; - IntPtr bitmapHandle = IntPtr.Zero; - - try - { - // Copy the contents of the bitmap into memDc - bitmapHandle = bitmap.GetHbitmap(Color.FromArgb(0)); - oldBitmap = Gdi32.SelectObject(memDc, bitmapHandle); - - SIZE size = new SIZE - { - cx = bitmap.Width, - cy = bitmap.Height - }; - - POINT pointSource = new POINT - { - x = 0, - y = 0 - }; - POINT topPos = new POINT - { - x = Left, - y = Top - }; - BLENDFUNCTION blend = new BLENDFUNCTION - { - // We want to blend the bitmap's content with the screen content under it - BlendOp = Convert.ToByte((int) AC.AC_SRC_OVER), - BlendFlags = 0, - // Follow the parent forms' opacity level - SourceConstantAlpha = (byte)(_parentForm.Opacity * 255), - // We use the bitmap's alpha channel for blending instead of a pre-defined transparency key - AlphaFormat = Convert.ToByte((int) AC.AC_SRC_ALPHA) - }; - - // Blend the tab content with the underlying content - if (!User32.UpdateLayeredWindow( - Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, ULW.ULW_ALPHA)) - { - int error = Marshal.GetLastWin32Error(); - throw new Win32Exception(error, "Error while calling UpdateLayeredWindow()."); - } - } - - // Clean up after ourselves - finally - { - User32.ReleaseDC(IntPtr.Zero, screenDc); - - if (bitmapHandle != IntPtr.Zero) - { - Gdi32.SelectObject(memDc, oldBitmap); - Gdi32.DeleteObject(bitmapHandle); - } - - Gdi32.DeleteDC(memDc); - } - } - } - } - } - - /// Gets the relative location of the cursor within the overlay. - /// Cursor position that represents the absolute position of the cursor on the screen. - /// The relative location of the cursor within the overlay. - public Point GetRelativeCursorPosition(Point cursorPosition) - { - return new Point(cursorPosition.X - Location.X, cursorPosition.Y - Location.Y); - } - - /// Overrides the message pump for the window so that we can respond to click events on the tabs themselves. - /// Message received by the pump. - protected override void WndProc(ref Message m) - { - switch ((WM) m.Msg) - { - case WM.WM_SYSCOMMAND: - if (m.WParam == new IntPtr(0xF030) || m.WParam == new IntPtr(0xF120) || m.WParam == new IntPtr(0xF020)) - { - _parentForm.ForwardMessage(ref m); - } - - else + : new Point(0, 0); + + // Render the tabs into the bitmap + _parentForm.TabRenderer.Render(_parentForm.Tabs, graphics, offset, cursorPosition, forceRedraw); + + // Cut out a hole in the background so that the control box on the underlying window can be shown + if (DisplayType == DisplayType.Classic && (_parentForm.ControlBox || _parentForm.MaximizeBox || _parentForm.MinimizeBox)) + { + int boxWidth = 0; + + if (_parentForm.ControlBox) + { + boxWidth += SystemInformation.CaptionButtonSize.Width; + } + + if (_parentForm.MinimizeBox) + { + boxWidth += SystemInformation.CaptionButtonSize.Width; + } + + if (_parentForm.MaximizeBox) + { + boxWidth += SystemInformation.CaptionButtonSize.Width; + } + + CompositingMode oldCompositingMode = graphics.CompositingMode; + + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.FillRectangle( + new SolidBrush(Color.Transparent), Width - boxWidth, 0, boxWidth, SystemInformation.CaptionButtonSize.Height); + graphics.CompositingMode = oldCompositingMode; + } + + IntPtr screenDc = User32.GetDC(IntPtr.Zero); + IntPtr memDc = Gdi32.CreateCompatibleDC(screenDc); + IntPtr oldBitmap = IntPtr.Zero; + IntPtr bitmapHandle = IntPtr.Zero; + + try + { + // Copy the contents of the bitmap into memDc + bitmapHandle = bitmap.GetHbitmap(Color.FromArgb(0)); + oldBitmap = Gdi32.SelectObject(memDc, bitmapHandle); + + SIZE size = new SIZE + { + cx = bitmap.Width, + cy = bitmap.Height + }; + + POINT pointSource = new POINT + { + x = 0, + y = 0 + }; + POINT topPos = new POINT + { + x = Left, + y = Top + }; + BLENDFUNCTION blend = new BLENDFUNCTION + { + // We want to blend the bitmap's content with the screen content under it + BlendOp = Convert.ToByte((int) AC.AC_SRC_OVER), + BlendFlags = 0, + // Follow the parent forms' opacity level + SourceConstantAlpha = (byte)(_parentForm.Opacity * 255), + // We use the bitmap's alpha channel for blending instead of a pre-defined transparency key + AlphaFormat = Convert.ToByte((int) AC.AC_SRC_ALPHA) + }; + + // Blend the tab content with the underlying content + if (!User32.UpdateLayeredWindow( + Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, ULW.ULW_ALPHA)) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error, "Error while calling UpdateLayeredWindow()."); + } + } + + // Clean up after ourselves + finally + { + User32.ReleaseDC(IntPtr.Zero, screenDc); + + if (bitmapHandle != IntPtr.Zero) + { + Gdi32.SelectObject(memDc, oldBitmap); + Gdi32.DeleteObject(bitmapHandle); + } + + Gdi32.DeleteDC(memDc); + } + } + } + } + } + + /// Gets the relative location of the cursor within the overlay. + /// Cursor position that represents the absolute position of the cursor on the screen. + /// The relative location of the cursor within the overlay. + public Point GetRelativeCursorPosition(Point cursorPosition) + { + return new Point(cursorPosition.X - Location.X, cursorPosition.Y - Location.Y); + } + + /// Overrides the message pump for the window so that we can respond to click events on the tabs themselves. + /// Message received by the pump. + protected override void WndProc(ref Message m) + { + switch ((WM) m.Msg) + { + case WM.WM_SYSCOMMAND: + if (m.WParam == new IntPtr(0xF030) || m.WParam == new IntPtr(0xF120) || m.WParam == new IntPtr(0xF020)) + { + _parentForm.ForwardMessage(ref m); + } + + else + { + base.WndProc(ref m); + } + + break; + + case WM.WM_NCLBUTTONDOWN: + case WM.WM_LBUTTONDOWN: + Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); + + // If we were over a tab, set the capture state for the window so that we'll actually receive a WM_LBUTTONUP message + if (_parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition) == null && + !_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition)) + { + _parentForm.ForwardMessage(ref m); + } + + else { - base.WndProc(ref m); + // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released + TitleBarTab clickedTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); + + if (clickedTab != null) + { + // If the user clicked the close button, remove the tab from the list + if (!_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition)) + { + _parentForm.ResizeTabContents(clickedTab); + _parentForm.SelectedTabIndex = _parentForm.Tabs.IndexOf(clickedTab); + + Render(); + } + + OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); + } + + _parentForm.Activate(); + } + + break; + + case WM.WM_LBUTTONDBLCLK: + _parentForm.ForwardMessage(ref m); + break; + + // We always return HTCAPTION for the hit test message so that the underlying window doesn't have its focus removed + case WM.WM_NCHITTEST: + m.Result = new IntPtr((int) _parentForm.TabRenderer.NonClientHitTest(m, GetRelativeCursorPosition(Cursor.Position))); + break; + + case WM.WM_LBUTTONUP: + case WM.WM_NCLBUTTONUP: + case WM.WM_MBUTTONUP: + case WM.WM_NCMBUTTONUP: + Point relativeCursorPosition2 = GetRelativeCursorPosition(Cursor.Position); + + if (_parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition2) == null && + !_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) + { + _parentForm.ForwardMessage(ref m); + } + + else + { + // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released + TitleBarTab clickedTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition2); + + if (clickedTab != null) + { + // If the user clicks the middle button/scroll wheel over a tab, close it + if ((WM) m.Msg == WM.WM_MBUTTONUP || (WM) m.Msg == WM.WM_NCMBUTTONUP) + { + clickedTab.Content.Close(); + Render(); + } + + else + { + // If the user clicked the close button, remove the tab from the list + if (_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition2)) + { + clickedTab.Content.Close(); + Render(); + } + + else + { + _parentForm.OnTabClicked( + new TitleBarTabEventArgs + { + Tab = clickedTab, + TabIndex = _parentForm.SelectedTabIndex, + Action = TabControlAction.Selected, + WasDragging = _wasDragging + }); + } + } + } + + // Otherwise, if the user clicked the add button, call CreateTab to add a new tab to the list and select it + else if (_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) + { + _parentForm.AddNewTab(); + } + + if ((WM) m.Msg == WM.WM_LBUTTONUP || (WM) m.Msg == WM.WM_NCLBUTTONUP) + { + OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); + } } - break; - - case WM.WM_NCLBUTTONDOWN: - case WM.WM_LBUTTONDOWN: - Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); - - // If we were over a tab, set the capture state for the window so that we'll actually receive a WM_LBUTTONUP message - if (_parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition) == null && - !_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition)) - { - _parentForm.ForwardMessage(ref m); - } - - else - { - // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released - TitleBarTab clickedTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); - - if (clickedTab != null) - { - // If the user clicked the close button, remove the tab from the list - if (!_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition)) - { - _parentForm.ResizeTabContents(clickedTab); - _parentForm.SelectedTabIndex = _parentForm.Tabs.IndexOf(clickedTab); - - Render(); - } - - OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); - } - - _parentForm.Activate(); - } - - break; - - case WM.WM_LBUTTONDBLCLK: - _parentForm.ForwardMessage(ref m); - break; - - // We always return HTCAPTION for the hit test message so that the underlying window doesn't have its focus removed - case WM.WM_NCHITTEST: - m.Result = new IntPtr((int) _parentForm.TabRenderer.NonClientHitTest(m, GetRelativeCursorPosition(Cursor.Position))); - break; - - case WM.WM_LBUTTONUP: - case WM.WM_NCLBUTTONUP: - case WM.WM_MBUTTONUP: - case WM.WM_NCMBUTTONUP: - Point relativeCursorPosition2 = GetRelativeCursorPosition(Cursor.Position); - - if (_parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition2) == null && - !_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) - { - _parentForm.ForwardMessage(ref m); - } - - else - { - // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released - TitleBarTab clickedTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition2); - - if (clickedTab != null) - { - // If the user clicks the middle button/scroll wheel over a tab, close it - if ((WM) m.Msg == WM.WM_MBUTTONUP || (WM) m.Msg == WM.WM_NCMBUTTONUP) - { - clickedTab.Content.Close(); - Render(); - } - - else - { - // If the user clicked the close button, remove the tab from the list - if (_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition2)) - { - clickedTab.Content.Close(); - Render(); - } - - else - { - _parentForm.OnTabClicked( - new TitleBarTabEventArgs - { - Tab = clickedTab, - TabIndex = _parentForm.SelectedTabIndex, - Action = TabControlAction.Selected, - WasDragging = _wasDragging - }); - } - } - } - - // Otherwise, if the user clicked the add button, call CreateTab to add a new tab to the list and select it - else if (_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) - { - _parentForm.AddNewTab(); - } - - if ((WM) m.Msg == WM.WM_LBUTTONUP || (WM) m.Msg == WM.WM_NCLBUTTONUP) - { - OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); - } - } - - break; - - default: - base.WndProc(ref m); - break; - } - } - - /// Event handler that is called when 's event is fired. - /// Object from which this event originated. - /// Arguments associated with the event. - private void _parentForm_Activated(object sender, EventArgs e) - { - _active = true; - Render(); - } - - /// Event handler that is called when 's event is fired. - /// Object from which this event originated. - /// Arguments associated with the event. - private void _parentForm_Deactivate(object sender, EventArgs e) - { - _active = false; - Render(); - } - - /// Event handler that is called when 's event is fired. - /// Object from which this event originated. - /// Arguments associated with the event. - private void _parentForm_Disposed(object sender, EventArgs e) - { - } - - /// - /// Contains information on mouse events captured by and processed by - /// . - /// - protected class MouseEvent - { - /// Code for the event. - // ReSharper disable InconsistentNaming - public int nCode - { - get; - set; - } - - /// wParam value associated with the event. - public IntPtr wParam - { - get; - set; - } - - /// lParam value associated with the event. - public IntPtr lParam - { - get; - set; - } - - // ReSharper restore InconsistentNaming - - /// Data associated with the mouse event. - public MSLLHOOKSTRUCT? MouseData - { - get; - set; - } - } - - private void InitializeComponent() - { - this.SuspendLayout(); - // - // TitleBarTabsOverlay - // - this.ClientSize = new System.Drawing.Size(284, 261); - this.Name = "TitleBarTabsOverlay"; - this.ResumeLayout(false); - - } - } + break; + + default: + base.WndProc(ref m); + break; + } + } + + /// Event handler that is called when 's event is fired. + /// Object from which this event originated. + /// Arguments associated with the event. + private void _parentForm_Activated(object sender, EventArgs e) + { + _active = true; + Render(); + } + + /// Event handler that is called when 's event is fired. + /// Object from which this event originated. + /// Arguments associated with the event. + private void _parentForm_Deactivate(object sender, EventArgs e) + { + _active = false; + Render(); + } + + /// Event handler that is called when 's event is fired. + /// Object from which this event originated. + /// Arguments associated with the event. + private void _parentForm_Disposed(object sender, EventArgs e) + { + } + + /// + /// Contains information on mouse events captured by and processed by + /// . + /// + protected class MouseEvent + { + /// Code for the event. + // ReSharper disable InconsistentNaming + public int nCode + { + get; + set; + } + + /// wParam value associated with the event. + public IntPtr wParam + { + get; + set; + } + + /// lParam value associated with the event. + public IntPtr lParam + { + get; + set; + } + + // ReSharper restore InconsistentNaming + + /// Data associated with the mouse event. + public MSLLHOOKSTRUCT? MouseData + { + get; + set; + } + } + + private void InitializeComponent() + { + this.SuspendLayout(); + // + // TitleBarTabsOverlay + // + this.ClientSize = new System.Drawing.Size(284, 261); + this.Name = "TitleBarTabsOverlay"; + this.ResumeLayout(false); + + } + } } \ No newline at end of file From a8dda76dd9698df4b13ea60a66bfcaba4bf007aa Mon Sep 17 00:00:00 2001 From: Marcus Strini Date: Wed, 6 Jul 2022 11:25:52 +0200 Subject: [PATCH 2/2] Change indentation to tabs --- TitleBarTabsOverlay.cs | 2294 ++++++++++++++++++++-------------------- 1 file changed, 1147 insertions(+), 1147 deletions(-) diff --git a/TitleBarTabsOverlay.cs b/TitleBarTabsOverlay.cs index 7a1e54cf..7f5c6e9b 100644 --- a/TitleBarTabsOverlay.cs +++ b/TitleBarTabsOverlay.cs @@ -17,1156 +17,1156 @@ namespace EasyTabs { - /// - /// Borderless overlay window that is moved with and rendered on top of the non-client area of a instance that's responsible - /// for rendering the actual tab content and responding to click events for those tabs. - /// - public class TitleBarTabsOverlay : Form - { - protected Timer showTooltipTimer; + /// + /// Borderless overlay window that is moved with and rendered on top of the non-client area of a instance that's responsible + /// for rendering the actual tab content and responding to click events for those tabs. + /// + public class TitleBarTabsOverlay : Form + { + protected Timer showTooltipTimer; - /// All of the parent forms and their overlays so that we don't create duplicate overlays across the application domain. - protected static Dictionary _parents = new Dictionary(); + /// All of the parent forms and their overlays so that we don't create duplicate overlays across the application domain. + protected static Dictionary _parents = new Dictionary(); - /// Tab that has been torn off from this window and is being dragged. - protected static TitleBarTab _tornTab; + /// Tab that has been torn off from this window and is being dragged. + protected static TitleBarTab _tornTab; - /// Thumbnail representation of used when dragging. - protected static TornTabForm _tornTabForm; + /// Thumbnail representation of used when dragging. + protected static TornTabForm _tornTabForm; - /// - /// Flag used in and to track whether the user was click/dragging when a particular event - /// occurred. - /// - protected static bool _wasDragging = false; + /// + /// Flag used in and to track whether the user was click/dragging when a particular event + /// occurred. + /// + protected static bool _wasDragging = false; - /// Flag indicating whether or not has been installed as a hook. - protected static bool _hookProcInstalled; - - /// Semaphore to control access to . - protected static object _tornTabLock = new object(); - - protected static uint _doubleClickInterval = User32.GetDoubleClickTime(); - - /// Flag indicating whether or not the underlying window is active. - protected bool _active = false; - - /// Flag indicating whether we should draw the titlebar background (i.e. we are in a non-Aero environment). - protected bool _aeroEnabled = false; - - /// - /// When a tab is torn from the window, this is where we store the areas on all open windows where tabs can be dropped to combine the tab with that - /// window. - /// - protected Tuple[] _dropAreas = null; - - /// Pointer to the low-level mouse hook callback (). - protected IntPtr _hookId; - - /// Delegate of ; declared as a member variable to keep it from being garbage collected. - protected HOOKPROC _hookproc = null; - - /// Index of the tab, if any, whose close button is being hovered over. - protected int _isOverCloseButtonForTab = -1; - - protected bool _isOverSizingBox = false; - - protected bool _isOverAddButton = true; - - /// Queue of mouse events reported by that need to be processed. - protected BlockingCollection _mouseEvents = new BlockingCollection(); - - /// Consumer thread for processing events in . - protected Thread _mouseEventsThread = null; - - /// Parent form for the overlay. - protected TitleBarTabs _parentForm; - - protected long _lastLeftButtonClickTicks = 0; - - protected bool _firstClick = true; - protected Point _lastLeftButtonClickPoint = Point.Empty; - - protected bool _parentFormClosing = false; - - /// Blank default constructor to ensure that the overlays are only initialized through . - protected TitleBarTabsOverlay() - { - } - - /// Creates the overlay window and attaches it to . - /// Parent form that the overlay should be rendered on top of. - protected TitleBarTabsOverlay(TitleBarTabs parentForm) - { - _parentForm = parentForm; - - // We don't want this window visible in the taskbar - ShowInTaskbar = false; - FormBorderStyle = FormBorderStyle.SizableToolWindow; - MinimizeBox = false; - MaximizeBox = false; - _aeroEnabled = _parentForm.IsCompositionEnabled; - - Show(_parentForm); - AttachHandlers(); - - showTooltipTimer = new Timer - { - AutoReset = false - }; - - showTooltipTimer.Elapsed += ShowTooltipTimer_Elapsed; - } - - /// - /// Makes sure that the window is created with an flag set so that it can be alpha-blended properly with the content ( - /// ) underneath the overlay. - /// - protected override CreateParams CreateParams - { - get - { - CreateParams createParams = base.CreateParams; - createParams.ExStyle |= (int) (WS_EX.WS_EX_LAYERED | WS_EX.WS_EX_NOACTIVATE); - - return createParams; - } - } - - /// Primary color for the titlebar background. - protected Color TitleBarColor - { - get - { - if (Application.RenderWithVisualStyles && Environment.OSVersion.Version.Major >= 6) - { - return _active - ? SystemColors.GradientActiveCaption - : SystemColors.GradientInactiveCaption; - } - - return _active - ? SystemColors.ActiveCaption - : SystemColors.InactiveCaption; - } - } - - /// Type of theme being used by the OS to render the desktop. - protected DisplayType DisplayType - { - get - { - if (_aeroEnabled) - { - return DisplayType.Aero; - } - - if (Application.RenderWithVisualStyles && Environment.OSVersion.Version.Major >= 6) - { - return DisplayType.Basic; - } - - return DisplayType.Classic; - } - } - - /// Gradient color for the titlebar background. - protected Color TitleBarGradientColor - { - get - { - return _active - ? SystemInformation.IsTitleBarGradientEnabled - ? SystemColors.GradientActiveCaption - : SystemColors.ActiveCaption - : SystemInformation.IsTitleBarGradientEnabled - ? SystemColors.GradientInactiveCaption - : SystemColors.InactiveCaption; - } - } - - /// Screen area in which tabs can be dragged to and dropped for this window. - public Rectangle TabDropArea - { - get - { - RECT windowRectangle; - User32.GetWindowRect(_parentForm.Handle, out windowRectangle); - - return new Rectangle( - windowRectangle.left + SystemInformation.HorizontalResizeBorderThickness, windowRectangle.top + SystemInformation.VerticalResizeBorderThickness, - ClientRectangle.Width, _parentForm.NonClientAreaHeight - SystemInformation.VerticalResizeBorderThickness); - } - } - - /// Retrieves or creates the overlay for . - /// Parent form that we are to create the overlay for. - /// Newly-created or previously existing overlay for . - public static TitleBarTabsOverlay GetInstance(TitleBarTabs parentForm) - { - if (!_parents.ContainsKey(parentForm)) - { - _parents.Add(parentForm, new TitleBarTabsOverlay(parentForm)); - } - - return _parents[parentForm]; - } - - /// - /// Attaches the various event handlers to so that the overlay is moved in synchronization to - /// . - /// - protected void AttachHandlers() - { - FormClosing += TitleBarTabsOverlay_FormClosing; - - _parentForm.FormClosing += _parentForm_FormClosing; - _parentForm.Disposed += _parentForm_Disposed; - _parentForm.Deactivate += _parentForm_Deactivate; - _parentForm.Activated += _parentForm_Activated; - _parentForm.SizeChanged += _parentForm_Refresh; - _parentForm.Shown += _parentForm_Refresh; - _parentForm.VisibleChanged += _parentForm_Refresh; - _parentForm.Move += _parentForm_Refresh; - _parentForm.SystemColorsChanged += _parentForm_SystemColorsChanged; - - if (_hookproc == null) - { - // Spin up a consumer thread to process mouse events from _mouseEvents - _mouseEventsThread = new Thread(InterpretMouseEvents) - { - Name = "Low level mouse hooks processing thread" - }; - _mouseEventsThread.Priority = ThreadPriority.Highest; - _mouseEventsThread.Start(); - - using (Process curProcess = Process.GetCurrentProcess()) - { - using (ProcessModule curModule = curProcess.MainModule) - { - // Install the low level mouse hook that will put events into _mouseEvents - _hookproc = MouseHookCallback; - _hookId = User32.SetWindowsHookEx(WH.WH_MOUSE_LL, _hookproc, Kernel32.GetModuleHandle(curModule.ModuleName), 0); - } - } - } - } - - private void TitleBarTabsOverlay_FormClosing(object sender, FormClosingEventArgs e) - { - if (!_parentFormClosing) - { - e.Cancel = true; - _parentFormClosing = true; - _parentForm.Close(); - } - } - - /// - /// Event handler that is called when is in the process of closing. This uninstalls from the low- - /// level hooks list and stops the consumer thread that processes those events. - /// - /// Object from which this event originated, in this case. - /// Arguments associated with this event. - private void _parentForm_FormClosing(object sender, CancelEventArgs e) - { - if (e.Cancel) - { - _parentFormClosing = false; - return; - } - - TitleBarTabs form = (TitleBarTabs) sender; - - if (form == null) - { - return; - } - - _parentFormClosing = true; - - if (_parents.ContainsKey(form)) - { - _parents.Remove(form); - } - - // Uninstall the mouse hook - User32.UnhookWindowsHookEx(_hookId); - - // Kill the mouse events processing thread - _mouseEvents.CompleteAdding(); - _mouseEventsThread.Abort(); - } - - private void HideTooltip() - { - showTooltipTimer.Stop(); - - if (_parentForm.InvokeRequired) - { - _parentForm.Invoke(new Action(() => - { - _parentForm.Tooltip.Hide(_parentForm); - })); - } - - else - { - _parentForm.Tooltip.Hide(_parentForm); - } - } - - private void ShowTooltip(TitleBarTabs tabsForm, string caption) - { - Point tooltipLocation = new Point(Cursor.Position.X + 7, Cursor.Position.Y + 55); - tabsForm.Tooltip.Show(caption, tabsForm, tabsForm.PointToClient(tooltipLocation), tabsForm.Tooltip.AutoPopDelay); - } - - private void ShowTooltipTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) - { - if (!_parentForm.ShowTooltips) - { - return; - } - - Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); - TitleBarTab hoverTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); - - if (hoverTab != null) - { - TitleBarTabs hoverTabForm = hoverTab.Parent; - - if (hoverTabForm.InvokeRequired) - { - hoverTabForm.Invoke(new Action(() => - { - ShowTooltip(hoverTabForm, hoverTab.Caption); - })); - } - - else - { - ShowTooltip(hoverTabForm, hoverTab.Caption); - } - } - } - - private void StartTooltipTimer() - { - if (!_parentForm.ShowTooltips) - { - return; - } - - Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); - TitleBarTab hoverTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); - - if (hoverTab != null) - { - showTooltipTimer.Interval = hoverTab.Parent.Tooltip.AutomaticDelay; - showTooltipTimer.Start(); - } - } - - /// Consumer method that processes mouse events in that are recorded by . - protected void InterpretMouseEvents() - { - foreach (MouseEvent mouseEvent in _mouseEvents.GetConsumingEnumerable()) - { - int nCode = mouseEvent.nCode; - IntPtr wParam = mouseEvent.wParam; - MSLLHOOKSTRUCT? hookStruct = mouseEvent.MouseData; - - if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) - { - HideTooltip(); - - // ReSharper disable PossibleInvalidOperationException - Point cursorPosition = new Point(hookStruct.Value.pt.x, hookStruct.Value.pt.y); - // ReSharper restore PossibleInvalidOperationException - bool reRender = false; - - if (_tornTab != null && _dropAreas != null) - { - // ReSharper disable ForCanBeConvertedToForeach - for (int i = 0; i < _dropAreas.Length; i++) - // ReSharper restore ForCanBeConvertedToForeach - { - // If the cursor is within the drop area, combine the tab for the window that belongs to that drop area - if (_dropAreas[i].Item2.Contains(cursorPosition)) - { - TitleBarTab tabToCombine = null; - - lock (_tornTabLock) - { - if (_tornTab != null) - { - tabToCombine = _tornTab; - _tornTab = null; - } - } - - if (tabToCombine != null) - { - int i1 = i; - - // In all cases where we need to affect the UI, we call Invoke so that those changes are made on the main UI thread since - // we are on a separate processing thread in this case - Invoke( - new Action( - () => - { - _dropAreas[i1].Item1.TabRenderer.CombineTab(tabToCombine, cursorPosition); - - tabToCombine = null; - _tornTabForm.Close(); - _tornTabForm = null; - - if (_parentForm.Tabs.Count == 0) - { - _parentForm.Close(); - } - })); - } - } - } - } - - else if (!_parentForm.TabRenderer.IsTabRepositioning) - { - StartTooltipTimer(); - - Point relativeCursorPosition = GetRelativeCursorPosition(cursorPosition); - - // If we were over a close button previously, check to see if the cursor is still over that tab's - // close button; if not, re-render - if (_isOverCloseButtonForTab != -1 && - (_isOverCloseButtonForTab >= _parentForm.Tabs.Count || - !_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[_isOverCloseButtonForTab], relativeCursorPosition))) - { - reRender = true; - _isOverCloseButtonForTab = -1; - } - - // Otherwise, see if any tabs' close button is being hovered over - else - { - // ReSharper disable ForCanBeConvertedToForeach - for (int i = 0; i < _parentForm.Tabs.Count; i++) - // ReSharper restore ForCanBeConvertedToForeach - { - if (_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[i], relativeCursorPosition)) - { - _isOverCloseButtonForTab = i; - reRender = true; - - break; - } - } - } - - if (_isOverCloseButtonForTab == -1 && _parentForm.TabRenderer.RendersEntireTitleBar) - { - if (_parentForm.TabRenderer.IsOverSizingBox(relativeCursorPosition)) - { - _isOverSizingBox = true; - reRender = true; - } - - else if (_isOverSizingBox) - { - _isOverSizingBox = false; - reRender = true; - } - } - - if (_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition)) - { - _isOverAddButton = true; - reRender = true; - } - - else if (_isOverAddButton) - { - _isOverAddButton = false; - reRender = true; - } - } - - else - { - Invoke( - new Action( - () => - { - _wasDragging = true; - - // When determining if a tab has been torn from the window while dragging, we take the drop area for this window and inflate it by the - // TabTearDragDistance setting - Rectangle dragArea = TabDropArea; - dragArea.Inflate(_parentForm.TabRenderer.TabTearDragDistance, _parentForm.TabRenderer.TabTearDragDistance); - - // If the cursor is outside the tear area, tear it away from the current window - if (!dragArea.Contains(cursorPosition) && _tornTab == null) - { - lock (_tornTabLock) - { - if (_tornTab == null) - { - _parentForm.TabRenderer.IsTabRepositioning = false; - - // Clear the event handler subscriptions from the tab and then create a thumbnail representation of it to use when dragging - _tornTab = _parentForm.SelectedTab; - _tornTab.ClearSubscriptions(); - _tornTabForm = new TornTabForm(_tornTab, _parentForm.TabRenderer); - } - } - - if (_tornTab != null) - { - _parentForm.SelectedTabIndex = (_parentForm.SelectedTabIndex == _parentForm.Tabs.Count - 1 - ? _parentForm.SelectedTabIndex - 1 - : _parentForm.SelectedTabIndex + 1); - _parentForm.Tabs.Remove(_tornTab); - - // If this tab was the only tab in the window, hide the parent window - if (_parentForm.Tabs.Count == 0) - { - _parentForm.Hide(); - } - - _tornTabForm.Show(); - _dropAreas = (from window in _parentForm.ApplicationContext.OpenWindows.Where(w => w.Tabs.Count > 0) - select new Tuple(window, window.TabDropArea)).ToArray(); - } - } - })); - } - - Invoke(new Action(() => OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, cursorPosition.X, cursorPosition.Y, 0)))); - - if (_parentForm.TabRenderer.IsTabRepositioning) - { - reRender = true; - } - - if (reRender) - { - Invoke(new Action(() => Render(cursorPosition, true))); - } - } - - else if (nCode >= 0 && (int) WM.WM_LBUTTONDBLCLK == (int) wParam) - { - if (DesktopBounds.Contains(_lastLeftButtonClickPoint)) - { - Invoke(new Action(() => - { - if (_parentForm.Focused || _parentForm.ContainsFocus) - { - _parentForm.WindowState = _parentForm.WindowState == FormWindowState.Maximized - ? FormWindowState.Normal - : FormWindowState.Maximized; - } - })); - } - } - - else if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) - { - _firstClick = false; - _wasDragging = false; - } - - else if (nCode >= 0 && (int) WM.WM_LBUTTONUP == (int) wParam) - { - // If we released the mouse button while we were dragging a torn tab, put that tab into a new window - if (_tornTab != null) - { - TitleBarTab tabToRelease = null; - - lock (_tornTabLock) - { - if (_tornTab != null) - { - tabToRelease = _tornTab; - _tornTab = null; - } - } - - if (tabToRelease != null) - { - Invoke( - new Action( - () => - { - TitleBarTabs newWindow = (TitleBarTabs) Activator.CreateInstance(_parentForm.GetType()); - - // Set the initial window position and state properly - if (newWindow.WindowState == FormWindowState.Maximized) - { - Screen screen = Screen.AllScreens.First(s => s.WorkingArea.Contains(Cursor.Position)); - - newWindow.StartPosition = FormStartPosition.Manual; - newWindow.WindowState = FormWindowState.Normal; - newWindow.Left = screen.WorkingArea.Left; - newWindow.Top = screen.WorkingArea.Top; - newWindow.Width = screen.WorkingArea.Width; - newWindow.Height = screen.WorkingArea.Height; - } - - else - { - newWindow.Left = Cursor.Position.X; - newWindow.Top = Cursor.Position.Y; - } - - tabToRelease.Parent = newWindow; - _parentForm.ApplicationContext.OpenWindow(newWindow); - - newWindow.Show(); - newWindow.Tabs.Add(tabToRelease); - newWindow.SelectedTabIndex = 0; - newWindow.ResizeTabContents(); - - _tornTabForm.Close(); - _tornTabForm = null; - - if (_parentForm.Tabs.Count == 0) - { - _parentForm.Close(); - } - })); - } - } - - Invoke(new Action(() => OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)))); - } - } - } - - /// Hook callback to process messages to highlight/un-highlight the close button on each tab. - /// The message being received. - /// Additional information about the message. - /// Additional information about the message. - /// A zero value if the procedure processes the message; a nonzero value if the procedure ignores the message. - protected IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) - { - MouseEvent mouseEvent = new MouseEvent - { - nCode = nCode, - wParam = wParam, - lParam = lParam - }; - - if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) - { - mouseEvent.MouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT)); - } - - _mouseEvents.Add(mouseEvent); - - if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) - { - long currentTicks = DateTime.Now.Ticks; - - var mouseData = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); - var currentPoint = new Point(mouseData.pt.x, mouseData.pt.y); - var isSameClickPositionAsFirstClick = _lastLeftButtonClickPoint == currentPoint; - if (isSameClickPositionAsFirstClick && _lastLeftButtonClickTicks > 0 && currentTicks - _lastLeftButtonClickTicks < _doubleClickInterval * 10000) - { - _mouseEvents.Add(new MouseEvent - { - nCode = nCode, - wParam = new IntPtr((int) WM.WM_LBUTTONDBLCLK), - lParam = lParam - }); - } - - _lastLeftButtonClickTicks = currentTicks; - _lastLeftButtonClickPoint = currentPoint; - } - - return User32.CallNextHookEx(_hookId, nCode, wParam, lParam); - } - - /// Draws the titlebar background behind the tabs if Aero glass is not enabled. - /// Graphics context with which to draw the background. - protected virtual void DrawTitleBarBackground(Graphics graphics) - { - if (DisplayType == DisplayType.Aero) - { - return; - } - - Rectangle fillArea; - - if (DisplayType == DisplayType.Basic) - { - fillArea = new Rectangle( - new Point( - 1, Top == 0 - ? SystemInformation.CaptionHeight - 1 - : (SystemInformation.CaptionHeight + SystemInformation.VerticalResizeBorderThickness) - (Top - _parentForm.Top) - 1), - new Size(Width - 2, _parentForm.Padding.Top)); - } - - else - { - fillArea = new Rectangle(new Point(1, 0), new Size(Width - 2, Height - 1)); - } - - if (fillArea.Height <= 0) - { - return; - } - - // Adjust the margin so that the gradient stops immediately prior to the control box in the titlebar - int rightMargin = 3; - - if (_parentForm.ControlBox && _parentForm.MinimizeBox) - { - rightMargin += SystemInformation.CaptionButtonSize.Width; - } - - if (_parentForm.ControlBox && _parentForm.MaximizeBox) - { - rightMargin += SystemInformation.CaptionButtonSize.Width; - } - - if (_parentForm.ControlBox) - { - rightMargin += SystemInformation.CaptionButtonSize.Width; - } - - LinearGradientBrush gradient = new LinearGradientBrush( - new Point(24, 0), new Point(fillArea.Width - rightMargin + 1, 0), TitleBarColor, TitleBarGradientColor); - - using (BufferedGraphics bufferedGraphics = BufferedGraphicsManager.Current.Allocate(graphics, fillArea)) - { - bufferedGraphics.Graphics.FillRectangle(new SolidBrush(TitleBarColor), fillArea); - bufferedGraphics.Graphics.FillRectangle( - new SolidBrush(TitleBarGradientColor), - new Rectangle(new Point(fillArea.Location.X + fillArea.Width - rightMargin, fillArea.Location.Y), new Size(rightMargin, fillArea.Height))); - bufferedGraphics.Graphics.FillRectangle( - gradient, new Rectangle(fillArea.Location, new Size(fillArea.Width - rightMargin, fillArea.Height))); - bufferedGraphics.Graphics.FillRectangle(new SolidBrush(TitleBarColor), new Rectangle(fillArea.Location, new Size(24, fillArea.Height))); - - bufferedGraphics.Render(graphics); - } - } - - /// - /// Event handler that is called when 's event is fired which re-renders - /// the tabs. - /// - /// Object from which the event originated. - /// Arguments associated with the event. - private void _parentForm_SystemColorsChanged(object sender, EventArgs e) - { - _aeroEnabled = _parentForm.IsCompositionEnabled; - OnPosition(); - } - - /// - /// Event handler that is called when 's , , or - /// events are fired which re-renders the tabs. - /// - /// Object from which the event originated. - /// Arguments associated with the event. - private void _parentForm_Refresh(object sender, EventArgs e) - { - if (_parentForm.WindowState == FormWindowState.Minimized) - { - Visible = false; - } - - else - { - OnPosition(); - } - } - - /// Sets the position of the overlay window to match that of so that it moves in tandem with it. - protected void OnPosition() - { - if (!IsDisposed) - { - // 92 is SM_CXPADDEDBORDER, which returns the amount of extra border padding around captioned windows - int borderPadding = DisplayType == DisplayType.Classic - ? 0 - : User32.GetSystemMetrics(92); - - // If the form is in a non-maximized state, we position the tabs below the minimize/maximize/close - // buttons - Top = _parentForm.Top + (DisplayType == DisplayType.Classic - ? SystemInformation.VerticalResizeBorderThickness - : _parentForm.WindowState == FormWindowState.Maximized - ? SystemInformation.VerticalResizeBorderThickness + borderPadding - : _parentForm.TabRenderer.RendersEntireTitleBar - ? _parentForm.TabRenderer.IsWindows10 - ? SystemInformation.BorderSize.Width - : 0 - : borderPadding); - Left = _parentForm.Left + SystemInformation.HorizontalResizeBorderThickness - (_parentForm.TabRenderer.IsWindows10 ? 0 : SystemInformation.BorderSize.Width) + borderPadding; - Width = _parentForm.Width - ((SystemInformation.VerticalResizeBorderThickness + borderPadding) * 2) + (_parentForm.TabRenderer.IsWindows10 ? 0 : (SystemInformation.BorderSize.Width * 2)); - Height = _parentForm.TabRenderer.TabHeight + (DisplayType == DisplayType.Classic && _parentForm.WindowState != FormWindowState.Maximized && !_parentForm.TabRenderer.RendersEntireTitleBar - ? SystemInformation.CaptionButtonSize.Height - : _parentForm.TabRenderer.IsWindows10 - ? -1 * SystemInformation.BorderSize.Width - : _parentForm.WindowState != FormWindowState.Maximized - ? borderPadding - : 0); - - Render(); - } - } - - /// - /// Renders the tabs and then calls to blend the tab content with the underlying window ( - /// ). - /// - /// Flag indicating whether a full render should be forced. - public void Render(bool forceRedraw = false) - { - Render(Cursor.Position, forceRedraw); - } - - /// - /// Renders the tabs and then calls to blend the tab content with the underlying window ( - /// ). - /// - /// Current position of the cursor. - /// Flag indicating whether a full render should be forced. - public void Render(Point cursorPosition, bool forceRedraw = false) - { - if (!IsDisposed && _parentForm.TabRenderer != null && _parentForm.WindowState != FormWindowState.Minimized && _parentForm.ClientRectangle.Width > 0) - { - cursorPosition = GetRelativeCursorPosition(cursorPosition); - - using (Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb)) - { - using (Graphics graphics = Graphics.FromImage(bitmap)) - { - DrawTitleBarBackground(graphics); - - // Since classic mode themes draw over the *entire* titlebar, not just the area immediately behind the tabs, we have to offset the tabs - // when rendering in the window - Point offset = _parentForm.WindowState != FormWindowState.Maximized && DisplayType == DisplayType.Classic && !_parentForm.TabRenderer.RendersEntireTitleBar - ? new Point(0, SystemInformation.CaptionButtonSize.Height) - : _parentForm.WindowState != FormWindowState.Maximized && !_parentForm.TabRenderer.RendersEntireTitleBar - ? new Point(0, SystemInformation.VerticalResizeBorderThickness - SystemInformation.BorderSize.Height) - : new Point(0, 0); - - // Render the tabs into the bitmap - _parentForm.TabRenderer.Render(_parentForm.Tabs, graphics, offset, cursorPosition, forceRedraw); - - // Cut out a hole in the background so that the control box on the underlying window can be shown - if (DisplayType == DisplayType.Classic && (_parentForm.ControlBox || _parentForm.MaximizeBox || _parentForm.MinimizeBox)) - { - int boxWidth = 0; - - if (_parentForm.ControlBox) - { - boxWidth += SystemInformation.CaptionButtonSize.Width; - } - - if (_parentForm.MinimizeBox) - { - boxWidth += SystemInformation.CaptionButtonSize.Width; - } - - if (_parentForm.MaximizeBox) - { - boxWidth += SystemInformation.CaptionButtonSize.Width; - } - - CompositingMode oldCompositingMode = graphics.CompositingMode; - - graphics.CompositingMode = CompositingMode.SourceCopy; - graphics.FillRectangle( - new SolidBrush(Color.Transparent), Width - boxWidth, 0, boxWidth, SystemInformation.CaptionButtonSize.Height); - graphics.CompositingMode = oldCompositingMode; - } - - IntPtr screenDc = User32.GetDC(IntPtr.Zero); - IntPtr memDc = Gdi32.CreateCompatibleDC(screenDc); - IntPtr oldBitmap = IntPtr.Zero; - IntPtr bitmapHandle = IntPtr.Zero; - - try - { - // Copy the contents of the bitmap into memDc - bitmapHandle = bitmap.GetHbitmap(Color.FromArgb(0)); - oldBitmap = Gdi32.SelectObject(memDc, bitmapHandle); - - SIZE size = new SIZE - { - cx = bitmap.Width, - cy = bitmap.Height - }; - - POINT pointSource = new POINT - { - x = 0, - y = 0 - }; - POINT topPos = new POINT - { - x = Left, - y = Top - }; - BLENDFUNCTION blend = new BLENDFUNCTION - { - // We want to blend the bitmap's content with the screen content under it - BlendOp = Convert.ToByte((int) AC.AC_SRC_OVER), - BlendFlags = 0, - // Follow the parent forms' opacity level - SourceConstantAlpha = (byte)(_parentForm.Opacity * 255), - // We use the bitmap's alpha channel for blending instead of a pre-defined transparency key - AlphaFormat = Convert.ToByte((int) AC.AC_SRC_ALPHA) - }; - - // Blend the tab content with the underlying content - if (!User32.UpdateLayeredWindow( - Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, ULW.ULW_ALPHA)) - { - int error = Marshal.GetLastWin32Error(); - throw new Win32Exception(error, "Error while calling UpdateLayeredWindow()."); - } - } - - // Clean up after ourselves - finally - { - User32.ReleaseDC(IntPtr.Zero, screenDc); - - if (bitmapHandle != IntPtr.Zero) - { - Gdi32.SelectObject(memDc, oldBitmap); - Gdi32.DeleteObject(bitmapHandle); - } - - Gdi32.DeleteDC(memDc); - } - } - } - } - } - - /// Gets the relative location of the cursor within the overlay. - /// Cursor position that represents the absolute position of the cursor on the screen. - /// The relative location of the cursor within the overlay. - public Point GetRelativeCursorPosition(Point cursorPosition) - { - return new Point(cursorPosition.X - Location.X, cursorPosition.Y - Location.Y); - } - - /// Overrides the message pump for the window so that we can respond to click events on the tabs themselves. - /// Message received by the pump. - protected override void WndProc(ref Message m) - { - switch ((WM) m.Msg) - { - case WM.WM_SYSCOMMAND: - if (m.WParam == new IntPtr(0xF030) || m.WParam == new IntPtr(0xF120) || m.WParam == new IntPtr(0xF020)) - { - _parentForm.ForwardMessage(ref m); - } - - else - { - base.WndProc(ref m); - } - - break; - - case WM.WM_NCLBUTTONDOWN: - case WM.WM_LBUTTONDOWN: - Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); - - // If we were over a tab, set the capture state for the window so that we'll actually receive a WM_LBUTTONUP message - if (_parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition) == null && - !_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition)) - { - _parentForm.ForwardMessage(ref m); - } - - else - { - // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released - TitleBarTab clickedTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); - - if (clickedTab != null) - { - // If the user clicked the close button, remove the tab from the list - if (!_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition)) - { - _parentForm.ResizeTabContents(clickedTab); - _parentForm.SelectedTabIndex = _parentForm.Tabs.IndexOf(clickedTab); - - Render(); - } - - OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); - } - - _parentForm.Activate(); - } - - break; - - case WM.WM_LBUTTONDBLCLK: - _parentForm.ForwardMessage(ref m); - break; - - // We always return HTCAPTION for the hit test message so that the underlying window doesn't have its focus removed - case WM.WM_NCHITTEST: - m.Result = new IntPtr((int) _parentForm.TabRenderer.NonClientHitTest(m, GetRelativeCursorPosition(Cursor.Position))); - break; - - case WM.WM_LBUTTONUP: - case WM.WM_NCLBUTTONUP: - case WM.WM_MBUTTONUP: - case WM.WM_NCMBUTTONUP: - Point relativeCursorPosition2 = GetRelativeCursorPosition(Cursor.Position); - - if (_parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition2) == null && - !_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) - { - _parentForm.ForwardMessage(ref m); - } - - else - { - // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released - TitleBarTab clickedTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition2); - - if (clickedTab != null) - { - // If the user clicks the middle button/scroll wheel over a tab, close it - if ((WM) m.Msg == WM.WM_MBUTTONUP || (WM) m.Msg == WM.WM_NCMBUTTONUP) - { - clickedTab.Content.Close(); - Render(); - } - - else - { - // If the user clicked the close button, remove the tab from the list - if (_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition2)) - { - clickedTab.Content.Close(); - Render(); - } - - else - { - _parentForm.OnTabClicked( - new TitleBarTabEventArgs - { - Tab = clickedTab, - TabIndex = _parentForm.SelectedTabIndex, - Action = TabControlAction.Selected, - WasDragging = _wasDragging - }); - } - } - } - - // Otherwise, if the user clicked the add button, call CreateTab to add a new tab to the list and select it - else if (_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) - { - _parentForm.AddNewTab(); - } - - if ((WM) m.Msg == WM.WM_LBUTTONUP || (WM) m.Msg == WM.WM_NCLBUTTONUP) - { - OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); - } - } - - break; - - default: - base.WndProc(ref m); - break; - } - } - - /// Event handler that is called when 's event is fired. - /// Object from which this event originated. - /// Arguments associated with the event. - private void _parentForm_Activated(object sender, EventArgs e) - { - _active = true; - Render(); - } - - /// Event handler that is called when 's event is fired. - /// Object from which this event originated. - /// Arguments associated with the event. - private void _parentForm_Deactivate(object sender, EventArgs e) - { - _active = false; - Render(); - } - - /// Event handler that is called when 's event is fired. - /// Object from which this event originated. - /// Arguments associated with the event. - private void _parentForm_Disposed(object sender, EventArgs e) - { - } - - /// - /// Contains information on mouse events captured by and processed by - /// . - /// - protected class MouseEvent - { - /// Code for the event. - // ReSharper disable InconsistentNaming - public int nCode - { - get; - set; - } - - /// wParam value associated with the event. - public IntPtr wParam - { - get; - set; - } - - /// lParam value associated with the event. - public IntPtr lParam - { - get; - set; - } - - // ReSharper restore InconsistentNaming - - /// Data associated with the mouse event. - public MSLLHOOKSTRUCT? MouseData - { - get; - set; - } - } - - private void InitializeComponent() - { - this.SuspendLayout(); - // - // TitleBarTabsOverlay - // - this.ClientSize = new System.Drawing.Size(284, 261); - this.Name = "TitleBarTabsOverlay"; - this.ResumeLayout(false); - - } - } + /// Flag indicating whether or not has been installed as a hook. + protected static bool _hookProcInstalled; + + /// Semaphore to control access to . + protected static object _tornTabLock = new object(); + + protected static uint _doubleClickInterval = User32.GetDoubleClickTime(); + + /// Flag indicating whether or not the underlying window is active. + protected bool _active = false; + + /// Flag indicating whether we should draw the titlebar background (i.e. we are in a non-Aero environment). + protected bool _aeroEnabled = false; + + /// + /// When a tab is torn from the window, this is where we store the areas on all open windows where tabs can be dropped to combine the tab with that + /// window. + /// + protected Tuple[] _dropAreas = null; + + /// Pointer to the low-level mouse hook callback (). + protected IntPtr _hookId; + + /// Delegate of ; declared as a member variable to keep it from being garbage collected. + protected HOOKPROC _hookproc = null; + + /// Index of the tab, if any, whose close button is being hovered over. + protected int _isOverCloseButtonForTab = -1; + + protected bool _isOverSizingBox = false; + + protected bool _isOverAddButton = true; + + /// Queue of mouse events reported by that need to be processed. + protected BlockingCollection _mouseEvents = new BlockingCollection(); + + /// Consumer thread for processing events in . + protected Thread _mouseEventsThread = null; + + /// Parent form for the overlay. + protected TitleBarTabs _parentForm; + + protected long _lastLeftButtonClickTicks = 0; + + protected bool _firstClick = true; + protected Point _lastLeftButtonClickPoint = Point.Empty; + + protected bool _parentFormClosing = false; + + /// Blank default constructor to ensure that the overlays are only initialized through . + protected TitleBarTabsOverlay() + { + } + + /// Creates the overlay window and attaches it to . + /// Parent form that the overlay should be rendered on top of. + protected TitleBarTabsOverlay(TitleBarTabs parentForm) + { + _parentForm = parentForm; + + // We don't want this window visible in the taskbar + ShowInTaskbar = false; + FormBorderStyle = FormBorderStyle.SizableToolWindow; + MinimizeBox = false; + MaximizeBox = false; + _aeroEnabled = _parentForm.IsCompositionEnabled; + + Show(_parentForm); + AttachHandlers(); + + showTooltipTimer = new Timer + { + AutoReset = false + }; + + showTooltipTimer.Elapsed += ShowTooltipTimer_Elapsed; + } + + /// + /// Makes sure that the window is created with an flag set so that it can be alpha-blended properly with the content ( + /// ) underneath the overlay. + /// + protected override CreateParams CreateParams + { + get + { + CreateParams createParams = base.CreateParams; + createParams.ExStyle |= (int) (WS_EX.WS_EX_LAYERED | WS_EX.WS_EX_NOACTIVATE); + + return createParams; + } + } + + /// Primary color for the titlebar background. + protected Color TitleBarColor + { + get + { + if (Application.RenderWithVisualStyles && Environment.OSVersion.Version.Major >= 6) + { + return _active + ? SystemColors.GradientActiveCaption + : SystemColors.GradientInactiveCaption; + } + + return _active + ? SystemColors.ActiveCaption + : SystemColors.InactiveCaption; + } + } + + /// Type of theme being used by the OS to render the desktop. + protected DisplayType DisplayType + { + get + { + if (_aeroEnabled) + { + return DisplayType.Aero; + } + + if (Application.RenderWithVisualStyles && Environment.OSVersion.Version.Major >= 6) + { + return DisplayType.Basic; + } + + return DisplayType.Classic; + } + } + + /// Gradient color for the titlebar background. + protected Color TitleBarGradientColor + { + get + { + return _active + ? SystemInformation.IsTitleBarGradientEnabled + ? SystemColors.GradientActiveCaption + : SystemColors.ActiveCaption + : SystemInformation.IsTitleBarGradientEnabled + ? SystemColors.GradientInactiveCaption + : SystemColors.InactiveCaption; + } + } + + /// Screen area in which tabs can be dragged to and dropped for this window. + public Rectangle TabDropArea + { + get + { + RECT windowRectangle; + User32.GetWindowRect(_parentForm.Handle, out windowRectangle); + + return new Rectangle( + windowRectangle.left + SystemInformation.HorizontalResizeBorderThickness, windowRectangle.top + SystemInformation.VerticalResizeBorderThickness, + ClientRectangle.Width, _parentForm.NonClientAreaHeight - SystemInformation.VerticalResizeBorderThickness); + } + } + + /// Retrieves or creates the overlay for . + /// Parent form that we are to create the overlay for. + /// Newly-created or previously existing overlay for . + public static TitleBarTabsOverlay GetInstance(TitleBarTabs parentForm) + { + if (!_parents.ContainsKey(parentForm)) + { + _parents.Add(parentForm, new TitleBarTabsOverlay(parentForm)); + } + + return _parents[parentForm]; + } + + /// + /// Attaches the various event handlers to so that the overlay is moved in synchronization to + /// . + /// + protected void AttachHandlers() + { + FormClosing += TitleBarTabsOverlay_FormClosing; + + _parentForm.FormClosing += _parentForm_FormClosing; + _parentForm.Disposed += _parentForm_Disposed; + _parentForm.Deactivate += _parentForm_Deactivate; + _parentForm.Activated += _parentForm_Activated; + _parentForm.SizeChanged += _parentForm_Refresh; + _parentForm.Shown += _parentForm_Refresh; + _parentForm.VisibleChanged += _parentForm_Refresh; + _parentForm.Move += _parentForm_Refresh; + _parentForm.SystemColorsChanged += _parentForm_SystemColorsChanged; + + if (_hookproc == null) + { + // Spin up a consumer thread to process mouse events from _mouseEvents + _mouseEventsThread = new Thread(InterpretMouseEvents) + { + Name = "Low level mouse hooks processing thread" + }; + _mouseEventsThread.Priority = ThreadPriority.Highest; + _mouseEventsThread.Start(); + + using (Process curProcess = Process.GetCurrentProcess()) + { + using (ProcessModule curModule = curProcess.MainModule) + { + // Install the low level mouse hook that will put events into _mouseEvents + _hookproc = MouseHookCallback; + _hookId = User32.SetWindowsHookEx(WH.WH_MOUSE_LL, _hookproc, Kernel32.GetModuleHandle(curModule.ModuleName), 0); + } + } + } + } + + private void TitleBarTabsOverlay_FormClosing(object sender, FormClosingEventArgs e) + { + if (!_parentFormClosing) + { + e.Cancel = true; + _parentFormClosing = true; + _parentForm.Close(); + } + } + + /// + /// Event handler that is called when is in the process of closing. This uninstalls from the low- + /// level hooks list and stops the consumer thread that processes those events. + /// + /// Object from which this event originated, in this case. + /// Arguments associated with this event. + private void _parentForm_FormClosing(object sender, CancelEventArgs e) + { + if (e.Cancel) + { + _parentFormClosing = false; + return; + } + + TitleBarTabs form = (TitleBarTabs) sender; + + if (form == null) + { + return; + } + + _parentFormClosing = true; + + if (_parents.ContainsKey(form)) + { + _parents.Remove(form); + } + + // Uninstall the mouse hook + User32.UnhookWindowsHookEx(_hookId); + + // Kill the mouse events processing thread + _mouseEvents.CompleteAdding(); + _mouseEventsThread.Abort(); + } + + private void HideTooltip() + { + showTooltipTimer.Stop(); + + if (_parentForm.InvokeRequired) + { + _parentForm.Invoke(new Action(() => + { + _parentForm.Tooltip.Hide(_parentForm); + })); + } + + else + { + _parentForm.Tooltip.Hide(_parentForm); + } + } + + private void ShowTooltip(TitleBarTabs tabsForm, string caption) + { + Point tooltipLocation = new Point(Cursor.Position.X + 7, Cursor.Position.Y + 55); + tabsForm.Tooltip.Show(caption, tabsForm, tabsForm.PointToClient(tooltipLocation), tabsForm.Tooltip.AutoPopDelay); + } + + private void ShowTooltipTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + if (!_parentForm.ShowTooltips) + { + return; + } + + Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); + TitleBarTab hoverTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); + + if (hoverTab != null) + { + TitleBarTabs hoverTabForm = hoverTab.Parent; + + if (hoverTabForm.InvokeRequired) + { + hoverTabForm.Invoke(new Action(() => + { + ShowTooltip(hoverTabForm, hoverTab.Caption); + })); + } + + else + { + ShowTooltip(hoverTabForm, hoverTab.Caption); + } + } + } + + private void StartTooltipTimer() + { + if (!_parentForm.ShowTooltips) + { + return; + } + + Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); + TitleBarTab hoverTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); + + if (hoverTab != null) + { + showTooltipTimer.Interval = hoverTab.Parent.Tooltip.AutomaticDelay; + showTooltipTimer.Start(); + } + } + + /// Consumer method that processes mouse events in that are recorded by . + protected void InterpretMouseEvents() + { + foreach (MouseEvent mouseEvent in _mouseEvents.GetConsumingEnumerable()) + { + int nCode = mouseEvent.nCode; + IntPtr wParam = mouseEvent.wParam; + MSLLHOOKSTRUCT? hookStruct = mouseEvent.MouseData; + + if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) + { + HideTooltip(); + + // ReSharper disable PossibleInvalidOperationException + Point cursorPosition = new Point(hookStruct.Value.pt.x, hookStruct.Value.pt.y); + // ReSharper restore PossibleInvalidOperationException + bool reRender = false; + + if (_tornTab != null && _dropAreas != null) + { + // ReSharper disable ForCanBeConvertedToForeach + for (int i = 0; i < _dropAreas.Length; i++) + // ReSharper restore ForCanBeConvertedToForeach + { + // If the cursor is within the drop area, combine the tab for the window that belongs to that drop area + if (_dropAreas[i].Item2.Contains(cursorPosition)) + { + TitleBarTab tabToCombine = null; + + lock (_tornTabLock) + { + if (_tornTab != null) + { + tabToCombine = _tornTab; + _tornTab = null; + } + } + + if (tabToCombine != null) + { + int i1 = i; + + // In all cases where we need to affect the UI, we call Invoke so that those changes are made on the main UI thread since + // we are on a separate processing thread in this case + Invoke( + new Action( + () => + { + _dropAreas[i1].Item1.TabRenderer.CombineTab(tabToCombine, cursorPosition); + + tabToCombine = null; + _tornTabForm.Close(); + _tornTabForm = null; + + if (_parentForm.Tabs.Count == 0) + { + _parentForm.Close(); + } + })); + } + } + } + } + + else if (!_parentForm.TabRenderer.IsTabRepositioning) + { + StartTooltipTimer(); + + Point relativeCursorPosition = GetRelativeCursorPosition(cursorPosition); + + // If we were over a close button previously, check to see if the cursor is still over that tab's + // close button; if not, re-render + if (_isOverCloseButtonForTab != -1 && + (_isOverCloseButtonForTab >= _parentForm.Tabs.Count || + !_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[_isOverCloseButtonForTab], relativeCursorPosition))) + { + reRender = true; + _isOverCloseButtonForTab = -1; + } + + // Otherwise, see if any tabs' close button is being hovered over + else + { + // ReSharper disable ForCanBeConvertedToForeach + for (int i = 0; i < _parentForm.Tabs.Count; i++) + // ReSharper restore ForCanBeConvertedToForeach + { + if (_parentForm.TabRenderer.IsOverCloseButton(_parentForm.Tabs[i], relativeCursorPosition)) + { + _isOverCloseButtonForTab = i; + reRender = true; + + break; + } + } + } + + if (_isOverCloseButtonForTab == -1 && _parentForm.TabRenderer.RendersEntireTitleBar) + { + if (_parentForm.TabRenderer.IsOverSizingBox(relativeCursorPosition)) + { + _isOverSizingBox = true; + reRender = true; + } + + else if (_isOverSizingBox) + { + _isOverSizingBox = false; + reRender = true; + } + } + + if (_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition)) + { + _isOverAddButton = true; + reRender = true; + } + + else if (_isOverAddButton) + { + _isOverAddButton = false; + reRender = true; + } + } + + else + { + Invoke( + new Action( + () => + { + _wasDragging = true; + + // When determining if a tab has been torn from the window while dragging, we take the drop area for this window and inflate it by the + // TabTearDragDistance setting + Rectangle dragArea = TabDropArea; + dragArea.Inflate(_parentForm.TabRenderer.TabTearDragDistance, _parentForm.TabRenderer.TabTearDragDistance); + + // If the cursor is outside the tear area, tear it away from the current window + if (!dragArea.Contains(cursorPosition) && _tornTab == null) + { + lock (_tornTabLock) + { + if (_tornTab == null) + { + _parentForm.TabRenderer.IsTabRepositioning = false; + + // Clear the event handler subscriptions from the tab and then create a thumbnail representation of it to use when dragging + _tornTab = _parentForm.SelectedTab; + _tornTab.ClearSubscriptions(); + _tornTabForm = new TornTabForm(_tornTab, _parentForm.TabRenderer); + } + } + + if (_tornTab != null) + { + _parentForm.SelectedTabIndex = (_parentForm.SelectedTabIndex == _parentForm.Tabs.Count - 1 + ? _parentForm.SelectedTabIndex - 1 + : _parentForm.SelectedTabIndex + 1); + _parentForm.Tabs.Remove(_tornTab); + + // If this tab was the only tab in the window, hide the parent window + if (_parentForm.Tabs.Count == 0) + { + _parentForm.Hide(); + } + + _tornTabForm.Show(); + _dropAreas = (from window in _parentForm.ApplicationContext.OpenWindows.Where(w => w.Tabs.Count > 0) + select new Tuple(window, window.TabDropArea)).ToArray(); + } + } + })); + } + + Invoke(new Action(() => OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, cursorPosition.X, cursorPosition.Y, 0)))); + + if (_parentForm.TabRenderer.IsTabRepositioning) + { + reRender = true; + } + + if (reRender) + { + Invoke(new Action(() => Render(cursorPosition, true))); + } + } + + else if (nCode >= 0 && (int) WM.WM_LBUTTONDBLCLK == (int) wParam) + { + if (DesktopBounds.Contains(_lastLeftButtonClickPoint)) + { + Invoke(new Action(() => + { + if (_parentForm.Focused || _parentForm.ContainsFocus) + { + _parentForm.WindowState = _parentForm.WindowState == FormWindowState.Maximized + ? FormWindowState.Normal + : FormWindowState.Maximized; + } + })); + } + } + + else if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) + { + _firstClick = false; + _wasDragging = false; + } + + else if (nCode >= 0 && (int) WM.WM_LBUTTONUP == (int) wParam) + { + // If we released the mouse button while we were dragging a torn tab, put that tab into a new window + if (_tornTab != null) + { + TitleBarTab tabToRelease = null; + + lock (_tornTabLock) + { + if (_tornTab != null) + { + tabToRelease = _tornTab; + _tornTab = null; + } + } + + if (tabToRelease != null) + { + Invoke( + new Action( + () => + { + TitleBarTabs newWindow = (TitleBarTabs) Activator.CreateInstance(_parentForm.GetType()); + + // Set the initial window position and state properly + if (newWindow.WindowState == FormWindowState.Maximized) + { + Screen screen = Screen.AllScreens.First(s => s.WorkingArea.Contains(Cursor.Position)); + + newWindow.StartPosition = FormStartPosition.Manual; + newWindow.WindowState = FormWindowState.Normal; + newWindow.Left = screen.WorkingArea.Left; + newWindow.Top = screen.WorkingArea.Top; + newWindow.Width = screen.WorkingArea.Width; + newWindow.Height = screen.WorkingArea.Height; + } + + else + { + newWindow.Left = Cursor.Position.X; + newWindow.Top = Cursor.Position.Y; + } + + tabToRelease.Parent = newWindow; + _parentForm.ApplicationContext.OpenWindow(newWindow); + + newWindow.Show(); + newWindow.Tabs.Add(tabToRelease); + newWindow.SelectedTabIndex = 0; + newWindow.ResizeTabContents(); + + _tornTabForm.Close(); + _tornTabForm = null; + + if (_parentForm.Tabs.Count == 0) + { + _parentForm.Close(); + } + })); + } + } + + Invoke(new Action(() => OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)))); + } + } + } + + /// Hook callback to process messages to highlight/un-highlight the close button on each tab. + /// The message being received. + /// Additional information about the message. + /// Additional information about the message. + /// A zero value if the procedure processes the message; a nonzero value if the procedure ignores the message. + protected IntPtr MouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam) + { + MouseEvent mouseEvent = new MouseEvent + { + nCode = nCode, + wParam = wParam, + lParam = lParam + }; + + if (nCode >= 0 && (int) WM.WM_MOUSEMOVE == (int) wParam) + { + mouseEvent.MouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT)); + } + + _mouseEvents.Add(mouseEvent); + + if (nCode >= 0 && (int) WM.WM_LBUTTONDOWN == (int) wParam) + { + long currentTicks = DateTime.Now.Ticks; + + var mouseData = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); + var currentPoint = new Point(mouseData.pt.x, mouseData.pt.y); + var isSameClickPositionAsFirstClick = _lastLeftButtonClickPoint == currentPoint; + if (isSameClickPositionAsFirstClick && _lastLeftButtonClickTicks > 0 && currentTicks - _lastLeftButtonClickTicks < _doubleClickInterval * 10000) + { + _mouseEvents.Add(new MouseEvent + { + nCode = nCode, + wParam = new IntPtr((int) WM.WM_LBUTTONDBLCLK), + lParam = lParam + }); + } + + _lastLeftButtonClickTicks = currentTicks; + _lastLeftButtonClickPoint = currentPoint; + } + + return User32.CallNextHookEx(_hookId, nCode, wParam, lParam); + } + + /// Draws the titlebar background behind the tabs if Aero glass is not enabled. + /// Graphics context with which to draw the background. + protected virtual void DrawTitleBarBackground(Graphics graphics) + { + if (DisplayType == DisplayType.Aero) + { + return; + } + + Rectangle fillArea; + + if (DisplayType == DisplayType.Basic) + { + fillArea = new Rectangle( + new Point( + 1, Top == 0 + ? SystemInformation.CaptionHeight - 1 + : (SystemInformation.CaptionHeight + SystemInformation.VerticalResizeBorderThickness) - (Top - _parentForm.Top) - 1), + new Size(Width - 2, _parentForm.Padding.Top)); + } + + else + { + fillArea = new Rectangle(new Point(1, 0), new Size(Width - 2, Height - 1)); + } + + if (fillArea.Height <= 0) + { + return; + } + + // Adjust the margin so that the gradient stops immediately prior to the control box in the titlebar + int rightMargin = 3; + + if (_parentForm.ControlBox && _parentForm.MinimizeBox) + { + rightMargin += SystemInformation.CaptionButtonSize.Width; + } + + if (_parentForm.ControlBox && _parentForm.MaximizeBox) + { + rightMargin += SystemInformation.CaptionButtonSize.Width; + } + + if (_parentForm.ControlBox) + { + rightMargin += SystemInformation.CaptionButtonSize.Width; + } + + LinearGradientBrush gradient = new LinearGradientBrush( + new Point(24, 0), new Point(fillArea.Width - rightMargin + 1, 0), TitleBarColor, TitleBarGradientColor); + + using (BufferedGraphics bufferedGraphics = BufferedGraphicsManager.Current.Allocate(graphics, fillArea)) + { + bufferedGraphics.Graphics.FillRectangle(new SolidBrush(TitleBarColor), fillArea); + bufferedGraphics.Graphics.FillRectangle( + new SolidBrush(TitleBarGradientColor), + new Rectangle(new Point(fillArea.Location.X + fillArea.Width - rightMargin, fillArea.Location.Y), new Size(rightMargin, fillArea.Height))); + bufferedGraphics.Graphics.FillRectangle( + gradient, new Rectangle(fillArea.Location, new Size(fillArea.Width - rightMargin, fillArea.Height))); + bufferedGraphics.Graphics.FillRectangle(new SolidBrush(TitleBarColor), new Rectangle(fillArea.Location, new Size(24, fillArea.Height))); + + bufferedGraphics.Render(graphics); + } + } + + /// + /// Event handler that is called when 's event is fired which re-renders + /// the tabs. + /// + /// Object from which the event originated. + /// Arguments associated with the event. + private void _parentForm_SystemColorsChanged(object sender, EventArgs e) + { + _aeroEnabled = _parentForm.IsCompositionEnabled; + OnPosition(); + } + + /// + /// Event handler that is called when 's , , or + /// events are fired which re-renders the tabs. + /// + /// Object from which the event originated. + /// Arguments associated with the event. + private void _parentForm_Refresh(object sender, EventArgs e) + { + if (_parentForm.WindowState == FormWindowState.Minimized) + { + Visible = false; + } + + else + { + OnPosition(); + } + } + + /// Sets the position of the overlay window to match that of so that it moves in tandem with it. + protected void OnPosition() + { + if (!IsDisposed) + { + // 92 is SM_CXPADDEDBORDER, which returns the amount of extra border padding around captioned windows + int borderPadding = DisplayType == DisplayType.Classic + ? 0 + : User32.GetSystemMetrics(92); + + // If the form is in a non-maximized state, we position the tabs below the minimize/maximize/close + // buttons + Top = _parentForm.Top + (DisplayType == DisplayType.Classic + ? SystemInformation.VerticalResizeBorderThickness + : _parentForm.WindowState == FormWindowState.Maximized + ? SystemInformation.VerticalResizeBorderThickness + borderPadding + : _parentForm.TabRenderer.RendersEntireTitleBar + ? _parentForm.TabRenderer.IsWindows10 + ? SystemInformation.BorderSize.Width + : 0 + : borderPadding); + Left = _parentForm.Left + SystemInformation.HorizontalResizeBorderThickness - (_parentForm.TabRenderer.IsWindows10 ? 0 : SystemInformation.BorderSize.Width) + borderPadding; + Width = _parentForm.Width - ((SystemInformation.VerticalResizeBorderThickness + borderPadding) * 2) + (_parentForm.TabRenderer.IsWindows10 ? 0 : (SystemInformation.BorderSize.Width * 2)); + Height = _parentForm.TabRenderer.TabHeight + (DisplayType == DisplayType.Classic && _parentForm.WindowState != FormWindowState.Maximized && !_parentForm.TabRenderer.RendersEntireTitleBar + ? SystemInformation.CaptionButtonSize.Height + : _parentForm.TabRenderer.IsWindows10 + ? -1 * SystemInformation.BorderSize.Width + : _parentForm.WindowState != FormWindowState.Maximized + ? borderPadding + : 0); + + Render(); + } + } + + /// + /// Renders the tabs and then calls to blend the tab content with the underlying window ( + /// ). + /// + /// Flag indicating whether a full render should be forced. + public void Render(bool forceRedraw = false) + { + Render(Cursor.Position, forceRedraw); + } + + /// + /// Renders the tabs and then calls to blend the tab content with the underlying window ( + /// ). + /// + /// Current position of the cursor. + /// Flag indicating whether a full render should be forced. + public void Render(Point cursorPosition, bool forceRedraw = false) + { + if (!IsDisposed && _parentForm.TabRenderer != null && _parentForm.WindowState != FormWindowState.Minimized && _parentForm.ClientRectangle.Width > 0) + { + cursorPosition = GetRelativeCursorPosition(cursorPosition); + + using (Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb)) + { + using (Graphics graphics = Graphics.FromImage(bitmap)) + { + DrawTitleBarBackground(graphics); + + // Since classic mode themes draw over the *entire* titlebar, not just the area immediately behind the tabs, we have to offset the tabs + // when rendering in the window + Point offset = _parentForm.WindowState != FormWindowState.Maximized && DisplayType == DisplayType.Classic && !_parentForm.TabRenderer.RendersEntireTitleBar + ? new Point(0, SystemInformation.CaptionButtonSize.Height) + : _parentForm.WindowState != FormWindowState.Maximized && !_parentForm.TabRenderer.RendersEntireTitleBar + ? new Point(0, SystemInformation.VerticalResizeBorderThickness - SystemInformation.BorderSize.Height) + : new Point(0, 0); + + // Render the tabs into the bitmap + _parentForm.TabRenderer.Render(_parentForm.Tabs, graphics, offset, cursorPosition, forceRedraw); + + // Cut out a hole in the background so that the control box on the underlying window can be shown + if (DisplayType == DisplayType.Classic && (_parentForm.ControlBox || _parentForm.MaximizeBox || _parentForm.MinimizeBox)) + { + int boxWidth = 0; + + if (_parentForm.ControlBox) + { + boxWidth += SystemInformation.CaptionButtonSize.Width; + } + + if (_parentForm.MinimizeBox) + { + boxWidth += SystemInformation.CaptionButtonSize.Width; + } + + if (_parentForm.MaximizeBox) + { + boxWidth += SystemInformation.CaptionButtonSize.Width; + } + + CompositingMode oldCompositingMode = graphics.CompositingMode; + + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.FillRectangle( + new SolidBrush(Color.Transparent), Width - boxWidth, 0, boxWidth, SystemInformation.CaptionButtonSize.Height); + graphics.CompositingMode = oldCompositingMode; + } + + IntPtr screenDc = User32.GetDC(IntPtr.Zero); + IntPtr memDc = Gdi32.CreateCompatibleDC(screenDc); + IntPtr oldBitmap = IntPtr.Zero; + IntPtr bitmapHandle = IntPtr.Zero; + + try + { + // Copy the contents of the bitmap into memDc + bitmapHandle = bitmap.GetHbitmap(Color.FromArgb(0)); + oldBitmap = Gdi32.SelectObject(memDc, bitmapHandle); + + SIZE size = new SIZE + { + cx = bitmap.Width, + cy = bitmap.Height + }; + + POINT pointSource = new POINT + { + x = 0, + y = 0 + }; + POINT topPos = new POINT + { + x = Left, + y = Top + }; + BLENDFUNCTION blend = new BLENDFUNCTION + { + // We want to blend the bitmap's content with the screen content under it + BlendOp = Convert.ToByte((int) AC.AC_SRC_OVER), + BlendFlags = 0, + // Follow the parent forms' opacity level + SourceConstantAlpha = (byte)(_parentForm.Opacity * 255), + // We use the bitmap's alpha channel for blending instead of a pre-defined transparency key + AlphaFormat = Convert.ToByte((int) AC.AC_SRC_ALPHA) + }; + + // Blend the tab content with the underlying content + if (!User32.UpdateLayeredWindow( + Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, ULW.ULW_ALPHA)) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error, "Error while calling UpdateLayeredWindow()."); + } + } + + // Clean up after ourselves + finally + { + User32.ReleaseDC(IntPtr.Zero, screenDc); + + if (bitmapHandle != IntPtr.Zero) + { + Gdi32.SelectObject(memDc, oldBitmap); + Gdi32.DeleteObject(bitmapHandle); + } + + Gdi32.DeleteDC(memDc); + } + } + } + } + } + + /// Gets the relative location of the cursor within the overlay. + /// Cursor position that represents the absolute position of the cursor on the screen. + /// The relative location of the cursor within the overlay. + public Point GetRelativeCursorPosition(Point cursorPosition) + { + return new Point(cursorPosition.X - Location.X, cursorPosition.Y - Location.Y); + } + + /// Overrides the message pump for the window so that we can respond to click events on the tabs themselves. + /// Message received by the pump. + protected override void WndProc(ref Message m) + { + switch ((WM) m.Msg) + { + case WM.WM_SYSCOMMAND: + if (m.WParam == new IntPtr(0xF030) || m.WParam == new IntPtr(0xF120) || m.WParam == new IntPtr(0xF020)) + { + _parentForm.ForwardMessage(ref m); + } + + else + { + base.WndProc(ref m); + } + + break; + + case WM.WM_NCLBUTTONDOWN: + case WM.WM_LBUTTONDOWN: + Point relativeCursorPosition = GetRelativeCursorPosition(Cursor.Position); + + // If we were over a tab, set the capture state for the window so that we'll actually receive a WM_LBUTTONUP message + if (_parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition) == null && + !_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition)) + { + _parentForm.ForwardMessage(ref m); + } + + else + { + // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released + TitleBarTab clickedTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition); + + if (clickedTab != null) + { + // If the user clicked the close button, remove the tab from the list + if (!_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition)) + { + _parentForm.ResizeTabContents(clickedTab); + _parentForm.SelectedTabIndex = _parentForm.Tabs.IndexOf(clickedTab); + + Render(); + } + + OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); + } + + _parentForm.Activate(); + } + + break; + + case WM.WM_LBUTTONDBLCLK: + _parentForm.ForwardMessage(ref m); + break; + + // We always return HTCAPTION for the hit test message so that the underlying window doesn't have its focus removed + case WM.WM_NCHITTEST: + m.Result = new IntPtr((int) _parentForm.TabRenderer.NonClientHitTest(m, GetRelativeCursorPosition(Cursor.Position))); + break; + + case WM.WM_LBUTTONUP: + case WM.WM_NCLBUTTONUP: + case WM.WM_MBUTTONUP: + case WM.WM_NCMBUTTONUP: + Point relativeCursorPosition2 = GetRelativeCursorPosition(Cursor.Position); + + if (_parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition2) == null && + !_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) + { + _parentForm.ForwardMessage(ref m); + } + + else + { + // When the user clicks a mouse button, save the tab that the user was over so we can respond properly when the mouse button is released + TitleBarTab clickedTab = _parentForm.TabRenderer.OverTab(_parentForm.Tabs, relativeCursorPosition2); + + if (clickedTab != null) + { + // If the user clicks the middle button/scroll wheel over a tab, close it + if ((WM) m.Msg == WM.WM_MBUTTONUP || (WM) m.Msg == WM.WM_NCMBUTTONUP) + { + clickedTab.Content.Close(); + Render(); + } + + else + { + // If the user clicked the close button, remove the tab from the list + if (_parentForm.TabRenderer.IsOverCloseButton(clickedTab, relativeCursorPosition2)) + { + clickedTab.Content.Close(); + Render(); + } + + else + { + _parentForm.OnTabClicked( + new TitleBarTabEventArgs + { + Tab = clickedTab, + TabIndex = _parentForm.SelectedTabIndex, + Action = TabControlAction.Selected, + WasDragging = _wasDragging + }); + } + } + } + + // Otherwise, if the user clicked the add button, call CreateTab to add a new tab to the list and select it + else if (_parentForm.TabRenderer.IsOverAddButton(relativeCursorPosition2)) + { + _parentForm.AddNewTab(); + } + + if ((WM) m.Msg == WM.WM_LBUTTONUP || (WM) m.Msg == WM.WM_NCLBUTTONUP) + { + OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, Cursor.Position.X, Cursor.Position.Y, 0)); + } + } + + break; + + default: + base.WndProc(ref m); + break; + } + } + + /// Event handler that is called when 's event is fired. + /// Object from which this event originated. + /// Arguments associated with the event. + private void _parentForm_Activated(object sender, EventArgs e) + { + _active = true; + Render(); + } + + /// Event handler that is called when 's event is fired. + /// Object from which this event originated. + /// Arguments associated with the event. + private void _parentForm_Deactivate(object sender, EventArgs e) + { + _active = false; + Render(); + } + + /// Event handler that is called when 's event is fired. + /// Object from which this event originated. + /// Arguments associated with the event. + private void _parentForm_Disposed(object sender, EventArgs e) + { + } + + /// + /// Contains information on mouse events captured by and processed by + /// . + /// + protected class MouseEvent + { + /// Code for the event. + // ReSharper disable InconsistentNaming + public int nCode + { + get; + set; + } + + /// wParam value associated with the event. + public IntPtr wParam + { + get; + set; + } + + /// lParam value associated with the event. + public IntPtr lParam + { + get; + set; + } + + // ReSharper restore InconsistentNaming + + /// Data associated with the mouse event. + public MSLLHOOKSTRUCT? MouseData + { + get; + set; + } + } + + private void InitializeComponent() + { + this.SuspendLayout(); + // + // TitleBarTabsOverlay + // + this.ClientSize = new System.Drawing.Size(284, 261); + this.Name = "TitleBarTabsOverlay"; + this.ResumeLayout(false); + + } + } } \ No newline at end of file