Skip to content

Commit 06b7549

Browse files
committed
WinForms - Demo workaround for activation issue when showing two browser forms
When there are two forms that contain browser instances shown at the same time they rapidly switch between the two windows. Issue #2928
1 parent 84c0e3c commit 06b7549

File tree

5 files changed

+140
-3
lines changed

5 files changed

+140
-3
lines changed

CefSharp.WinForms.Example/CefSharp.WinForms.Example.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
<Compile Include="Handlers\KeyboardHandler.cs" />
9090
<Compile Include="Handlers\LifeSpanHandler.cs" />
9191
<Compile Include="Handlers\MenuHandler.cs" />
92+
<Compile Include="Handlers\MultiFormFocusHandler.cs" />
9293
<Compile Include="Handlers\ScheduleMessagePumpBrowserProcessHandler.cs" />
9394
<Compile Include="Handlers\WinFormsBrowserProcessHandler.cs" />
9495
<Compile Include="Handlers\WinFormsRequestHandler.cs" />
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright © 2019 The CefSharp Authors. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
4+
5+
using CefSharp.WinForms.Internals;
6+
7+
namespace CefSharp.WinForms.Example.Handlers
8+
{
9+
/// <summary>
10+
/// Implementation of <see cref="IFocusHandler"/> that shows a workaround
11+
/// for https://github.com/cefsharp/CefSharp/issues/2928
12+
/// This version is based on the old behaviour seen in version 71 etc
13+
/// </summary>
14+
/// <seealso cref="CefSharp.IFocusHandler" />
15+
public class MultiFormFocusHandler : IFocusHandler
16+
{
17+
/// <summary>
18+
/// Called when the browser component has received focus.
19+
/// </summary>
20+
/// <param name="chromiumWebBrowser">the ChromiumWebBrowser control</param>
21+
/// <param name="browser">the browser object</param>
22+
/// <remarks>Try to avoid needing to override this logic in a subclass. The implementation in
23+
/// DefaultFocusHandler relies on very detailed behavior of how WinForms and
24+
/// Windows interact during window activation.</remarks>
25+
void IFocusHandler.OnGotFocus(IWebBrowser chromiumWebBrowser, IBrowser browser)
26+
{
27+
//We don't deal with popups as they're rendered by default entirely by CEF
28+
//For print dialogs the browser will be null, we don't want to deal with that either.
29+
if (browser == null || browser.IsPopup)
30+
{
31+
return;
32+
}
33+
34+
var winFormsChromiumWebBrowser = (ChromiumWebBrowser)chromiumWebBrowser;
35+
// During application activation, CEF receives a WM_SETFOCUS
36+
// message from Windows because it is the top window
37+
// on the CEF UI thread.
38+
//
39+
// If the WinForm ChromiumWebBrowser control is the
40+
// current .ActiveControl before app activation
41+
// then we MUST NOT try to reactivate the WinForm
42+
// control during activation because that will
43+
// start a race condition between reactivating
44+
// the CEF control AND having another control
45+
// that should be the new .ActiveControl.
46+
//
47+
// For example:
48+
// * CEF control has focus, and thus ChromiumWebBrowser
49+
// is the current .ActiveControl
50+
// * Alt-Tab to another application
51+
// * Click a non CEF control in the WinForms application.
52+
// * This begins the Windows activation process.
53+
// * The WM_ACTIVATE process on the WinForm UI thread
54+
// will update .ActiveControl to the clicked control.
55+
// The clicked control will receive WM_SETFOCUS as well.
56+
// (i.e. OnGotFocus)
57+
// If the ChromiumWebBrowser was the previous .ActiveControl,
58+
// then we set .Activating = true.
59+
// * The WM_ACTIVATE process on the CEF thread will
60+
// send WM_SETFOCUS to CEF thus staring the race of
61+
// which will end first, the WndProc WM_ACTIVATE process
62+
// on the WinForm UI thread or the WM_ACTIVATE process
63+
// on the CEF UI thread.
64+
// * CEF will then call this method on the CEF UI thread
65+
// due to WM_SETFOCUS.
66+
// * This method will clear the activation state (if any)
67+
// on the ChromiumWebBrowser control, due to the race
68+
// condition the WinForm UI thread cannot.
69+
if (winFormsChromiumWebBrowser.IsActivating)
70+
{
71+
winFormsChromiumWebBrowser.IsActivating = false;
72+
}
73+
else
74+
{
75+
// Otherwise, we're not being activated
76+
// so we must activate the ChromiumWebBrowser control
77+
// for WinForms focus tracking.
78+
winFormsChromiumWebBrowser.InvokeOnUiThreadIfRequired(() =>
79+
{
80+
winFormsChromiumWebBrowser.Activate();
81+
});
82+
}
83+
}
84+
85+
/// <summary>
86+
/// Called when the browser component is requesting focus.
87+
/// </summary>
88+
/// <param name="chromiumWebBrowser">the ChromiumWebBrowser control</param>
89+
/// <param name="browser">the browser object</param>
90+
/// <param name="source">Indicates where the focus request is originating from.</param>
91+
/// <returns>Return false to allow the focus to be set or true to cancel setting the focus.</returns>
92+
bool IFocusHandler.OnSetFocus(IWebBrowser chromiumWebBrowser, IBrowser browser, CefFocusSource source)
93+
{
94+
//We don't deal with popups as they're rendered by default entirely by CEF
95+
if (browser.IsPopup)
96+
{
97+
return false;
98+
}
99+
// Do not let the browser take focus when a Load method has been called
100+
return source == CefFocusSource.FocusSourceNavigation;
101+
}
102+
103+
/// <summary>
104+
/// Called when the browser component is about to lose focus.
105+
/// For instance, if focus was on the last HTML element and the user pressed the TAB key.
106+
/// </summary>
107+
/// <param name="chromiumWebBrowser">the ChromiumWebBrowser control</param>
108+
/// <param name="browser">the browser object</param>
109+
/// <param name="next">Will be true if the browser is giving focus to the next component
110+
/// and false if the browser is giving focus to the previous component.</param>
111+
void IFocusHandler.OnTakeFocus(IWebBrowser chromiumWebBrowser, IBrowser browser, bool next)
112+
{
113+
//We don't deal with popups as they're rendered by default entirely by CEF
114+
if (browser.IsPopup)
115+
{
116+
return;
117+
}
118+
119+
var winFormsChromiumWebBrowser = (ChromiumWebBrowser)chromiumWebBrowser;
120+
121+
// NOTE: OnTakeFocus means leaving focus / not taking focus
122+
winFormsChromiumWebBrowser.InvokeOnUiThreadIfRequired(() => winFormsChromiumWebBrowser.SelectNextControl(next));
123+
}
124+
}
125+
}
126+

