当 WPF 应用程序的控件获得焦点时捕获活动 window 标题失败

Capturing active window title failed when WPF application's control focused

我正在尝试使用 SetWindowsHookEx 添加到我的时间跟踪器 window 字幕跟踪,但它只部分起作用。这是我用来为订阅者提供相应事件的代码:

public class Hooks
{
    #region DllImport

    delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    [DllImport("user32.dll")]
    static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

    private const uint WINEVENT_SKIPOWNPROCESS = 2;
    private const uint WINEVENT_SKIPOWNTHREAD = 1;
    private const uint WINEVENT_OUTOFCONTEXT = 0;
    private const uint EVENT_SYSTEM_FOREGROUND = 3;

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count); 

    #endregion DllImport

    public static event EventHandler<EventArgs<string>> WindowActivated;

    public void Bind()
    {
        var listener = new WinEventDelegate(EventCallback);
        var result = SetWinEventHook(EVENT_SYSTEM_FOREGROUND,
                        EVENT_SYSTEM_FOREGROUND,
                        IntPtr.Zero,
                        listener,
                        0,
                        0,
                        (WINEVENT_OUTOFCONTEXT));
    }

    private static void EventCallback(IntPtr hWinEventHook, uint eventType, IntPtr hwnd,
                                      int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        System.Diagnostics.Debug.Write("EventCallback enter!");

        if (eventType == EVENT_SYSTEM_FOREGROUND)
        {
            var buffer = new StringBuilder(300);
            var handle = GetForegroundWindow();

            System.Diagnostics.Debug.Write(string.Format("EventCallback GetWindowText: {0}, '{1}'",
                GetWindowText(handle, buffer, 300),
                buffer));

            if (GetWindowText(handle, buffer, 300) > 0 && WindowActivated != null)
            {
                System.Diagnostics.Debug.Write(string.Format(
                    "Calling handlers for WindowActivated with buffer='{0}'",
                    buffer));
                WindowActivated(handle, new EventArgs<string>(buffer.ToString()));
            } 
        }

        System.Diagnostics.Debug.Write("EventCallback leave!");
    }
}

我有主 ui WPF 应用程序和单个 Textbox,我将 Hook 的事件绑定到文本框内容。当我 运行 它看起来工作正常,直到它自己聚焦。 IE。如果我单击 WPF window 的 texbox 变为活动挂钩,则会停止工作约 30-40 秒。在那之后事件捕获开始工作,但再次 - 直到主 WPF windows 变为活动状态。

知道它是什么以及如何解决它吗?

我使用标准 Visual Studio 模板(VS2012;.NET 4.5)创建了一个新的 WPF 应用程序。然后我替换了 XAML 和 code-behind 如下:

MainWindow.xaml

<Window x:Class="Whosebugtest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow">
    <Grid>
        <StackPanel VerticalAlignment="Top" Margin="15">
            <TextBox x:Name="_tb" />
            <Button Content="Does Nothing" HorizontalAlignment="Left" Margin="0,15" />
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace Whosebugtest
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            _hooks = new Hooks(_tb);
            _hooks.Bind();
        }

        readonly Hooks _hooks;
    }

    public class Hooks
    {
        public Hooks(TextBox textbox)
        {
            _listener = EventCallback;
            _textbox = textbox;
        }

        readonly WinEventDelegate _listener;
        readonly TextBox _textbox;
        IntPtr _result;

        public void Bind()
        {
            _result = SetWinEventHook(
                EVENT_SYSTEM_FOREGROUND,
                EVENT_SYSTEM_FOREGROUND,
                IntPtr.Zero,
                _listener,
                0,
                0,
                WINEVENT_OUTOFCONTEXT
                );
        }

        void EventCallback(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild,
            uint dwEventThread, uint dwmsEventTime)
        {
            var windowTitle = new StringBuilder(300);
            GetWindowText(hwnd, windowTitle, 300);
            _textbox.Text = windowTitle.ToString();
        }

        #region P/Invoke
        const uint WINEVENT_OUTOFCONTEXT = 0;
        const uint EVENT_SYSTEM_FOREGROUND = 3;

        [DllImport("user32.dll")]
        static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc,
            WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

        [DllImport("user32.dll")]
        static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);

        delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject,
            int idChild, uint dwEventThread, uint dwmsEventTime);
        #endregion
    }
}

对我来说,这很好用。我可以在不同的应用程序之间切换,它们的 window 标题反映在 WPF 应用程序的文本框中。即使文本框获得焦点也没有延迟。我添加了一个 do-nothing Button 控件只是为了证明无论 texbox 还是按钮被聚焦它都不会改变行为。

我只能假设您遇到的问题与您在事件处理程序中所做的事情有关,或者与某种线程问题有关。

可能的原因是委托被垃圾回收,因为它被分配到局部变量中。这就是它在 30-40 秒后停止工作的原因。