Focus bugs with interop/WindowsFormsHost, some caused by InteropFocusTracking.SetIsEnabled

Docking/MDI for WPF Forum

Posted 6 years ago by Chien A.
Version: 17.2.0660
Platform: .NET 4.6
Environment: Windows 10 (64-bit)
Avatar

Hi, we've noticed misc focus issues including a couple that seem to be due to the InteropFocusTracking.SetIsEnabled workaround itself.

  1. Ctrl+Tab window navigator popup doesn't handle arrow key input if the active toolwindow is hosting a winforms control via WindowsFormsHost, and it has focus. You can reproduce this via the "window 1" toolwindow in Simple IDE sample when the node is selected after adding the code below and calling in Constructor.
  2. In the same modified sample, if the winform treeview has LabelEdit set to true, if you select the "A Node" node, then move focus to another toolwindow, then click back on "A Node" (toolwindow will have to have been changed to non-autohide beforehand) it seems to activate the window as well as place the node in edit, with that single click. This does not occur if InteropFocusTracking.SetIsEnabled is not performed.
  3. Something odd is also affecting a 3rd party winforms grid control of ours when it is hosted in a toolwindow - as soon as a cell is placed into edit via clicking (if edit initiated via keyboard e.g. F2 there is no issue), it seems focus is shifted elsewhere temporarily and the editor control's LostFocus event is raised. If InteropFocusTracking.SetIsEnabled is disabled, then editing is fine and it works as it did under WinForms. At the point when the cell editor control loses focus, MainWindow.IsKeyboardFocusWithin will return false, and System.Windows.Input.Keyboard.FocusedElement returns null. Calling user32 GetFocus returns a hwnd that appears to be an unmanaged control...what exactly does InteropFocusTracking.SetIsEnabled do that might be causing this?

 

        Private Sub DockTest()
            Me.dockSite.UseHostedPopups = False
            Me.dockSite.IsLiveSplittingEnabled = False

            Dim f As New System.Windows.Forms.Form
            Dim tv As New System.Windows.Forms.TreeView
            Dim tn As New System.Windows.Forms.TreeNode("A Node")

            f.FormBorderStyle = Forms.FormBorderStyle.None
            tv.Location = New Drawing.Point(0, 25)
            tv.LabelEdit = True
            tv.Nodes.Add(tn)
            tv.Dock = Forms.DockStyle.Fill
            f.Controls.Add(tv)

            Dim ts As New System.Windows.Forms.ToolStrip
            Dim tb As New System.Windows.Forms.ToolStripButton
            tb.Text = "A Button"
            tb.DisplayStyle = Forms.ToolStripItemDisplayStyle.Text

            ts.Items.Add(tb)
            ts.Dock = Forms.DockStyle.Top
            ts.Location = New Drawing.Point(0, 0)
            f.Controls.Add(ts)

            AddHandler tb.Click, Sub(ByVal sender As Object, ByVal e As EventArgs)
                                     MessageBox.Show("Hello")
                                 End Sub

            Dim wfh As New System.Windows.Forms.Integration.WindowsFormsHost
            Dim tw As New ToolWindow(dockSite, "1", "window1", Nothing, wfh)

            InteropFocusTracking.SetIsEnabled(wfh, True)
            f.Dock = Forms.DockStyle.Fill
            f.TopLevel = False
            wfh.Child = f

            tw.Activate()
            tw.Dock(tabbedMdiHost, ActiproSoftware.Windows.Controls.Side.Bottom)
            tw.AutoHide(ActiproSoftware.Windows.Controls.Side.Bottom)
        End Sub

[Modified 6 years ago]

Comments (21)

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chien,

I'm sorry but we don't have any WinForms controls in our samples.  If you'd like us to take a look at these scenarios, then please make a new simple sample project that shows them and email that to our support address, referencing this thread.  Be sure to exclude any bin/obj folders from the ZIP and rename the .zip file extension so it doesn't get spam blocked.  Thanks!

As for your third question, InteropFocusTracking will watch the attached HwndHost's LostFocus and MessageHook events.  The LostFocus handler tries to update whether the window container that hosts the tool window still should appear active.  The MessageHook handler watches several Windows API messages and takes some actions based on those to try and ensure that the container appears active, the DockSite.ActiveWindow is updated properly, and does call HwndHost.Focus() in the case of WM_MOUSEACTIVATE to ensure that WPF knows the host is focused (WPF doesn't always track focus properly and this was needed to work around another issue in the past).


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

Right, I forgot that I had previously modified that sample to test other interop issues! See above edit for code to reproduce.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chien,

