Skip to content

Instantly share code, notes, and snippets.

@jankurianski
Last active June 12, 2023 15:32
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jankurianski/2163d7928d3d6f839e47 to your computer and use it in GitHub Desktop.
Save jankurianski/2163d7928d3d6f839e47 to your computer and use it in GitHub Desktop.
Hack to close ToolStrip dropdown when ChromiumWebBrowser is focused
ChromeWidgetMessageInterceptor chromeWidgetMessageInterceptor;
private void OnIsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs args)
{
if (args.IsBrowserInitialized)
{
SetupChromeWidgetMessageInterceptor();
}
}
void SetupChromeWidgetMessageInterceptor()
{
Task.Factory.StartNew(() =>
{
try {
var browser = (ChromiumWebBrowser)Browser;
bool foundWidget = false;
while (!foundWidget)
{
browser.Invoke((Action)(() =>
{
IntPtr chromeWidgetHostHandle;
if (ChromeWidgetHandleFinder.TryFindHandle(browser, out chromeWidgetHostHandle))
{
foundWidget = true;
chromeWidgetMessageInterceptor = new ChromeWidgetMessageInterceptor(chromeWidgetHostHandle);
}
else
{
// Chrome hasn't yet set up its message-loop window.
Thread.Sleep(10);
}
}));
}
}
catch
{
// Errors are likely to occur if browser is disposed, and no good way to check from another thread
}
});
}
class ChromeWidgetMessageInterceptor : NativeWindow
{
public ChromeWidgetMessageInterceptor(IntPtr chromeWidgetHostHandle)
{
AssignHandle(chromeWidgetHostHandle);
}
const int WM_MOUSEACTIVATE = 0x0021;
const int WM_NCLBUTTONDOWN = 0x00A1;
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_MOUSEACTIVATE)
{
// The default processing of WM_MOUSEACTIVATE results in MA_NOACTIVATE,
// and the subsequent mouse click is eaten by Chrome.
// This means any .NET ToolStrip or ContextMenuStrip does not get closed.
// By posting a WM_NCLBUTTONDOWN message to a harmless co-ordinate of the
// top-level window, we rely on the ToolStripManager's message handling
// to close any open dropdowns:
// http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/ToolStripManager.cs,1249
var topLevelWindowHandle = m.WParam;
IntPtr lParam = IntPtr.Zero;
PostMessage(topLevelWindowHandle, WM_NCLBUTTONDOWN, IntPtr.Zero, lParam);
}
}
}
public class ChromeWidgetHandleFinder
{
private delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr lParam);
private readonly IntPtr _MainHandle;
string seekClassName;
IntPtr descendantFound;
private ChromeWidgetHandleFinder(IntPtr handle)
{
this._MainHandle = handle;
}
private IntPtr FindDescendantByClassName(string className)
{
descendantFound = IntPtr.Zero;
seekClassName = className;
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(this._MainHandle, childProc, IntPtr.Zero);
return descendantFound;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
private bool EnumWindow(IntPtr hWnd, IntPtr lParam)
{
StringBuilder buffer = new StringBuilder(128);
GetClassName(hWnd, buffer, buffer.Capacity);
if (buffer.ToString() == seekClassName)
{
descendantFound = hWnd;
return false;
}
return true;
}
/// <summary>
/// Chrome's message-loop Window isn't created synchronously, so this may not find it.
/// If so, you need to wait and try again later.
/// </summary>
public static bool TryFindHandle(ChromiumWebBrowser browser, out IntPtr chromeWidgetHostHandle)
{
var browserHandle = browser.Handle;
var windowHandleInfo = new ChromeWidgetHandleFinder(browserHandle);
const string chromeWidgetHostClassName = "Chrome_RenderWidgetHostHWND";
chromeWidgetHostHandle = windowHandleInfo.FindDescendantByClassName(chromeWidgetHostClassName);
return chromeWidgetHostHandle != IntPtr.Zero;
}
}
@UweKeim
Copy link

UweKeim commented Dec 26, 2018

Works for DevExpress controls in a similar way, too. See my question and the answer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment