【问题标题】:VSTO WPF modal dialogs cursor doesn't blink in TextBoxVSTO WPF 模式对话框光标在 TextBox 中不闪烁
【发布时间】:2019-06-29 20:43:30
【问题描述】:

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

问题在于这些方法中的任何一种,该领域似乎都没有“完全”集中。光标显示在 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

【问题讨论】:

  • 您使用哪个代码来显示您的 WPF 对话框窗口?
  • 我将所有者设置为 Excel 主窗口,然后只是 .ShowDialog()。
  • 我可以看看你使用的完整代码吗?
  • @ChrisBordeman 如果没有minimal reproducible example 来阐明您的具体问题或其他详细信息以准确突出您需要什么,就很难重现问题,从而更好地理解所询问的内容。
  • 代码已添加。 XAML 没什么特别的,发生在所有窗口上。

标签: c# .net wpf vsto


【解决方案1】:

注意到您提到的所有焦点方法似乎都不是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 加载项项目中测试的,一旦显示对话框,它就会立即允许键入转到焦点文本框。与您的窗口所有者黑客一起使用(必须在我的项目中做类似的事情)。

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

编辑

这是我从头开始启动 VSTO 加载项项目后发现的内容。它似乎是在 Excel VSTO 加载项中使用 WPF 的许多 怪癖之一,如果将 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();
}

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

【讨论】:

  • 不走运,我已经尝试了各种方法来聚焦人类已知的东西。我可以让文本框显示为焦点,键盘光标在框内,只是不闪烁。如果您键入的文本不会进入 Excel,它也不会进入文本框,直到您按 TAB,然后它开始闪烁。
  • @ChrisBordeman 如果你没有问这个,我永远不会调查并意识到这个怪癖。我只是假设一切都按预期工作,因为我总是有一个启动加载窗口显示。无论如何检查我的编辑!希望对您有所帮助。
  • 这是个好主意,但遗憾的是,它不适用于我的情况。在此之前,我实际上也打开了一个加载窗口。尽管如此,我还是尝试像您的代码一样打开一个隐藏窗口,但没有任何效果。然后我尝试在我自己的窗口加载(之后)上打开假窗口,仍然没有运气。 VSTO + WPF 真的很古怪!
  • 这很不幸。如果这与以下任何因素有关,我不会感到惊讶:.Net 框架版本(我安装了 4.6.2)/VSTO 版本/Office 版本。至少确保你的 Office 有最新的补丁可能值得。
  • 有效!我在我自己的窗口之后打开虚拟窗口(每次不仅仅是一次),我必须引入异步 1ms 延迟,但我让它工作了。谢谢 ErrCode。
【解决方案2】:

该领域似乎没有“完全”集中。光标显示在 TextBox 中,但不闪烁且无法输入!

您正在看到消息泵行为不端的影响:

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);
}

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


以下是 Hans Passant 的行为总结:

永远不会有问题(即经常会出现问题)是您依赖 Excel 中的消息泵来发送 Windows 消息,这些消息使这些控件响应输入。这在 WPF 中和 Winforms 一样出错,它们有自己的调度循环,可以在消息传递到窗口之前过滤消息。在不使用各自的调度程序时出现问题的关键是 Tab 键和快捷键。

然后一些,这种问题将由 Excel 在发送消息之前进行自己的过滤引起。我猜是反恶意软件功能,微软一直担心程序会干扰 Office 应用程序。

【讨论】:

  • 如何在 WPF 而不是 WinForms 中做到这一点?
  • 您可以通过覆盖 WndProc 方法来实现。让我找一个例子,你从内存中把它添加到你的 AddIn.cs 中?
  • 谢谢,我能够注册一个处理程序,但msg == WM_PARENTNOTIFY 从来都不是真的,所以整个方法都行不通。我在 Loaded() 上执行了代码,因为在此之前窗口的句柄为 0。
【解决方案3】:

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

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

【讨论】:

  • 我会添加代码,但本质上是你在做的。
【解决方案4】:

代替:

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

尝试使用:

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

【讨论】:

    【解决方案5】:

    你有没有尝试过以下

    1。尝试将焦点设置在对话的Loaded Event 内。哪个会成功 对话窗口完全加载后聚焦。

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

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

               Keyboard.Focus(myTextBox);
    

    【讨论】:

    • 是的,我试过了,并且使用了 FocusManager。并将窗口设置为焦点上下文。没有一个能解决这个奇怪的问题。奇怪的是,在 TextBox 中可以看到闪烁的竖线,只是没有闪烁!
    猜你喜欢
    • 1970-01-01
    • 2016-01-26
    • 2023-03-26
    • 2011-12-22
    • 1970-01-01
    • 2018-01-04
    • 2010-10-10
    • 2010-12-03
    相关资源
    最近更新 更多