CefSharp.WinForms.Example/Minimal/MultiFormAppContext.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
44

55
using System.Windows.Forms;
6+
using CefSharp.WinForms.Example.Handlers;
67

78
namespace CefSharp.WinForms.Example.Minimal
89
{
@@ -13,9 +14,9 @@ public class MultiFormAppContext : ApplicationContext
1314

1415
public MultiFormAppContext(bool multiThreadedMessageLoop)
1516
{
16-
form1 = new SimpleBrowserForm(multiThreadedMessageLoop);
17+
form1 = new SimpleBrowserForm(multiThreadedMessageLoop, new MultiFormFocusHandler());
1718
form1.WindowState = FormWindowState.Normal;
18-
form2 = new SimpleBrowserForm(multiThreadedMessageLoop);
19+
form2 = new SimpleBrowserForm(multiThreadedMessageLoop, new MultiFormFocusHandler());
1920
form2.WindowState = FormWindowState.Normal;
2021

2122
form1.FormClosed += OnFormClosed;

CefSharp.WinForms.Example/Minimal/SimpleBrowserForm.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ namespace CefSharp.WinForms.Example.Minimal
1313
public partial class SimpleBrowserForm : Form
1414
{
1515
private ChromiumWebBrowser browser;
16+
private IFocusHandler customFocusHandler;
1617
private bool multiThreadedMessageLoop;
1718

18-
public SimpleBrowserForm(bool multiThreadedMessageLoop)
19+
public SimpleBrowserForm(bool multiThreadedMessageLoop, IFocusHandler customFocusHandler = null)
1920
{
2021
InitializeComponent();
2122

23+
this.customFocusHandler = customFocusHandler;
2224
this.multiThreadedMessageLoop = multiThreadedMessageLoop;
2325

2426
Text = "CefSharp";
@@ -72,6 +74,12 @@ private void CreateBrowser()
7274
browser.FocusHandler = null;
7375
}
7476

77+
//Only override if we have a custom handler
78+
if (customFocusHandler != null)
79+
{
80+
browser.FocusHandler = customFocusHandler;
81+
}
82+
7583
}
7684

7785
private void OnBrowserConsoleMessage(object sender, ConsoleMessageEventArgs args)

CefSharp.WinForms.Example/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public static int Main(string[] args)
9595

9696
CefExample.Init(settings, browserProcessHandler: browserProcessHandler);
9797

98+
//Application.Run(new MultiFormAppContext(multiThreadedMessageLoop));
9899
Application.Run(browser);
99100
}
100101

0 commit comments

Comments
 (0)