VSTO WPF модальные диалоги курсор не мигает в TextBox - PullRequest
2 голосов
/ 29 июня 2019

У меня есть надстройка VSTO (Excel или Word) с диалоговым окном WPF, показанным модально. Диалог имеет TextBox, на котором я должен быть изначально сфокусирован. Я могу сфокусировать его, используя различные методы, такие как классы FocusManager или Keyboard, или запросив Traversal. Или я могу смоделировать нажатие клавиши TAB через keybd_event() (user32.dll).

Проблема с любым из этих методов, поле не кажется "полностью" сфокусированным. Курсор отображается в TextBox, но он не мигает и набор текста не работает! Чтобы решить проблему, я могу либо:

  1. нажмите клавишу TAB один раз (не программно), и курсор начнет мигать, и я смогу печатать; или

  2. Нажмите Alt-Tab, чтобы переключиться на другое приложение, затем вернитесь. Снова курсор начнет мигать, и я могу напечатать.

РЕДАКТИРОВАТЬ: РЕШЕНИЕ

Базовый подход ErrCode работает надежно, с некоторыми изменениями. Во-первых, вместо того, чтобы делать его идею только один раз, я делаю это на всех окнах. Во-вторых, я открываю фиктивное окно после загрузки собственного окна, а не раньше. Наконец, я ввожу некоторые задержки, без которых подход не работает:

// Window or UserControl, works for both or any FrameworkElement technically.
public static class FocusExtensions
    {
        #region FocusElementOnLoaded Attached Behavior

        public static IInputElement GetFocusElementOnLoaded(FrameworkElement obj)
        {
            return (IInputElement)obj.GetValue(FocusElementOnLoadedProperty);
        }

        public static void SetFocusElementOnLoaded(FrameworkElement obj, IInputElement value)
        {
            obj.SetValue(FocusElementOnLoadedProperty, value);
        }

        public static readonly DependencyProperty FocusElementOnLoadedProperty =
        DependencyProperty.RegisterAttached("FocusElementOnLoaded", typeof(IInputElement), typeof(FocusExtensions), new PropertyMetadata(null, FocusElementOnLoadedChangedCallback));

        private static async void FocusElementOnLoadedChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // This cast always succeeds.
            var c = (FrameworkElement) d;
            var element = (IInputElement) e.NewValue;
            if (c != null && element != null)
            {
                if (c.IsLoaded)
                    await FocusFirst(element);
                else
                    c.Loaded += async (sender, args) =>
                        await FocusFirst(element);
            }
        }

        private static async Task FocusFirst(IInputElement firstInputElement)
        {
            var tmpWindow = new Window
            {
                Width = 0,
                Height = 0,
                Visibility = Visibility.Collapsed
            };

            tmpWindow.Loaded += async (s, e) =>
            {
                await Task.Delay(1);
                Application.Current?.Dispatcher?.Invoke(() =>
                {
                    tmpWindow.Close();
                    firstInputElement.Focus();
                });
            };

            await Task.Delay(1);
            Application.Current?.Dispatcher?.Invoke(() =>
            {
                tmpWindow.ShowDialog(); 
            });
        }

        #endregion FocusElementOnLoaded Attached Behavior
    }

Чтобы применить, в пользовательском элементе управления или окне XAML добавьте:

FocusExtensions.FocusElementOnLoaded="{Binding ElementName=MyTextBox}"

MyTextBox, конечно, должен быть производным от IInputElement.

Ответы [ 5 ]

2 голосов
/ 09 июля 2019

Заметил, что ни один из упомянутых вами методов фокусировки не является простейшим случаем Control.Focus().Так, например:

// Constructor of your window
public MyWindow()
{
    // ...
    // set any DataContext before InitializeComponent() if needed
    // ...
    InitializeComponent();
    // ...
    // add anything else you need to do within the constructor of the window
    // ...
    textbox1.Focus();   // Set focus on your text box
}

Это было проверено в проекте надстройки Excel VSTO, и оно сразу же позволяет набирать текст, чтобы перейти к выделенному текстовому полю, как только появится диалоговое окно.Используйте со своим окном Owner hack (пришлось сделать аналогичное в моем проекте).

Разница между Control.Focus () и FocusManager.SetFocusedElement ()

РЕДАКТИРОВАТЬ

Вот что я нашел после запуска проекта надстройки VSTO с нуля.Похоже, это одна из многих странностей с использованием WPF в надстройке Excel VSTO, когда вы не можете надежно заставить работать Control.Focus(), если она используется с первым когда-либо созданным окном WPF.

Обходной путь : создание фиктивного окна WPF.Закрой его.А затем создайте реальное, которое будет использовать Control.Focus().

Никогда раньше не замечал проблемы, так как у моего проекта всегда было короткое окно «Загрузка», открывалось и закрывалось перед представлением реального окна.

