WebView2 WinForms control in Excel VSTO Task pane steals and holds on to keyboard focus
Description
I have a Winforms WebView2 hosted in an Excel VSTO Task pane, There is no System.Windows.Forms.Form hosting the WebView2 control, instead, a System.Windows.Forms.UserControl is the owner/host of the control. After giving the WebView2 focus, it holds on to it after I've clicked back into the Excel worksheet.
Version SDK: 1.0.705.50 Runtime: Evergreen RT 88.0.705.68 Framework: WinForms OS: Win10 1909
Repro Steps
- Render some fairly simple static HTML in the WebView2
- Click in a worksheet cell, verify that keystrokes are acted on there (eg. right-arrow moves selected cell)
- Click in the HTML content in the WebView2 (in the task pane), verify that keystrokes are acted on there (eg. Enter toggles an expand/collapse widget)
- Click back in a different worksheet cell Observed: selected cell is changed as expected, however keystrokes are still getting captured by WebView2 (ie. Enter toggles the HTML widget instead of moving the cell selection).
Thanks for finding this issue @ShaunLoganOracle - we have some other bugs around VSTO Task Panes and I think they probably share a common root cause. I've created a bug on our backlog and we'll take a look.
I have that same behaviour with a project using Custom Task Panes from ExcelDNA.
- SDK: "1.0.865-prerelease"
- Runtime: 90.0.818.51
FYI, a demo project to reproduce can be found there: https://github.com/alaincao/ExcelDNASamples/tree/webview2_issue_933/CustomTaskPane
@champnic Update: I still see issues with keyboard focus getting stuck using SDK 1.0.902.49 and WV2 runtime 93.0.961.38 The bad behavior in #933 where there was a crash/hang is fixed. However the behavior of keyboard focus getting stuck in the WebView2-rendered content is still there. To add to that, the F6 and Shift-F6 keys seem to be ignored/swallowed by WV2. In Excel, these keys should move the focus from the VSTO task pane to other controls in Excel (the ribbon, worksheet grid, etc.). Since these are ignored, this makes the WebView2 control in the task pane a "keyboard trap" - thus it is an accessibility issue.
@champnic Update: customers are still encountering this "focus getting stuck" issue using WV2 RT 98.0.1108.55 and our add-in code using the WV2 SDK 1.0.902.49. As our adoption of WebView2 gets wider distribution among our customers, the frequency (and annoyance) of this issue increases. We would be glad to explore a code-around if there is something our code could do to avoid this issue.
Hey @ShaunLoganOracle - Can you try this potential workaround?
Before initializing your WebView2, call:
SetEnvironmentVariable(L"COREWEBVIEW2_FORCED_HOSTING_MODE", L"COREWEBVIEW2_HOSTING_MODE_WINDOW_TO_VISUAL");
If that resolves the issue, then we are currently working on a better solution which should fix this automatically. That workaround also won't work on Win 7/8/8.1.
Thanks. My add-in supports Win 10 & 11 only so I'll try this shortly and report back.
I tried this by setting a System Environment variable (as spec'd above) in the Settings app and then running Excel and trying my use case. The stolen keyboard focus issue seems be resolved! I did see something a little unexpected - now when I mouse click into the WebView2 hosted in the VSTO task pane - it does not appear to get keyboard focus at all (focus stays where it was, in my case: the worksheet grid). If I use F6 to activate the task pane, then the content in the WV2 control does get focus. I will need to run additional tests to confirm no ill effects across my use cases, but this looks promising as a workaround that customers can even use without an updated add-in. Thanks! I look forward to the "better solution which should fix this automatically".
Observation: by setting this environment variable programmatically in my add-in (running in the Excel process), any other usages of WebView2 in that process will presumably have their behavior changed too. For example if there was another add-in using WebView2, it would be affected.
@champnic After some more testing, I have to report that this workaround setting the env. variable does not work for us. Yes, it appears to resolve the issue in the VSTO Task Pane, but it causes another worse issue. In addition to using WV2 in the Task Pane, we also have some modal WinForms dialog that host WV2 controls that render, for example, login pages. With the environment variable set, it seems that the html content never gets keyboard focus. So for example, tabbing from the username input box to the password input box does not work - the Tab key is ignored. This is a blocker for us. Without the env. variable set, the Tab key works as expected in these login pages in WV2. Edit: some keys are seen by the control (Backspace, Esc), but others (Tab, left/right/up/down arrows) are not.
@ShaunLoganOracle Thanks for trying it out. The fact that it fixes the original issue does suggest that the other work we are doing will fix this when it's completed. It's unfortunate the side effects mean you can't use the workaround yet though, sorry about that.
@champnic I have some questions around the environment variable that I hope you can help me with.
- Can you please shed some light on what setting the environment variable actually does?
- Are there any downsides / things to be aware of when setting the environment variable?
- Do you have some documentation around it?
- Will the default behavior at some point change to what setting environment variable does? If yes, can you share an approximate timeline?
- Will there be a setting/enum in code to change the hosting mode without setting an environment variable? If yes, can you share an approximate timeline?
Hey @sbolofsson!
- Roughly speaking, the environment variable changes how the app connects to the WebView2 control. Instead of using a child HWND, it uses a DComp visual.
- The main downside is that visuals are not supported on all Windows OSes. I believe the APIs we need for this to work only go down to Windows 10 (April 2018 update I think?). The other downside is that it does change the order of operations for some input and focus management, which can potentially introduce app compat issues for existing apps.
- No documentation. It's not really a documented feature, and more used for testing.
- We are working on making using the visuals a more supported feature. I don't think we've decided yet whether to make it default or not - it solves a few problems, but is a change in behavior that could cause app compat problems for existing apps relying on the existing behavior. No timeline yet on this work.
- There might be a setting/enum in the future as part of the work above.
Ok, many thanks for the clarifications and details @champnic. For us, setting this environment variable solves a focus/mouse related issue in a VSTO task pane. So please don't remove this "feature" 😊
We understand that it can be hard to make this hosting mode the default, due to possibly breaking existing compat. However, looking at the release notes history and also currently open issues for WebView2, it's quite clear that there have been (and still are) quite a number of issues related to focus and/or VSTO. Now, I obviously don't know if this environment variable solves all (probably not) - and it may even introduce new issues, as also mentioned in this issue.
Therefore, I think it would very much make sense to make this officially supported (when it's ready), and then opt-in instead of default.
I think that even the limited OS support could be fine for us. 2018 is somewhat old and VSTO is Windows only anyway.
Hello @champnic , we also added this workaround to address a mouse issue that appeared when running our add-in in PowerPoint. It did raise another issue, in our opinion much more impactful. It made the top left of the desktop (when everything is minimized) not available. The user cannot click on any icons while the add-ins are running. Therefore we had to remove the workaround.
Is there any alternative workaround we can pursue?
@champnic @ShaunLoganOracle @alaincao Is there anything else we could do with the issue?
@champnic This issue is still occurring using WV2 SDK 1.0.1832.23 and RT 114.0.1823.67 Is there any progress/update?
Hey @ShaunLoganOracle - I believe @zhuhaichao518 has been working on a change that's in experimental that can alleviate some of this issue. @zhuhaichao518 can you share the latest on your investigations using the hit-test transparent mode here, and the remaining issues?
We are still having this issue.
Hi @ShaunLoganOracle @andrewkittredge does the solution mentioned in #1254 work for you? Meanwhile @zhuhaichao518 is working on adding an API into ControllerOptions, you can check the detailed spec here: https://github.com/MicrosoftEdge/WebView2Feedback/pull/3596
does the solution mentioned in #1254 work for you?
@novac42
In a very quick test, this approach looks promising.
I added --enable-features=msWebView2BrowserHitTransparent to the CoreWebView2EnvironmentOptions.AdditionalBrowserArguments used when creating the (WinForms) WebView2 control (hosted in VSTO Excel Add-in Task Pane).
I observe now that clicking with the mouse in the HTML rendered in the WV2 control sends the mouse click to the browser (eg. HTML element gets onClick), but leaves keyboard focus where it was (in my case, the worksheet grid). If I explicitly give focus to the WV2 via the keyboard using a combination of F6 and Tab, then subsequent keystrokes are seen by the WV2 (and HTML).
I also quickly tried other cases where we host a WebView2 control in a modal WinForms Dialog: focus and keyboard access seem to behave as expected.
We will have our accessibility experts review the changes and report back if this approach introduces any issues in that domain.
There is one non-intuitive behavior: clicking the mouse in the HTML content rendered in the WV2 control does not give keyboard focus to that control. The keyboard focus stays in the worksheet grid. Previously, clicking in the HTML gave keyboard focus to the WV2 (which then did not give back the focus when clicking back on the grid).
@novac42 @zhuhaichao518
We have found a blocker issue for us with the --enable-features=msWebView2BrowserHitTransparent switch enabled:
In a WinForms modal dialog that hosts a WV2 control which renders a login form, the Tab key does not work to move focus from the username field to to the password field. Without the switch enabled, Tab works as expected.
@ShaunLoganOracle Thanks for the feedback. We will take a look into it.
same scenario, same problems.
Is there any update?
This is a showstopper bug, preventing any use in Microsoft Office applications like Excel.
First there was the failure to render issue, dating back to May 2020 (#187), where the successful workaround is to set the user data folder to a writeable location. So far so good.
This current issue (#951) dates to Feb 2021. The workaround above, unfortunately, shifts the focus from the WebView2 100% to the Excel worksheet 100%.
Is there a setting or another workaround that allows for focus to shift smoothly between the Excel sheet and WebView2 in a task pane?
Thanks a lot in advance.
I found a solution. In order to return keyboard focus back to Excel after a WebView2 control steals keyboard focus, you can use the Windows API to re-set the foreground window back to Excel. Here is how you do it:
Here it is in VB.Net
Add this module to your code:
Public Module NativeMethods
<DllImport("user32.dll", SetLastError:=True)>
Public Function SetForegroundWindow(hWnd As IntPtr) As Boolean
End Function
Public Declare Function GetDesktopWindow Lib "user32" () As IntPtr
End Module
When you want to return focus back to Excel, just call the method FocusExcel():
Public Class YourClass
Public Sub FocusExcel()
NativeMethods.SetForegroundWindow(NativeMethods.GetDesktopWindow())
NativeMethods.SetForegroundWindow(Globals.ThisAddIn.Application.Hwnd)
End Sub
End Class
Here it is in C#
public class NativeMethods
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
}
public class YourClass
{
public void FocusExcel()
{
NativeMethods.SetForegroundWindow(NativeMethods.GetDesktopWindow());
IntPtr excelHwnd = new IntPtr(Globals.ThisAddIn.Application.Hwnd);
NativeMethods.SetForegroundWindow(excelHwnd);
}
}
I have tested this to work with WebView2 version 1.0.2592.51 in a VSTO Excel add in where the WebView2 is embedded in a Custom Task Pane
Note: You might not actually need to focus the desktop hWnd before focusing Excel. This is just overkill to ensure it works. I ran into a situation where it was necessary, so I thought I'd include it.
I have run into this issue in the context of an Excel-DNA add-in for Excel (targeting .NET Framework 4.8). The add-in hosts a WebView2 inside a WinForms UserControl loadded into an Excel CTP. The WebView2 control we tested with is from this package version
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3240.44" />
We see exactly the problem raised here four years ago, and the shortcomings with the workarounds. I don’t fully understand why a click directly into the WebView2-hosted page does not set up the focus state (maybe in the Excel windows or in the WinForms UserControl) so that the click back to the sheet gives it the input focus again. I did observe that if you had other controls in the hosted UserControl, then clicking on these before the web page click does set everything up correctly.
The three suggested workarounds for this issue are all problematic:
-
Setting the environment variable before loading the WebView2 control. For example, you can add this line into your add-in’s the AutoOpen() (or other initialization routine) before the CTP is loaded.
// Environment.SetEnvironmentVariable("COREWEBVIEW2_FORCED_HOSTING_MODE", "COREWEBVIEW2_HOSTING_MODE_WINDOW_TO_VISUAL");This switches the control hosting and rendering mode to use DirectComposition (a newish feature of Windows) and this does fix the input focus issue - clicks back to the sheet work fine. But now the WebView2 content does not have correct keyboard navigation, e.g. Tab does not work. It might be possible to add some extra handlers to indicate which additional keyboard events should be handled in the WebView2. This might work with a bit of extra exploring, but its not clear from the GitHub issue messages whether this is the preferred direction from Microsoft, or an experimental fix. -
There is also an option to send an extra flag to the hosted chrome control:
CoreWebView2EnvironmentOptions options = new CoreWebView2EnvironmentOptions("--enable-features=msWebView2BrowserHitTransparent");This makes the browser clicks work without grabbing the input focus, so the browser doesn’t steal the focus from Excel, but it also does not get it for typing or shortcuts like Ctrl+C. So, this doesn’t look appropriate for most use cases. -
Another option is to register for the Excel
SheetChangeevent, and when this is received to force the input focus back to Excel, for example with aSetForegroundWindowcall as shown above. However, this does not work if the click back to Excel is onto the active cell (in which case there is noSheetChange.) I did not want to register for getting a large number of messages from the normal Excel windows on order to catch this case.
Finally, I was able to pin down a message that does get routed to the UserControl when the hosted web page gets deep-clicked. The hosting UserControl gets a WM_PARENTNOTIFY message with information about the deeper mouse clicks. We can use this message to bring focus to the UserControl, after which the interaction with the Excel sheet seems to work right. For this you need to add the override in the code below to your UserControl which is hosting the WebView2. Then just set the assignment in the first line to the correct name for the WebView2 control in your UserControl, and everything should work right. This seems like a low risk workaround, though it might well not work in future versions of the WebView control. So, if you do use it, I’d suggest adding a comment about it in the project file where the WebView2 package reference version is picked.
// When the WebView2 control is embedded in a WinForms UserControl, the focus management is not always handled correctly by the WebView2 control
// However, mouse click events that occur within the bounds of the WebView2 control can be captured by overriding the WndProc method of the UserControl
// We do this by intercepting the WM_PARENTNOTIFY message that is sent when a mouse button is pressed on a child control of the UserControl
// Adding an explicit WebView2.Focus() call at that moment sets up the right state in the hosting UserControl for future focus routing to work right
protected override void WndProc(ref Message m)
{
// TODO: Fix up this assignment to point to the embedded WebView2 control in your UserControl.
var webViewControl = this.webView;
const int WM_PARENTNOTIFY = 0x0210;
const int WM_LBUTTONDOWN = 0x0201;
const int WM_RBUTTONDOWN = 0x0204;
const int WM_MBUTTONDOWN = 0x0207;
if (m.Msg == WM_PARENTNOTIFY)
{
// Extract the event and coordinates
int childEvent = m.WParam.ToInt32() & 0xFFFF; // LOWORD - the event like WM_LBUTTONDOWN
if (childEvent == WM_LBUTTONDOWN ||
childEvent == WM_RBUTTONDOWN ||
childEvent == WM_MBUTTONDOWN)
{
int lParamInt = m.LParam.ToInt32();
int xCoord = (short)(lParamInt & 0xFFFF); // X from LOWORD(lParam)
int yCoord = (short)((lParamInt >> 16) & 0xFFFF); // Y from HIWORD(lParam)
Point clickPointInUserControlClient = new Point(xCoord, yCoord);
// TODO: Consider DPI if needed
// Check if this click point is within the bounds of our webView control
if (webViewControl != null && webViewControl.Bounds.Contains(clickPointInUserControlClient))
{
Debug.WriteLine($"UserControl.WndProc: WM_PARENTNOTIFY (WM_LBUTTONDOWN) received. Click at {clickPointInUserControlClient} is within WebView2's bounds ({webViewControl.Bounds}).");
// The click originated within the area occupied by your WebView2 control
// Here we add the extra message to set up future focus routing
if (webViewControl.CanFocus)
{
// Set focus to the WebView2 control
var focusOK = webViewControl.Focus();
if (!focusOK)
{
Debug.WriteLine("UserControl.WndProc: WebView2 control focus failed. Unexpected after CanFocus == true.");
}
}
else
{
Debug.WriteLine("UserControl.WndProc: WebView2 control cannot be focused at this time.");
}
}
else
{
// It might mean the click was on another child control of the UserControl,
// or the coordinate interpretation needs adjustment.
Debug.WriteLine($"UserControl.WndProc: WM_PARENTNOTIFY (WM_LBUTTONDOWN) at {clickPointInUserControlClient}. Not within WebView2's bounds ({webViewControl?.Bounds}).");
}
}
else
{
Debug.WriteLine($"UserControl.WndProc: Other WM_PARENTNOTIFY event received: {childEvent} at {m.LParam.ToInt32()}");
}
}
base.WndProc(ref m); // Important to call base.WndProc for other messages
}