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

Add ChromeWidgetMessageInterceptor to WinForms Example #1138

Merged
merged 4 commits into from
Jul 23, 2015
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
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;
}
}
}