bool isFirstTime = true;
private void button1_Click(object sender, RibbonControlEventArgs e)
{
    if (isFirstTime)
    {
        // For some reason the Control.Focus() is unreliable for the first WPF window ever shown within the VSTO addin. :( 
        // So make a dummy one once before we open the real window...
        // NOTE: The reason why I never noticed such a problem before in my own project, is since my project always showed a short loading window that closed itself
        // before the main window is shown. So I could use Control.Focus() in the main window without issues
        var pretendLoadWindow = new Window();
        pretendLoadWindow.SizeToContent = SizeToContent.WidthAndHeight;
        pretendLoadWindow.Visibility = Visibility.Collapsed;
        pretendLoadWindow.Loaded += (_sender, _e) => pretendLoadWindow.Close();
        pretendLoadWindow.ShowDialog();
        isFirstTime = false;
    }
    var window = new Window();
    var excelHwnd = m_ExcelApplication != null ? new IntPtr(m_ExcelApplication.Hwnd) : Process.GetCurrentProcess().MainWindowHandle;
    WindowInteropHelper interopHelper = new WindowInteropHelper(window)
    {
        Owner = excelHwnd
    };
    window.Content = new UserControl1();
    window.SizeToContent = SizeToContent.WidthAndHeight;
    // FYI: just in case you have any breakpoints that switch the focus away from the Excel (to your VS IDE), then when this window is shown it won't typically get focus. Below should fix this...
    window.Loaded += (_sender, _e) => window.Focus();       
    window.ShowDialog();
}

Полный тестовый код доступен с здесь

1 голос
/ 09 июля 2019

Поле, кажется, не "полностью" сфокусировано. Курсор отображается в TextBox, но он не мигает и набор текста не работает!

Вы видите последствия неправильной работы насосов сообщений:

Excel CustomTaskPane с управлением веб-браузером - проблемы с клавиатурой / фокусом

BUG: Не могу выбрать даты на DatePicker, которые выходят за пределы плавающей надстройки VSTO

VSTO WPF ContextMenu.MenuItem Клик за пределами области задач не отображается

Ошибка связана с тем, что элементы управления, реагирующие на ввод, и сообщения void WndProc(ref Message m) неправильно фильтруются или перенаправляются в цикле отправки. Чтобы решить эту проблему, вы должны нажать на цикл сообщений, например:

protected override void WndProc(ref Message m)
{
  const int WM_PARENTNOTIFY = 528;
  if(m.Msg == WM_PARENTNOTIFY && !this.Focused)
  {
    this.Focus();
  }
  base.WndProc(ref m);
}

Это похоже на то, что @ Ян ответил в ссылке, на которую вы ссылались в своем другом вопросе ElementHost блокирует события мыши .


Вот краткое изложение поведения Ханса Пассанта:

Что никогда не является проблемой (т. Е. Часто может быть проблематичным), так это то, что вы используете насос сообщений в Excel для отправки сообщений Windows, сообщений, которые заставляют эти элементы управления реагировать на ввод. Это не так в WPF, как в Winforms, у них есть собственный цикл отправки, который фильтрует сообщения перед их доставкой в ​​окно. Ключевые вещи, которые идут не так, когда их соответствующий диспетчер не используется, такие как табуляция и быстрые нажатия клавиш.

А потом некоторые, такого рода проблемы будут вызваны тем, что Excel будет выполнять собственную фильтрацию перед отправкой сообщений. Я полагаю, что благодаря функции защиты от вредоносных программ Microsoft всегда беспокоится о программах, связанных с приложениями Office.

0 голосов
/ 09 июля 2019

Вы пробовали следующее

1 .Попробуйте установить фокус внутри Loaded Event диалога.Который будет фокусироваться после полной загрузки диалогового окна.

            private void MyWindow_Loaded(object sender, RoutedEventArgs e)
            {
                myTextBox.Focus();
            }

2 .Попробуйте настроить фокус клавиатуры для вашего элемента управления.

           Keyboard.Focus(myTextBox);
0 голосов
/ 09 июля 2019

Вместо:

var hwndOwner = (IntPtr)ExcelInterop.App.Hwnd;

попробуйте использовать:

new WindowInteropHelper(window) { Owner = Process.GetCurrentProcess().MainWindowHandle };
window.ShowDialog();
0 голосов
/ 01 июля 2019

Кажется, вам просто нужно установить владельца вашего окна WPF. Чтобы выполнить работу, вам нужно инициализировать WindowInteropHelper объектом окна WPF для этого диалогового окна. Затем вы можете получить дескриптор окна WPF (HWND) из свойства Handle и указать владельца окна WPF со свойством Owner. В следующем примере кода показано, как использовать WindowInteropHelper при размещении диалогового окна WPF в приложении Win32.

  WindowInteropHelper wih = new WindowInteropHelper(myDialog);
  wih.Owner = ownerHwnd;
  myDialog.ShowDialog();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...