Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce dockable context menu to toolwindows #5203

Merged
merged 4 commits into from
Oct 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 99 additions & 2 deletions Rubberduck.Main/ComClientLibrary/UI/DockableWindowHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using Rubberduck.VBEditor;
using Rubberduck.VBEditor.WindowsApi;
Expand Down Expand Up @@ -145,7 +146,7 @@ private void RemoveChildControlsFromExposedControl()
public int /* IOleObject:: */ Close([In] uint dwSaveOption)
{
_logger.Log(LogLevel.Trace, "IOleObject::Close() called");
int hr = _userControl.IOleObject.Close(dwSaveOption);
var hr = _userControl.IOleObject.Close(dwSaveOption);

// IOleObject::SetClientSite is typically called with pClientSite = null just before calling IOleObject::Close()
// If it didn't, we release all host COM objects here instead,
Expand Down Expand Up @@ -386,7 +387,7 @@ public void AddUserControl(UserControl control, IntPtr vbeHwnd)

control.Dock = DockStyle.Fill;
_userControl.Controls.Add(control);

AdjustSize();
}

Expand Down Expand Up @@ -429,14 +430,79 @@ private void AdjustSize()
}
}

private static void ToggleDockable(IntPtr hWndVBE)
{
NativeMethods.SendMessage(hWndVBE, 0x1044, (IntPtr)0xB5, IntPtr.Zero);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: do these hex numbers have particular meaning? would constants be useful here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was more or less a copy-pasta from the original VBA solution from the linked issue, which is also documented (sort of) in the wiki:

https://github.com/rubberduck-vba/Rubberduck/wiki/VBE-Window-Messages

In particular, the 0x1044 is WM_USER + 3140 and the 0xB5 seems to be a subcode for the WM_USER + 3140 message.

My original thought was that this would be a one-trick pony and wouldn't be used anywhere, so it didn't need constants. However, if we want to wrap it in VbIdeMessage static class, I can get one started with it just and we can add other when we actually need them.

}

