Skip to content

Commit

Permalink
Merge pull request #1138 from NumbersInternational/example/menu-closer
Browse files Browse the repository at this point in the history
Add ChromeWidgetMessageInterceptor to WinForms Example
  • Loading branch information
jankurianski committed Jul 23, 2015
2 parents 0379632 + 786cf35 commit 44a13c3
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 1 deletion.
41 changes: 40 additions & 1 deletion CefSharp.WinForms.Example/BrowserTabUserControl.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CefSharp.Example;
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using CefSharp.WinForms.Internals;

Expand Down Expand Up @@ -100,9 +101,47 @@ private void SetIsLoading(bool isLoading)
HandleToolStripLayout();
}

[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

private void OnIsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs args)
{

if (args.IsBrowserInitialized)
{
ChromeWidgetMessageInterceptor.SetupLoop((ChromiumWebBrowser)Browser, (message) =>
{
const int WM_MOUSEACTIVATE = 0x0021;
const int WM_NCLBUTTONDOWN = 0x00A1;

if (message.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 = message.WParam;
PostMessage(topLevelWindowHandle, WM_NCLBUTTONDOWN, IntPtr.Zero, IntPtr.Zero);
}

// The ChromiumWebBrowserControl does not fire MouseEnter/Move/Leave events, because Chromium handles these.
// However we can hook into Chromium's messaging window to receive the events.
//
//const int WM_MOUSEMOVE = 0x0200;
//const int WM_MOUSELEAVE = 0x02A3;
//
//switch (message.Msg) {
// case WM_MOUSEMOVE:
// Console.WriteLine("WM_MOUSEMOVE");
// break;
// case WM_MOUSELEAVE:
// Console.WriteLine("WM_MOUSELEAVE");
// break;
//}
});
}
}

public void ExecuteScript(string script)
Expand Down
1 change: 1 addition & 0 deletions CefSharp.WinForms.Example/CefSharp.WinForms.Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
<Compile Include="BrowserTabUserControl.Designer.cs">
<DependentUpon>BrowserTabUserControl.cs</DependentUpon>
</Compile>
<Compile Include="ChromeWidgetMessageInterceptor.cs" />
<Compile Include="GeolocationHandler.cs" />
<Compile Include="KeyboardHandler.cs" />
<Compile Include="MenuHandler.cs" />
Expand Down
152 changes: 152 additions & 0 deletions CefSharp.WinForms.Example/ChromeWidgetMessageInterceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright © 2010-2015 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CefSharp.WinForms.Example
{
/// <summary>
/// Intercepts Windows messages sent to the ChromiumWebBrowser control's widget sub-window.
///
/// It is necessary to listen to the widget sub-window because it receives all Windows messages
/// and forwards them to CEF, rather than the ChromiumWebBrowser.Handle.
///
/// The supplied Action delegate is fired upon each message.
/// </summary>
class ChromeWidgetMessageInterceptor : NativeWindow
{
private readonly ChromiumWebBrowser browser;
private Action<Message> forwardAction;

private ChromeWidgetMessageInterceptor(ChromiumWebBrowser browser, IntPtr chromeWidgetHostHandle, Action<Message> forwardAction)
{
AssignHandle(chromeWidgetHostHandle);

this.browser = browser;
browser.HandleDestroyed += BrowserHandleDestroyed;

this.forwardAction = forwardAction;
}

/// <summary>
/// Asynchronously wait for the Chromium widget window to be created for the given ChromiumWebBrowser,
/// and when created hook into its Windows message loop.
/// </summary>
/// <param name="browser">The browser to intercept Windows messages for.</param>
/// <param name="forwardAction">This action will be called whenever a Windows message is received.</param>
internal static void SetupLoop(ChromiumWebBrowser browser, Action<Message> forwardAction)
{
Task.Factory.StartNew(() =>
{
try
{
bool foundWidget = false;
while (!foundWidget)
{
browser.Invoke((Action)(() =>
{
IntPtr chromeWidgetHostHandle;
if (ChromeWidgetHandleFinder.TryFindHandle(browser, out chromeWidgetHostHandle))
{
foundWidget = true;
new ChromeWidgetMessageInterceptor(browser, chromeWidgetHostHandle, forwardAction);
}
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
}
});
}

private void BrowserHandleDestroyed(object sender, EventArgs e)
{
ReleaseHandle();

browser.HandleDestroyed -= BrowserHandleDestroyed;
forwardAction = null;
}

protected override void WndProc(ref Message m)
{
base.WndProc(ref m);

if (forwardAction != null)
{
forwardAction(m);
}
}
}

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;
private string seekClassName;
private 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;
}
}
}

0 comments on commit 44a13c3

Please sign in to comment.