Click here to Skip to main content
15,897,968 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I am working on a WinForms app which is being migrated in parts to WPF so there is a mix of Winforms and WPF.

The problem: On a win form with a WPF control hosted via ElementHost, When focus shifts from a win forms text box to a WPF control (say text box), the Validating event of the win forms text box gets called such that the validation fails (e.Cancel = true), then instead of the focus being set to the win forms text box, the Enter, Leave and Validating event keeps on calling repeatedly on the WinForms text box. Note that i am calling a Winforms message box in the validating event and if the user presses Cancel button, the validating event is set to cancelled. In my code sample, the user presses Cancel each time and ideally the focus should go back to the originating winforms text box.

I have tried the following:

What I have tried:

The code looks like this:

Winforms
C#
private void WindowsFormTestLoad(object sender, EventArgs e)
 {
     // Host WPF UserControl in winforms
     UserControl1 ctrl = new UserControl1();
     host = new ElementHost { Dock = DockStyle.Left , Child = ctrl};
     this.panel1.Controls.Add(host);
 }

 // Textbox1 events
 private void TextBox1Enter(object sender, EventArgs e)
 {
     Debug.WriteLine("Enter: text box 1");
 }

 private void TextBox1Leave(object sender, EventArgs e)
 {
     Debug.WriteLine("Leave: text box 1");
 }

 // Show a message box with OK and Cancel buttons.
 // Clicking on Cancel simulates a validation failure (This is what happens in my application).
 private void TextBox1Validating(object sender, CancelEventArgs e)
 {
     Debug.WriteLine("Validating: text box 1");

     var dialog = System.Windows.Forms.MessageBox.Show("blah blah", "caption", MessageBoxButtons.OKCancel);
     if (dialog == DialogResult.Cancel)
     {
         e.Cancel = true;
     }
 }


WPF Control
XML
   <UserControl x:Class="WpfWinformInterop.UserControl1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
    <TextBox Name="txt1"></TextBox>
</StackPanel>


When I try to shift focus to a Winform control (say text box), the Validating event is fired only once and focus gets back to the original text box.

The debug output when switching to a win forms text box (text box 1 to text box 2) is: Leave: text box 1

Validating: text box 1

Enter: text box 1

When i try to move focus from win form to WPF the validating event on win form's text box keeps on firing.

The debug output when switching to a WPF control is:

Leave: text box 1

Validating: text box 1

Enter: text box 1

Leave: text box 1

Validating: text box 1

Enter: text box 1

Leave: text box 1

Validating: text box 1

goes on and on like this ...

I fail to understand why WPF behavior is different and what can be done to make it behave similarly to the win forms version. Any help would be greatly appreciated.
Posted
Updated 11-Jul-17 4:19am
v3

1 solution

The LostFocus events are a bit different in WPF than in Winforms as you realise. (I will get back with a link to the best explanation I've seen so far later : To bubble or tunnel basic WPF events[^] )
I've found that sometimes you get popups hanging because there is not a loss of focus when the mouse leaves the window (or the control) for various reasons.
Instead we have used the LostMouseCapture to trigger the desired behaviours, perhaps there is something you can use from this idea (sorry that it may be a bit confusing because we have a list popup part rather than something that needs to be validated in this control):

In the UserControl xaml add this
LostMouseCapture="thisControl_LostMouseCapture"

and in the code behind we've added this
private void thisControl_LostMouseCapture(object sender, MouseEventArgs e)
{
    if (Mouse.Captured != this)
    {
        if (e.OriginalSource == this)
        {
            if (Mouse.Captured == null || !IsDescendant(this, Mouse.Captured as DependencyObject))
            {
                ClosePopup();
                return;
            }
        }
        else if (IsDescendant(this, e.OriginalSource as DependencyObject))
        {
            if (PART_Popup.IsOpen && Mouse.Captured == null && GetCapture() == IntPtr.Zero)
            {
                Mouse.Capture(this, CaptureMode.SubTree);
                e.Handled = true;
                return;
            }
        }
        else
        {
            ClosePopup();
        }
    }
}

private void ClosePopup(bool killFocus = true)
{
    ResetListBoxSelection();
    PART_Popup.IsOpen = false;
    if(killFocus) Keyboard.Focus(null);
}
internal static bool IsDescendant(DependencyObject reference, DependencyObject node)
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(reference); i++)
    {
        var child = VisualTreeHelper.GetChild(reference, i);
        if (child == node) return true;
        if (IsDescendant(child, node)) return true;

        var popup = child as Popup;
        if(popup != null)
        {
            if (IsDescendant(popup.Child, node)) return true;
        }
    }
    return false;
}
public static Visual GetDescendantByType(Visual element, Type type)
{
    if (element == null)
    {
        return null;
    }
    if (element.GetType() == type)
    {
        return element;
    }
    Visual foundElement = null;
    if (element is FrameworkElement)
    {
        (element as FrameworkElement).ApplyTemplate();
    }
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
    {
        Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
        foundElement = GetDescendantByType(visual, type);
        if (foundElement != null)
        {
            break;
        }
    }
    return foundElement;
}


[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetCapture();

private void PART_EditableTextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
    var node = Keyboard.FocusedElement as DependencyObject;
    if (node == null) return;
    if (!IsDescendant(this, node)) ClosePopup(false);
}
 
Share this answer
 
v2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900