[ComVisible(false)]
public class ParentWindow : SubclassingWindow
{
private readonly Logger _logger = LogManager.GetCurrentClassLogger();

private const int MF_BYPOSITION = 0x400;

public event SubClassingWindowEventHandler CallBackEvent;
public delegate void SubClassingWindowEventHandler(object sender, SubClassingWindowEventArgs e);

private readonly IntPtr _vbeHwnd;

private IntPtr _containerHwnd;
private ToolWindowState _windowState;
private IntPtr _menuHandle;

private enum ToolWindowState
{
Unknown,
Docked,
Floating,
Undockable
}

private ToolWindowState GetWindowState(IntPtr containerHwnd)
{
var className = new StringBuilder(255);
if (NativeMethods.GetClassName(containerHwnd, className, className.Capacity) > 0)
{
switch (className.ToString())
{
case "wndclass_desked_gsk":
return ToolWindowState.Docked;
case "VBFloatingPalette":
return ToolWindowState.Floating;
case "DockingView":
return ToolWindowState.Undockable;
}
}

return ToolWindowState.Unknown;
}

private void DisplayUndockableContextMenu(IntPtr handle, IntPtr lParam)
{
if (_menuHandle == IntPtr.Zero)
{
_menuHandle = NativeMethods.CreatePopupMenu();

if (_menuHandle == IntPtr.Zero)
{
_logger.Warn("Cannot create menu handle");
return;
}

if (!NativeMethods.InsertMenu(_menuHandle, 0, MF_BYPOSITION, (UIntPtr)WM.RUBBERDUCK_UNDOCKABLE_CONTEXT_MENU, "Dockable" + char.MinValue))
{
_logger.Warn("Failed to insert a menu item for dockable command");
}
}

var param = new LParam {Value = (uint)lParam};
if (!NativeMethods.TrackPopupMenuEx(_menuHandle, 0x0, param.LowWord, param.HighWord, handle, IntPtr.Zero ))
{
_logger.Warn("Failed to set the context menu for undockable tool windows");
};
}

private void OnCallBackEvent(SubClassingWindowEventArgs e)
{
CallBackEvent?.Invoke(this, e);
Expand All @@ -452,6 +518,28 @@ public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr
{
switch ((uint)msg)
{
case (uint)WM.WINDOWPOSCHANGED:
var containerHwnd = GetParent(hWnd);
if (containerHwnd != _containerHwnd)
{
_containerHwnd = containerHwnd;
_windowState = GetWindowState(_containerHwnd);
}
break;
case (uint)WM.CONTEXTMENU:
if (_windowState == ToolWindowState.Undockable)
{
DisplayUndockableContextMenu(hWnd, lParam);
}
break;
case (uint)WM.COMMAND:
switch (wParam.ToInt32())
{
case (int)WM.RUBBERDUCK_UNDOCKABLE_CONTEXT_MENU:
ToggleDockable(_vbeHwnd);
break;
}
break;
case (uint)WM.SIZE:
var args = new SubClassingWindowEventArgs(lParam);
if (!_closing) OnCallBackEvent(args);
Expand All @@ -462,6 +550,15 @@ public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr
case (uint)WM.KILLFOCUS:
if (!_closing) User32.SendMessage(_vbeHwnd, WM.RUBBERDUCK_CHILD_FOCUS, Hwnd, IntPtr.Zero);
break;
case (uint)WM.DESTROY:
if (_menuHandle != IntPtr.Zero)
{
if (!NativeMethods.DestroyMenu(_menuHandle))
{
_logger.Fatal($"Failed to destroy the menu handle {_menuHandle}");
}
}
break;
}
return base.SubClassProc(hWnd, msg, wParam, lParam, uIdSubclass, dwRefData);
}
Expand Down
20 changes: 20 additions & 0 deletions Rubberduck.VBEEditor/WindowsApi/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ namespace Rubberduck.VBEditor.WindowsApi
/// </remarks>
public static class NativeMethods
{
[DllImport("user32.dll")]
public static extern IntPtr CreatePopupMenu();

[DllImport("user32.dll")]
public static extern bool TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y, IntPtr hwnd, IntPtr lptpm);

[DllImport("user32.dll", EntryPoint = "InsertMenuW", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool InsertMenu(IntPtr hMenu, uint wPosition, uint wFlags, UIntPtr wIDNewItem, [MarshalAs(UnmanagedType.LPWStr)]string lpNewItem);

[DllImport("user32.dll")]
public static extern bool DestroyMenu(IntPtr hMenu);

/// <summary> Sends a message to the OS. </summary>
///
/// <param name="hWnd"> The window handle. </param>
Expand Down Expand Up @@ -68,6 +80,14 @@ public enum PeekMessageRemoval : uint
[DllImport("user32", ExactSpelling = true, CharSet = CharSet.Unicode)]
public static extern int EnumChildWindows(IntPtr parentWindowHandle, EnumChildWindowsDelegate lpEnumFunction, IntPtr lParam);

/// <summary> Retrieves the name of the class to which the specified window belongs. </summary>
/// <param name="hWnd">A handle to the window and, indirectly, the class to which the window belongs.</param>
/// <param name="lpClassName">The class name string.</param>
/// <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>
/// <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>
[DllImport("user32.dll", EntryPoint = "GetClassNameW", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

/// <summary> Gets window text. </summary>
///
/// <param name="hWnd"> The window handle. </param>
Expand Down
4 changes: 4 additions & 0 deletions Rubberduck.VBEEditor/WindowsApi/WM.cs
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,10 @@ public enum WM : uint
/// Private message to signal focus RD shutdown. No parameters.
/// </summary>
RUBBERDUCK_SINKING = USER + 0x0D1E,
/// <summary>
/// Private message to indicate that the toolwindow is undockable and needs its special menu.
/// </summary>
RUBBERDUCK_UNDOCKABLE_CONTEXT_MENU = USER + 0x0F01
}

public static class WM_MAP
Expand Down