Thanks for providing the sample code for items 1/2.

1) Unfortunately I'm not sure what we can do about this since KeyDown (even PreviewKeyDown) for arrow keys is consumed by your WinForms controls in this scenario. I don't see that the events even get raised for those key presses. You might have to disable the switcher functionality if you need these kinds of controls in your app since it appears we have nothing to watch.

2) I tried this both with and without the "InteropFocusTracking.SetIsEnabled" commented out and seemed to see the same thing in both cases. So I don't think our property makes a difference there. I think what you see is because you have a single item that is selected, and if you click the selected item of a TreeView it likely starts edit mode. Whereas if you had a second item in the tree and clicked that instead, it won't start edit mode since the selection changed.


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

1) Is there any way to tell if the overlay is visible and/or forward key events to it? Ideally we would want to keep this functionality.

2) This seems to be the case, either I was mistaken or there may have be more to it than that, but definitely the InteropFocusTracking.SetIsEnabled is not completely benign, and we have since noticed a related issue of 2 using the same setup:

If you start in-line editing by left-clicking the node, it is not possible to interact with the inline editor (like moving caret) via mouse - clicking anywhere on it results in editing ending. This does not happen if InteropFocusTracking is not active.

Quite possibly whatever is happening may also be behind the grid editing issues we are having in 3.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chien,

1) Switchers have an IsOpen property you could look at.  If you create a class that inherits StandardSwitcher, you could perhaps call OnKeyDown in a new public method with some mocked up event arguments to simulate arrow keys.

2) I believe we found what was triggering the left click to select not working and were able to remove the logic.  It appears that the scenario that made us originally add that code is no longer a valid problem after the 2016.1 Docking/MDI rewrite.

If you'd like to try a preview build with this change, please write our support address and reference this thread.


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

Nice work : ). As per email, the preview build appears to have resolved issue 2 and 3 now.

It's almost at the point where WinForms is being hosted pretty seemlessly, but there's a new quite problematic issue that we've found.

WindowsFormsHost has a known issue of tending to obtain focus by default either initially or on window/parent activation, and this is also the case when hosted in a toolwindow like in the previous code above. Usually a workaround is to explicitly set focus to the approparite WinForms control, but attempting to do so is causing problems with titblebar colouring for the activate toolwindow. It also causes autohidden toolwindows to hide again soon after the click on the tab to activate it.

Easiest way to reproduce this is again using the previous code, clicking on the tab of the extra autohidden toolwindow to activate/unhide it, then pressing tab to change focus from the WindowsFormsHost to the child treeview. You will notice the toolwindow's active colouring will be lost once tab is pressed, and it will autohide shortly after. If the toolwindow is docked or floating instead, then the same click on titlebar to activate, then pressing tab will cause active titlebar colouring to be lost.

It doesn't matter whether the focus change is due to tab key or Control.Focus call etc - anytime a toolwindow hosting a WindowsFormsHost has not been activated by clicking within it's WinForms client area, changing focus to a child control will result in these problems.

This time InteropFocusTracking doesn't matter. Haven't checked whether this is a regression in the preview (2017.2.0663), or an existing issue.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chien,

I believe we found some logic that helps with this scenario.  Can you write our support address and reference this thread to request a preview build, so that you can test and make sure it's working well for your needs, and didn't negatively affect anything else?


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

Thanks, the changes seem to be working pretty well. The only remaining focus quirk I'm trying to sort out is a good way to auto-correct focus of activated autohidden toolwindows from the Window.Content WIndowsFormsHost to the WindowsFormsHost's.Child. Attempting to do so by overriding the WindowsFormsHost.OnGotFocus/KeyBoardFocus or DockSite.OnWindowAutoHidePopupOpened/WindowActivated to .Focus on WindowsFormsHost.Child still results in the WindowsFormsHost ending up with focus (only if autohidden)?

This seems like a bug...at the moment am having to resort to dispatcher idle, which is obviously not a good solution.

Actually, something else that is focus related but not an interop issue is that it seems after DockSiteLayoutSerializer.LoadFromFile, if there are no document windows open, the main DockHost of the docksite ends up with focus, rather than an open toolwindow or even the parent Window if no document/toolwindows in layout at all. This also results in a dotted focus rectangle around DockSite becoming visible for us.

The latter can be reproduced by changing the Layout Serialization sample's document window to be closeable (also changed DockSite.AreDocumentWindowsDestroyedOnClose to = false in case it was loading from the document window's contents), then closing the document window before "Layout|LoadLayout". The focus rectangle doesn't seem to show in your sample application, but the DockHost does end up with input focus.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chien,

