// Copyright © 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.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using CefSharp.Internals; namespace CefSharp.WinForms.Internals { /// /// ParentFormMessageInterceptor - hooks into the parent forms /// message loop to incercept messages like WM_MOVE /// /// /// internal class ParentFormMessageInterceptor : NativeWindow, IDisposable { /// /// Keep track of whether a move is in progress. /// private bool isMoving; /// /// Used to determine the coordinates involved in the move /// private Rectangle movingRectangle; /// /// Store the previous window state, used to determine if the /// Windows was previously /// and resume rendering /// private FormWindowState previousWindowState; /// /// Gets or sets the browser. /// /// The browser. private ChromiumWebBrowser Browser { get; set; } /// /// Gets or sets the parent form. /// /// The parent form. private Form ParentForm { get; set; } /// /// Initializes a new instance of the class. /// /// The browser. public ParentFormMessageInterceptor(ChromiumWebBrowser browser) { Browser = browser; // Get notified if our browser window parent changes: Browser.ParentChanged += ParentParentChanged; // Find the browser form to subclass to monitor WM_MOVE/WM_MOVING RefindParentForm(); } /// /// Call to force refinding of the parent Form. /// (i.e. top level window that owns the ChromiumWebBrowserControl) /// public void RefindParentForm() { ParentParentChanged(Browser, null); } /// /// Adjust the form to listen to if the ChromiumWebBrowserControl's parent changes. /// /// The ChromiumWebBrowser whose parent has changed. /// The instance containing the event data. private void ParentParentChanged(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; oldForm.Resize -= OnResize; } ParentForm = newForm; if (newForm != null) { newForm.HandleCreated += OnHandleCreated; newForm.HandleDestroyed += OnHandleDestroyed; newForm.Resize += OnResize; previousWindowState = newForm.WindowState; // 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 OnResize(object sender, EventArgs e) { var form = (Form)sender; if (previousWindowState == form.WindowState) { return; } switch (form.WindowState) { case FormWindowState.Normal: case FormWindowState.Maximized: { if (previousWindowState == FormWindowState.Minimized) { Browser?.ShowInternal(); } break; } case FormWindowState.Minimized: { Browser?.HideInternal(); break; } } previousWindowState = form.WindowState; } /// /// Handles the event. /// /// The sender. /// The instance containing the event data. private void OnHandleCreated(object sender, EventArgs e) { AssignHandle(((Form)sender).Handle); } /// /// Handles the event. /// /// The sender. /// The instance containing the event data. private void OnHandleDestroyed(object sender, EventArgs e) { ReleaseHandle(); } /// /// Invokes the default window procedure associated with this window. /// /// A that is associated with the current Windows message. 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_ACTIVATE: { // Intercept (de)activate messages for our form so that we can // ensure that we play nicely with WinForms .ActiveControl // tracking. var browser = Browser; if ((int)m.WParam == 0x0) // WA_INACTIVE { // If the CEF browser no longer has focus, // we won't get a call to OnLostFocus on ChromiumWebBrowser. // However, it doesn't matter so much since the CEF // browser will receive it instead. // Paranoia about making sure the IsActivating state is correct now. browser.IsActivating = false; DefWndProc(ref m); } else // WA_ACTIVE or WA_CLICKACTIVE { // Only set IsActivating if the ChromiumWebBrowser was the form's // ActiveControl before the last deactivation. browser.IsActivating = browser.IsActiveControl(); DefWndProc(ref m); // During activation, WM_SETFOCUS gets sent to // to the CEF control since it's the root window // of the CEF UI thread. // // Therefore, don't set .IsActivating to false here // instead do so in DefaultFocusHandler.OnGotFocus. // Otherwise there's a race condition between this // thread setting activating to false and // the CEF DefaultFocusHandler executing to determine // it shouldn't Activate() the control. } return; } case NativeMethods.WM_MOVING: { 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: // * Browser 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 Browser.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 && Browser.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); } /// /// Called when [moving]. /// protected virtual void OnMoving() { isMoving = true; if (Browser.IsBrowserInitialized) { Browser.GetBrowser().GetHost().NotifyMoveOrResizeStarted(); } isMoving = false; } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (disposing) { if (ParentForm != null) { ParentForm.HandleCreated -= OnHandleCreated; ParentForm.HandleDestroyed -= OnHandleDestroyed; ParentForm.Resize -= OnResize; 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 (Browser != null) { Browser.ParentChanged -= ParentParentChanged; Browser = null; } } } /// /// When overridden in a derived class, manages an unhandled thread exception. /// /// An that specifies the unhandled thread exception. protected override void OnThreadException(Exception e) { // TODO: Do something more interesting here, logging, whatever, something. base.OnThreadException(e); } } }