VSTO WPF模态对话框光标在TextBox中不闪烁

时间:2019-06-29 20:43:30

标签: c# .net wpf vsto

我有一个带有WPF对话框窗口的VSTO(Excel或Word)插件,以模态显示。对话框中有一个TextBox,我首先需要重点关注。我可以使用FocusManagerKeyboard类之类的各种方法,或者通过请求Traversal使其集中精力。或者,我可以通过keybd_event()(user32.dll)模拟TAB按键。

这些方法中的任何一个都有问题,该领域似乎没有“完全”集中。光标显示在“文本框”中,但它没有闪烁,因此无法输入!要解决该问题,我可以:

  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的UserControl或Window中添加:

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

MyTextBox当然必须从IInputElement派生。

5 个答案:

答案 0 :(得分:2)

注意到您提到的所有聚焦方法似乎都不是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加载项项目中进行了测试,并且一旦显示该对话框,便可以立即键入以转到焦点突出的文本框。与您的窗口所有者hack一起使用(必须在我的项目中做类似的事情)。

Difference between Control.Focus() and FocusManager.SetFocusedElement()

编辑

这是我从头启动VSTO外接程序项目后发现的内容。这似乎是在Excel VSTO加载项中使用WPF的许多问题之一,如果将Control.Focus()与第一个WPF窗口一起使用,则无法可靠地使Control.Focus()正常工作已创建。

解决方法:创建一个虚拟WPF窗口。关闭它。然后创建将使用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(); } 的真实对象。

以前从来没有注意到过这个问题,因为我的项目总是在显示真实窗口之前打开并关闭一个短的“正在加载”窗口。

<number-input ng-model="vm.currencyVal" ng-change="vm.mychangeShovi()"
              ng-class="vm.currencyValMsg">
</number-input>

可从here访问完整的测试代码

答案 1 :(得分:1)

  

该字段似乎没有“完全”聚焦。光标显示在“文本框”中,但它没有闪烁,并且无法输入文字!

您看到消息泵行为异常的后果:

Excel CustomTaskPane with WebBrowser control - keyboard/focus issues

BUG: Cant choose dates on a DatePicker that fall outside a floating VSTO Add-In

VSTO WPF ContextMenu.MenuItem Click outside a TaskPane not raised

该错误与响应输入的控件以及在分发循环中错误地过滤或重定向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);
}

这类似于您在其他问题@Ian answered中引用的链接中的ElementHost blocks mouse events


以下是Hans Passant的行为摘要:

  

永远不会出现问题(即经常会出问题)是,您依靠Excel中的消息泵来分发Windows消息,使这些控件响应输入的消息。在WPF中,这与Winforms一样多,它们有自己的分派循环,可以在将消息传递到窗口之前对其进行过滤。不使用各自的调度程序时出现问题的关键是制表键和快捷键之类的东西。

     

然后,这种问题可能是由于Excel在分派消息之前进行了自己的筛选而引起的。我猜想它具有反恶意软件功能,微软永远担心程序会与Office应用程序发生混乱。

答案 2 :(得分:0)

似乎您只需要设置WPF窗口的所有者即可。要完成工作,您需要使用对话框的WPF窗口对象初始化WindowInteropHelper。然后,您可以从Handle属性获取WPF窗口的句柄(HWND),并使用Owner属性指定WPF窗口的所有者。以下代码示例显示了在Win32应用程序中承载WPF对话框时如何使用WindowInteropHelper

  WindowInteropHelper wih = new WindowInteropHelper(myDialog);
  wih.Owner = ownerHwnd;
  myDialog.ShowDialog();

答案 3 :(得分:0)

代替:

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

尝试使用:

new WindowInteropHelper(window) { Owner = Process.GetCurrentProcess().MainWindowHandle };
window.ShowDialog();

答案 4 :(得分:0)

您是否尝试过以下

1 。尝试将焦点设置在对话框的Loaded Event中。哪个会做到  对话窗口完全加载后将焦点对准。

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

2 。尝试为控件设置键盘焦点。

           Keyboard.Focus(myTextBox);