Sorry I'm not sure what to suggest with the initial focus issue.  WPF/WinForms interop focus handling is notoriously bad, and your dispatcher idle solution might be the only way to work around getting a proper control within the WindowsFormsHost to have focus.

For the other issue, we can repro that and have made this update for the next build:

Updated layout deserialization to try and focus a selected docking window if a docking window previously had focus.


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

There's no other event or override that might occur sufficiently after the autohidden toolwindow has fully opened, that might be do the job?

Also, seems there's one more interop activation/focus issue we haven't covered yet that was just brought to my attention - changing the state of a toolwindow hosting a WindowsFormsHost does not activate it (or at least it titlebar doesn't render as active).

You can reproduce this using the original modified sample code above again - simply change the state of the 'window1' toolwindow and you'll notice it doesn't get activated like the other pure wpf toolwindows (E.g. if floated, the titlebar remains inactive and not even clicking on the titlebar makes it render as active). This applies to all methods of changing state - dragging, titlebar context menu, programmatically, etc, just make sure the destination isn't already active or it'll mask the issue.

Posted 6 years ago by Chien A.
Avatar

Might also be worth noting that in the above scenario of floating a toolwindow with WindowsFormsHost Content, in addition to the floating window not being active, the focused element immediately after seems to be an instance of type ActiproSoftware.Windows.Controls.Docking.Logic.NonHostedFloatingWindow+HostingWindow.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

The AutoHidePopupOpened event fires right after we've told WPF to initialize and show the popup.  Due to the nature of WPF, sometimes the visual layout might occur at a different dispatched level.  That's the only event that fires in that case, although WindowActivated will also possibly fire if focus is set to the auto-hidden tool window.  Instead of dispatching with Idle priority, I would think an Input priority would work as that should generally occur after focus was set.

For the other issue with tracking active containers during state changes when dealing with HwndHosts, we spent a while on this and think we have some fixes in place for that. It would be nice if you could test a preview build with the updates prior to our upcoming maintenance releases. Please contact us via email if you'd like to test the preview build.


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

Thanks for that. The preview seems to have improved things a lot but it still isn't always reliable.

Using an updated demo that I will send shortly:

1) Un-autohide window2 via pin (although method probably doesn't matter). Now try dragging window1 to somewhere else (e.g. docked to inner-right of document area). You've notice immediately after initiating drag, window2 becomes active for some reason. Also, when this happens window1 will not end up the active toolwindow upon drop.

2) Change window1 and window2 to be tabbed documents. Now try floating one of them via titlebar contextmenu - the floating window will not end up active (and iirc the same NonHostedFloatingWindow+HostingWindow object ends up with input focus). Actually, same happens when they are dock-tabbed instead of document-tabbed.

Possibly more, but these are the only ones I have been able to relaibly narrow down so far.

Posted 6 years ago by Chien A.
Avatar

3) The fixes can conflict with the previously mentioned workaround to change focus from WindowsFormsHost to child WinForms control on WindowsFormsHost.Got/GotKeyBoard-Focus and in Dispatcher.Idle (for autohide). If these are in effect, floating a docked toolwindow programmatically or via titlebar context menu does not leave it active. I don't know what logic is involved here, but changing focus from the WIndowsFormsHost to a child of it should not confuse the tracking logic of active containers. Will amend the demo to show this.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hello,

Thanks for the updated samples.  We put a few more hours into trying to get this all working better.  It's tough because HwndHosts don't work with focus in the same way native WPF controls do. 

I believe we have a good solution for #1/#2 now.  I didn't see an issue in #3 (tested after other updates for #1/#2) so if you still see a problem, please give us exact steps to repro it.  I'll email you now with a new preview build.


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

Latest is excellent. After preliminary testing, just about eveything is working now, and yes it seems whatever was done for #1 and #2 also resolves #3 as well. It might have even helped with an unreported issue that I was manually working around : ).

The only remaining scenario I was able to find was the following:

4) Using our v2 demo, changing window2 to document then back to docked does not leave it active (all via titlebar context). Ditto for window1.

Make sure you're not using v3 demo, as the extra me.child.focus calls actually corrects/masks problem 4, which great to know actually as with the previous 663 preview it was frequently having the opposite effect as mentined in 3#!

Huge improvements for hosting winforms vs current!

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chien,

Great to hear... we updated the build one more time with another several tweaks for the other issue you saw.


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

Thanks, haven't tested latest extensively yet, but just wanted to alert you to a possible issue with fix for post 9 previously:

Copy and paste below for convenience as doesn't seem to be shortcuts to individual posts?:

"...something else that is focus related but not an interop issue is that it seems after DockSiteLayoutSerializer.LoadFromFile, if there are no document windows open, the main DockHost of the docksite ends up with focus, rather than an open toolwindow or even the parent Window if no document/toolwindows in layout at all. This also results in a dotted focus rectangle around DockSite becoming visible for us.

The latter can be reproduced by changing the Layout Serialization sample's document window to be closeable (also changed DockSite.AreDocumentWindowsDestroyedOnClose to = false in case it was loading from the document window's contents), then closing the document window before "Layout|LoadLayout". The focus rectangle doesn't seem to show in your sample application, but the DockHost does end up with input focus."

Is this fix in our preview builds? If so there may have been a regression as the DockHost is still ending up with focus after above scenario.

Additionally and as an extension to the above, if after closing the document window you then drag-move say the 'solution explorer' toolwindow to be a document, "Layout|save layout", and then:

a) "layout|load layout" - the dockhost also ends up with focus

b) Drag float the 'solution explorer' toolwindow, then "layout|load layout" - the layout does not appear to load correctly, with the solution explorer remaining floating and oddly contained within a normal window chrome (with its only drag target being to become a document).

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chien,

That update is in the preview build but I think there was one case that needed adjusting.  We've done so for the next build.

We've also fixed the second issue you saw in the next build.

The same preview build URL is updated with these changes now if you'd like to have a look.


Actipro Software Support

Posted 6 years ago by Chien A.
Avatar

Hi, to pick this up again, we've identified another regression that occured in the thrid 663 preview build here. This should correspond to the changes to address issues in reply 8 (it would be useful if we could link/reference individual replies in a thread).

In 663 preview 2 the treenode label editing and our original 3rd party grid editing bugs had been resolved, however it seems something in preview build 3 and after (including official 663 release) has reintroduced an issue (1) where if Toolwindow.Activate occurs during click handling, it can disrupt/end any editing also initiated after.

To reproduce, use the code modifications in original post but move the tb.Click handler after tw instantiation and change it to:

tw.Activate(true)
tv.SelectedNode.BeginEdit()

Then run the project, un-autohide the toolwindow, select the only treenode, then click on the toolbar button. Note that edit does begin but ends immediately.

In the 2nd preview build, doing the above worked properly, placing and leaving the label editor in-edit.

The reason for the Activate call is because calling tv.Focus instead does not cause active Toolwindow to change (which is arguably another issue (2)), and otherwise if the ToolWindow was not already active beforehand, due to the way toolbars do not affect focus, you could otherwise end up with a treenode etc in edit while a different toolwindow is 'active'.

On that note, a related discrepancy I noticed vs WPF Toolbars/Buttons - issue (3): clicking on a WPF ToolBar and Button causes the active window to temporarily switch to the parent DockingWindow of the ToolBar on mouse down, but returns the the previously active DockingWindow on mouse up.

Posted 6 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Chien,

By the way, we have an official build 664 out now you might want to grab, although it's Docking/MDI interop functionality is likely the same as your latest preview.

Also of note, although we don't have a link to post feature in the forum, if you look at the page's source, the "post-container" div objects have an ID which is their post ID.  You can have this page's link appended with "#postidhere" to link directly to a post.

The problem with focus calls and interop controls like WinForms is that sometimes the focus call fails and requires us to kick in a layout to get it to work.  In that case we have to dispatch the focus attempt using Send priority.  I wish WPF and interop control focus handling was implemented more reliably by Microsoft. 

What I would suggest is that you change our code to the following:

tw.Activate(True)
tw.Dispatcher.BeginInvoke(DispatcherPriority.Input, Sub()
  tv.SelectedNode.BeginEdit()
End Sub)

That works because in this scenario, we are running into a situation where the focus change from the Activate call does need it to be dispatched.  There were certain scenarios (others brought up by you) where not doing the dispatch failed to properly move focus.  Whereas doing the dispatch does get them working.  The only thing is that if you are doing your own focus moving like with BeginEdit, it must also be dispatched.

For issue 2, we are not receiving any focus notification whatsoever in InteropFocusTracking when the toolbar button is clicked.  Whereas we do get a notification when the tree node is clicked.  I'm not sure why WinForms is sending it in the one case but not the other.

For issue 3, if you are seeing a problem, please make a new ticket for this and ideally send us a new simple sample project that shows it so we can debug with that.  Be sure to remove the bin/obj folders from the ZIP and rename the .zip file extension so it doesn't get spam blocked.  Thanks!


Actipro Software Support

The latest build of this product (v24.1.2) was released 1 days ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.