Last active
June 12, 2023 15:32
-
-
Save jankurianski/2163d7928d3d6f839e47 to your computer and use it in GitHub Desktop.
Hack to close ToolStrip dropdown when ChromiumWebBrowser is focused
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Works for DevExpress controls in a similar way, too. See my question and the answer.