0

I am trying to add to my time tracker window caption tracking using SetWindowsHookEx, but it is works only partially. Here is my code I using to provide subscribers with coresponding event:

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

I have main ui WPF application with single Textbox and I bind Hook's event to text box content. When I run it it looks work fine, until itself focused. I.e. if I clicked texbox of WPF window becomes active hook stops working for about 30-40 seconds. After that events capturing becomes work, but again - until main WPF windows becomes active.

Any ideas what is it and how to fix it?

Alex G.P.
  • 9,609
  • 6
  • 46
  • 81
  • Do you need to track your own WPF application? If not, couldn't you just filter it out by passing `WINEVENT_SKIPOWNPROCESS` to the `SetWinEventHook` call, or by comparing the `hwnd` received by the callback function against your own window's handle. – Steven Rands Jan 14 '15 at 10:45
  • @Steven Rands I tested it with flag you specified. It changed nothing. – Alex G.P. Jan 14 '15 at 10:54
  • How are you setting the flags? You need to pass them like this: `WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS`. I've tried that with a test WPF app here, and it definitely stops the `EventCallback` method from being called when the app itself becomes the foreground window. – Steven Rands Jan 14 '15 at 11:13
  • tested it one more time just now. Compiling app, running it, checks that VS title displayed in textbox when switching to it. After that clicking on application window and after that textbox not refreshed event if I switching to another window (FAR, VS, Notepad, etc). That is the issue. – Alex G.P. Jan 14 '15 at 11:42

2 Answers2

0

I created a new WPF application using the standard Visual Studio template (VS2012; .NET 4.5). I then replaced the XAML and code-behind as follows:

MainWindow.xaml

<Window x:Class="stackoverflowtest.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 stackoverflowtest
{
    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
    }
}

For me, this works fine. I can switch between different applications and their window titles are reflected in the textbox of the WPF app. There is no delay even when the textbox is focused. I added a do-nothing Button control just to prove that whether the texbox or the button is focused it does not change the behaviour.

I can only assume the problem you are having is related to something you are doing in your event handler, or failing that some kind of threading issue.

Steven Rands
  • 5,160
  • 3
  • 27
  • 56
  • Yes, your sample works. It looks like issue cause by different assemblies for UI and for core. Hooks class placed in Core assembly. That is the problem. – Alex G.P. Jan 14 '15 at 16:45
0

Probably the reason is that the delegate was garbage collected since it is assigned into a local variable. That's why it stopped to work after 30-40 sec.

SergeyT
  • 800
  • 8
  • 15