Skip to content

Instantly share code, notes, and snippets.

@amaitland
Last active January 23, 2017 06:48
Show Gist options
  • Save amaitland/fb2d14af2796aceb37e20cddad99459a to your computer and use it in GitHub Desktop.
Save amaitland/fb2d14af2796aceb37e20cddad99459a to your computer and use it in GitHub Desktop.
CefSharp SetAsChild PopupHandler
// Copyright © 2010-2016 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.Collections.Generic;
using System.Windows.Forms;
using CefSharp.WinForms.Popup;
namespace CefSharp.WinForms.Example.Handlers
{
public class LifeSpanHandler : ILifeSpanHandler
{
private Dictionary<int, PopupAsChildHelper> popupasChildHelpers = new Dictionary<int,PopupAsChildHelper>();
bool ILifeSpanHandler.OnBeforePopup(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
{
//Set newBrowser to null unless your attempting to host the popup in a new instance of ChromiumWebBrowser
newBrowser = null;
//EXPERIMENTAL OPTION #2: Use IWindowInfo.SetAsChild to specify the parent handle
//NOTE: user PopupAsChildHelper to handle with Form move and Control resize
var chromiumWebBrowser = (ChromiumWebBrowser)browserControl;
var windowX = windowInfo.X;
var windowY = windowInfo.Y;
var windowWidth = (windowInfo.Width == int.MinValue) ? 600 : windowInfo.Width;
var windowHeight = (windowInfo.Height == int.MinValue) ? 800 : windowInfo.Height;
chromiumWebBrowser.Invoke(new Action(() =>
{
var owner = chromiumWebBrowser.FindForm();
var popup = new Form
{
Left = windowX,
Top = windowY,
Width = windowWidth,
Height = windowHeight,
Text = targetFrameName
};
popup.CreateControl();
owner.AddOwnedForm(popup);
var control = new Control();
control.Dock = DockStyle.Fill;
control.CreateControl();
popup.Controls.Add(control);
popup.Show();
var rect = control.ClientRectangle;
windowInfo.SetAsChild(control.Handle, rect.Left, rect.Top, rect.Right, rect.Bottom);
}));
return false;
}
void ILifeSpanHandler.OnAfterCreated(IWebBrowser browserControl, IBrowser browser)
{
if(browser.IsPopup)
{
var interceptor = new PopupAsChildHelper(browser);
popupasChildHelpers.Add(browser.Identifier, interceptor);
}
}
bool ILifeSpanHandler.DoClose(IWebBrowser browserControl, IBrowser browser)
{
//We need to allow popups to close
//If the browser has been disposed then we'll just let the default behaviour take place
if(browser.IsDisposed || browser.IsPopup)
{
return false;
}
//The default CEF behaviour (return false) will send a OS close notification (e.g. WM_CLOSE).
//See the doc for this method for full details.
//return true here to handle closing yourself (no WM_CLOSE will be sent).
return true;
}
void ILifeSpanHandler.OnBeforeClose(IWebBrowser browserControl, IBrowser browser)
{
if(!browser.IsDisposed && browser.IsPopup)
{
var interceptor = popupasChildHelpers[browser.Identifier];
if(interceptor != null)
{
popupasChildHelpers[browser.Identifier] = null;
interceptor.Dispose();
}
}
}
}
}
// Copyright © 2010-2017 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.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using CefSharp.Internals;
using CefSharp.WinForms.Internals;
namespace CefSharp.WinForms.Popup
{
/// <summary>
/// When using ILifeSpanHandler.OnBeforePopup and calling IWindowInfo.SetAsChild
/// it's important to track form movement and control size change. This NativeWindow
/// does that for you.
/// </summary>
public class PopupAsChildHelper : NativeWindow, IDisposable
{
/// <summary>
/// Keep track of whether a move is in progress.
/// </summary>
private bool isMoving;
/// <summary>
/// Gets or sets the parent control used to host the popup child handle
/// </summary>
private Control parentControl;
/// <summary>
/// Gets or sets the parent form.
/// </summary>
/// <value>The parent form.</value>
private Form parentForm;
/// <summary>
/// The IBrowser that references the Popup
/// </summary>
private IBrowser browser;
/// <summary>
/// The browsers window handle(hwnd)
/// </summary>
private IntPtr browserHandle;
/// <summary>
/// Initializes a new instance of the <see cref="ParentFormMessageInterceptor"/> class.
/// </summary>
/// <param name="browser">The browser.</param>
public PopupAsChildHelper(IBrowser browser)
{
if (browser == null)
{
throw new ArgumentNullException("browser");
}
this.browser = browser;
//From the browser we grab the window handle (hwnd)
this.browserHandle = browser.GetHost().GetWindowHandle();
//WinForms will kindly lookup the child control from it's handle
this.parentControl = Control.FromChildHandle(browserHandle);
if(this.parentControl == null)
{
throw new Exception("Unable to locate parentControl from the browser handle.");
}
// Get notified if our control window parent changes:
this.parentControl.ParentChanged += ParentFormChanged;
//Get notified of size changes
this.parentControl.SizeChanged += ParentControlSizeChanged;
// Find the browser form to subclass to monitor WM_MOVE/WM_MOVING
RefindParentForm();
}
/// <summary>
/// Call to force refinding of the parent Form.
/// (i.e. top level window that owns the ChromiumWebBrowserControl)
/// </summary>
public void RefindParentForm()
{
parentControl.InvokeOnUiThreadIfRequired(() =>
{
ParentFormChanged(parentControl, null);
});
}
/// <summary>
/// Adjust the form to listen to if the ChromiumWebBrowserControl's parent changes.
/// </summary>
/// <param name="sender">The ChromiumWebBrowser whose parent has changed.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
private void ParentFormChanged(object sender, EventArgs e)
{
var control = (Control)sender;
var oldForm = parentForm;
var newForm = control.FindForm();
if (oldForm == null || newForm == null || oldForm.Handle != newForm.Handle)
{
if (Handle != IntPtr.Zero)
{
ReleaseHandle();
}
if (oldForm != null)
{
oldForm.HandleCreated -= OnHandleCreated;
oldForm.HandleDestroyed -= OnHandleDestroyed;
}
parentForm = newForm;
if (newForm != null)
{
newForm.HandleCreated += OnHandleCreated;
newForm.HandleDestroyed += OnHandleDestroyed;
// If newForm's Handle has been created already,
// our event listener won't be called, so call it now.
if (newForm.IsHandleCreated)
{
OnHandleCreated(newForm, null);
}
}
}
}
private void ParentControlSizeChanged(object sender, EventArgs e)
{
var bounds = parentControl.Bounds;
if(browserHandle != IntPtr.Zero)
{
NativeMethodWrapper.SetWindowPosition(browserHandle, bounds.X, bounds.Y, bounds.Width, bounds.Height);
}
}
/// <summary>
/// Handles the <see cref="E:HandleCreated" /> event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void OnHandleCreated(object sender, EventArgs e)
{
AssignHandle(((Form)sender).Handle);
}
/// <summary>
/// Handles the <see cref="E:HandleDestroyed" /> event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void OnHandleDestroyed(object sender, EventArgs e)
{
ReleaseHandle();
}
/// <summary>
/// Invokes the default window procedure associated with this window.
/// </summary>
/// <param name="m">A <see cref="T:System.Windows.Forms.Message" /> that is associated with the current Windows message.</param>
protected override void WndProc(ref Message m)
{
var isMovingMessage = false;
// Negative initial values keeps the compiler quiet and to
// ensure we have actual window movement to notify CEF about.
const int invalidMoveCoordinate = -1;
var x = invalidMoveCoordinate;
var y = invalidMoveCoordinate;
// Listen for operating system messages
switch (m.Msg)
{
case NativeMethods.WM_MOVING:
{
var movingRectangle = (Rectangle)Marshal.PtrToStructure(m.LParam, typeof(Rectangle));
x = movingRectangle.Left;
y = movingRectangle.Top;
isMovingMessage = true;
break;
}
case NativeMethods.WM_MOVE:
{
// Convert IntPtr into 32bit int safely without
// exceptions:
int dwLParam = m.LParam.CastToInt32();
// Extract coordinates from lo/hi word:
x = dwLParam & 0xffff;
y = (dwLParam >> 16) & 0xffff;
isMovingMessage = true;
break;
}
}
// Only notify about movement if:
// * ParentControl Handle Created
// NOTE: This is checked for paranoia.
// This WndProc can't be called unless ParentForm has
// its handle created, but that doesn't necessarily mean
// Browser has had its handle created.
// WinForm controls don't usually get eagerly created Handles
// in their constructors.
// * ParentForm Actually moved
// * Not currently moving (on the UI thread only of course)
// * The current WindowState is Normal.
// This check is to simplify the effort here.
// Other logic already handles the maximize/minimize
// cases just fine.
// You might consider checking Control.Visible and
// not notifying our browser control if the browser control isn't visible.
// However, if you do that, the non-Visible CEF tab will still
// have any SELECT drop downs rendering where they shouldn't.
if (isMovingMessage
&& parentControl.IsHandleCreated
&& parentForm.WindowState == FormWindowState.Normal
&& (parentForm.Left != x || parentForm.Top != y)
&& !isMoving)
{
// parentForm.Left & .Right are negative when the window
// is transitioning from maximized to normal.
// If we are transitioning, the form will also receive
// a WM_SIZE which can deal with the move/size combo itself.
if (parentForm.Left >= 0 && parentForm.Right >= 0)
{
OnMoving();
}
}
DefWndProc(ref m);
}
/// <summary>
/// Called when the window is moving.
/// </summary>
protected virtual void OnMoving()
{
isMoving = true;
if (!browser.IsDisposed)
{
browser.GetHost().NotifyMoveOrResizeStarted();
}
isMoving = false;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (parentForm != null)
{
parentForm.HandleCreated -= OnHandleCreated;
parentForm.HandleDestroyed -= OnHandleDestroyed;
parentForm = null;
}
// Unmanaged resource, but release here anyway.
// NativeWindow has its own finalization logic
// that should be run if this instance isn't disposed
// properly before arriving at the finalization thread.
// See: http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/NativeWindow.cs,147
// for the gruesome details.
if (Handle != IntPtr.Zero)
{
ReleaseHandle();
}
if (parentControl != null)
{
parentControl.ParentChanged -= ParentFormChanged;
parentControl.SizeChanged -= ParentControlSizeChanged;
parentControl = null;
}
}
}
/// <summary>
/// When overridden in a derived class, manages an unhandled thread exception.
/// </summary>
/// <param name="e">An <see cref="T:System.Exception" /> that specifies the unhandled thread exception.</param>
protected override void OnThreadException(Exception e)
{
// TODO: Do something more interesting here, logging, whatever, something.
base.OnThreadException(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment