Skip to content

Commit 7419dc6

Browse files
committed
First working version of Dockable context menu and tracking the toolwindow's state
1 parent a4fac30 commit 7419dc6

File tree

3 files changed

+108
-30
lines changed

3 files changed

+108
-30
lines changed

Rubberduck.Main/ComClientLibrary/UI/DockableWindowHost.cs

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics.CodeAnalysis;
55
using System.Drawing;
66
using System.Runtime.InteropServices;
7+
using System.Text;
78
using System.Windows.Forms;
89
using Rubberduck.VBEditor;
910
using Rubberduck.VBEditor.WindowsApi;
@@ -429,46 +430,76 @@ private void AdjustSize()
429430
}
430431
}
431432

432-
private const int WM_SYSCOMMAND = 0x112;
433-
private const int MF_BYPOSITION = 0x400;
434-
private const int ToggleDockableMenuId = 1000;
433+
private static void ToggleDockable(IntPtr hWndVBE)
434+
{
435+
NativeMethods.SendMessage(hWndVBE, 0x1044, (IntPtr)0xB5, IntPtr.Zero);
436+
}
437+
438+
[ComVisible(false)]
439+
public class ParentWindow : SubclassingWindow
440+
{
441+
private const int MF_BYPOSITION = 0x400;
435442

436-
[DllImport("user32.dll")]
437-
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
438-
[DllImport("user32.dll")]
439-
private static extern bool InsertMenu(IntPtr hMenu, int wPosition, int wFlags, int wIDNewItem, string lpNewItem);
443+
public event SubClassingWindowEventHandler CallBackEvent;
444+
public delegate void SubClassingWindowEventHandler(object sender, SubClassingWindowEventArgs e);
440445

441-
[DllImport("user32.dll", CharSet = CharSet.Auto)]
442-
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
446+
private readonly IntPtr _vbeHwnd;
443447

444-
private static void InsertDockableToggle(IntPtr handle)
445-
{
446-
var menuHandle = GetSystemMenu(handle, false);
448+
private IntPtr _containerHwnd;
449+
private ToolWindowState _windowState;
450+
private IntPtr _menuHandle;
447451

448-
if (menuHandle == IntPtr.Zero)
452+
private enum ToolWindowState
449453
{
450-
Debug.Print("No menu handle");
451-
return;
454+
Unknown,
455+
Docked,
456+
Floating,
457+
Undockable
452458
}
453459

454-
if (!InsertMenu(menuHandle, 5, MF_BYPOSITION, ToggleDockableMenuId, "Dockable"))
460+
private ToolWindowState GetWindowState(IntPtr containerHwnd)
455461
{
456-
Debug.Print("Failed to insert a menu item for dockable command");
462+
var className = new StringBuilder(255);
463+
if (NativeMethods.GetClassName(containerHwnd, className, className.Capacity) > 0)
464+
{
465+
switch (className.ToString())
466+
{
467+
case "wndclass_desked_gsk":
468+
return ToolWindowState.Docked;
469+
case "VBFloatingPalette":
470+
return ToolWindowState.Floating;
471+
case "DockingView":
472+
return ToolWindowState.Undockable;
473+
}
474+
}
475+
476+
return ToolWindowState.Unknown;
457477
}
458-
}
459478

460-
private static void ToggleDockable(IntPtr hWndVBE)
461-
{
462-
SendMessage(hWndVBE, 0x1044, 0xB5, IntPtr.Zero);
463-
}
479+
private void DisplayUndockableContextMenu(IntPtr handle, IntPtr lParam)
480+
{
481+
if (_menuHandle == IntPtr.Zero)
482+
{
483+
_menuHandle = NativeMethods.CreatePopupMenu();
464484

465-
[ComVisible(false)]
466-
public class ParentWindow : SubclassingWindow
467-
{
468-
public event SubClassingWindowEventHandler CallBackEvent;
469-
public delegate void SubClassingWindowEventHandler(object sender, SubClassingWindowEventArgs e);
485+
if (_menuHandle == IntPtr.Zero)
486+
{
487+
Debug.Print("Cannot create menu handle");
488+
return;
489+
}
470490

471-
private readonly IntPtr _vbeHwnd;
491+
if (!NativeMethods.InsertMenu(_menuHandle, 0, MF_BYPOSITION, (UIntPtr)WM.RUBBERDUCK_UNDOCKABLE_CONTEXT_MENU, "Dockable" + char.MinValue))
492+
{
493+
Debug.Print("Failed to insert a menu item for dockable command");
494+
}
495+
}
496+
497+
var param = new LParam {Value = (uint)lParam};
498+
if (!NativeMethods.TrackPopupMenuEx(_menuHandle, 0x0, param.LowWord, param.HighWord, handle, IntPtr.Zero ))
499+
{
500+
Debug.Print("Failed to set the context menu for undockable tool windows");
501+
};
502+
}
472503

473504
private void OnCallBackEvent(SubClassingWindowEventArgs e)
474505
{
@@ -485,10 +516,24 @@ public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr
485516
{
486517
switch ((uint)msg)
487518
{
488-
case (uint)WM.SYSCOMMAND:
519+
case (uint)WM.WINDOWPOSCHANGED:
520+
var containerHwnd = GetParent(hWnd);
521+
if (containerHwnd != _containerHwnd)
522+
{
523+
_containerHwnd = containerHwnd;
524+
_windowState = GetWindowState(_containerHwnd);
525+
}
526+
break;
527+
case (uint)WM.CONTEXTMENU:
528+
if (_windowState == ToolWindowState.Undockable)
529+
{
530+
DisplayUndockableContextMenu(hWnd, lParam);
531+
}
532+
break;
533+
case (uint)WM.COMMAND:
489534
switch (wParam.ToInt32())
490535
{
491-
case ToggleDockableMenuId:
536+
case (int)WM.RUBBERDUCK_UNDOCKABLE_CONTEXT_MENU:
492537
ToggleDockable(_vbeHwnd);
493538
break;
494539
}
@@ -503,6 +548,15 @@ public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr
503548
case (uint)WM.KILLFOCUS:
504549
if (!_closing) User32.SendMessage(_vbeHwnd, WM.RUBBERDUCK_CHILD_FOCUS, Hwnd, IntPtr.Zero);
505550
break;
551+
case (uint)WM.DESTROY:
552+
if (_menuHandle != IntPtr.Zero)
553+
{
554+
if (!NativeMethods.DestroyMenu(_menuHandle))
555+
{
556+
Debug.Print($"Failed to destroy the menu handle {_menuHandle}");
557+
}
558+
}
559+
break;
506560
}
507561
return base.SubClassProc(hWnd, msg, wParam, lParam, uIdSubclass, dwRefData);
508562
}

Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ namespace Rubberduck.VBEditor.WindowsApi
1313
/// </remarks>
1414
public static class NativeMethods
1515
{
16+
[DllImport("user32.dll")]
17+
public static extern IntPtr CreatePopupMenu();
18+
19+
[DllImport("user32.dll")]
20+
public static extern bool TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y, IntPtr hwnd, IntPtr lptpm);
21+
22+
[DllImport("user32.dll", EntryPoint = "InsertMenuW", SetLastError = true, CharSet = CharSet.Unicode)]
23+
public static extern bool InsertMenu(IntPtr hMenu, uint wPosition, uint wFlags, UIntPtr wIDNewItem, [MarshalAs(UnmanagedType.LPWStr)]string lpNewItem);
24+
25+
[DllImport("user32.dll")]
26+
public static extern bool DestroyMenu(IntPtr hMenu);
27+
1628
/// <summary> Sends a message to the OS. </summary>
1729
///
1830
/// <param name="hWnd"> The window handle. </param>
@@ -68,6 +80,14 @@ public enum PeekMessageRemoval : uint
6880
[DllImport("user32", ExactSpelling = true, CharSet = CharSet.Unicode)]
6981
public static extern int EnumChildWindows(IntPtr parentWindowHandle, EnumChildWindowsDelegate lpEnumFunction, IntPtr lParam);
7082

83+
/// <summary> Retrieves the name of the class to which the specified window belongs. </summary>
84+
/// <param name="hWnd">A handle to the window and, indirectly, the class to which the window belongs.</param>
85+
/// <param name="lpClassName">The class name string.</param>
86+
/// <param name="nMaxCount">The length of the <see cref="lpClassName" /> buffer, in characters. The buffer must be large enough to include the terminating null character; otherwise, the class name string is truncated to <see cref="nMaxCount" /> characters.</param>
87+
/// <returns>If the function succeeds, the return value is the number of characters copied to the buffer, not including the terminating null character. If the function fails, the return value is zero.To get extended error information, call GetLastError.</returns>
88+
[DllImport("user32.dll", EntryPoint = "GetClassNameW", SetLastError = true, CharSet = CharSet.Unicode)]
89+
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
90+
7191
/// <summary> Gets window text. </summary>
7292
///
7393
/// <param name="hWnd"> The window handle. </param>

Rubberduck.VBEEditor/WindowsApi/WM.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,10 @@ public enum WM : uint
940940
/// Private message to signal focus RD shutdown. No parameters.
941941
/// </summary>
942942
RUBBERDUCK_SINKING = USER + 0x0D1E,
943+
/// <summary>
944+
/// Private message to indicate that the toolwindow is undockable and needs its special menu.
945+
/// </summary>
946+
RUBBERDUCK_UNDOCKABLE_CONTEXT_MENU = USER + 0x0F01
943947
}
944948

945949
#if (DEBUG && (THIRSTY_DUCK || THIRSTY_DUCK_WM))

0 commit comments

Comments
 (0)