djkher Ответов: 1

Wierd ввод / выход/проверка поведения в элементе управления WPF, размещенном в winform


Я работаю над приложением WinForms, которое частично переносится в WPF, так что существует смесь Winforms и WPF.

Проблема: На форму элемент управления WPF прошел через elementhost с, когда фокус переходит от победы формы текстовое поле в WPF контрол (скажем, текстовое поле), в Проверка событие текстового поля win forms вызывается таким образом, что проверка завершается неудачно (е.Отмена = истина), то вместо того, чтобы фокус был установлен на текстовое поле win forms, Входите, Выходите и Проверка событие продолжает многократно вызываться в текстовом поле WinForms. Обратите внимание, что я звоню Окно сообщения Winforms в событии проверки и если пользователь нажимает кнопку отмены, событие проверки устанавливается в значение отменено. В моем примере кода пользователь каждый раз нажимает кнопку Отмена, и в идеале фокус должен вернуться к исходному текстовому полю winforms.

Я попробовал следующее:

Что я уже пробовал:

Код выглядит следующим образом:

Приложения WinForms
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
   <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>


Когда я пытаюсь переключить фокус на элемент управления Winform (скажем, текстовое поле), событие проверки срабатывает только один раз, и фокус возвращается к исходному текстовому полю.

Вывод отладки при переключении на текстовое поле win forms (текстовое поле 1 - текстовое поле 2) : Оставить: текстовое поле 1

Проверка: текстовое поле 1

Введите: текстовое поле 1

Когда я пытаюсь переместить фокус с формы win на WPF, событие проверки в текстовом поле формы win продолжает срабатывать.

Выход отладки при переключении на элемент управления WPF:

Оставить: текстовое поле 1

Проверка: текстовое поле 1

Введите: текстовое поле 1

Оставить: текстовое поле 1

Проверка: текстовое поле 1

Введите: текстовое поле 1

Оставить: текстовое поле 1

Проверка: текстовое поле 1

можно продолжать и дальше ...

Я не понимаю, почему поведение WPF отличается и что можно сделать, чтобы заставить его вести себя аналогично версии win forms. Любая помощь будет очень признательна.

1 Ответов

Рейтинг:
1

Erik Rude

События LostFocus немного отличаются в WPF от Winforms, как вы понимаете. (Я вернусь со ссылкой на лучшее объяснение, которое я видел до сих пор позже : Для пузырьковых или туннельных базовых событий WPF[^] )
Я обнаружил, что иногда всплывающие окна зависают, потому что нет потери фокуса, когда мышь покидает окно (или элемент управления) по разным причинам.
Вместо этого мы использовали LostMouseCapture для запуска желаемого поведения, возможно, есть что-то, что вы можете использовать из этой идеи (извините, что это может быть немного запутанно, потому что у нас есть всплывающая часть списка, а не то, что должно быть проверено в этом элементе управления):

В пользовательского элемента управления в XAML добавьте этот

LostMouseCapture="thisControl_LostMouseCapture"

и в коде позади мы добавили это
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);
}