-1

I am trying to make a taskbar app with WPF and I need to be able to react to windows being created and destroyed. In winforms, one way of doing this is to override the WndProc procedure to be able to receive and process Windows messages and I am trying to do the same in WPF.

After searching around and fiddling with the code for a week, I just can't get the thing to work.

I only get some messages when the app first starts up and I may get some messages only when opening a new explorer window although the message itself is never the "window created" message. Apart from that, the WndProc function is never called no matter how many windows I open, switch to or close.

I tried following the answers from Getting Wndproc events to work with WPF?, from How to handle WndProc messages in WPF? , several other websites from search results as well as the "MVVM-Compliant way" described in this blog post which is also referenced in one of the answers on stackoverflow.

By all accounts, this should work but it just doesn't. I tried it on two different computers, both running Windows 10 and the behavior is the same. WndPorc is just not called.

Here is some of the code I tried to use as well as a link to the zipped VS project:

public MainWindow()
{
    InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
// Or
// private void Window_Loaded(object sender, RoutedEventArgs e)
{
    base.OnSourceInitialized(e);

    HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
    source.AddHook(WndProc);

    // Or
    // HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
    // source.AddHook(new HwndSourceHook(WndProc));

    // Or
    // HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(Application.Current.MainWindow).Handle);
    // source.AddHook(new HwndSourceHook(WndProc));
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    System.Diagnostics.Debug.Write(string.Format("{0}; {1}; {2}; {3}; {4}",
        hwnd.ToString(), msg, wParam, lParam, handled));

    switch (wParam.ToInt32())
    {
        // For window created
        case 1:
            System.Diagnostics.Debug.Write(" window created");
            break;
    }
    System.Diagnostics.Debug.WriteLine("");
    return IntPtr.Zero;
}
user1969903
  • 810
  • 13
  • 26
  • I copied&pasted your code in an empty WPF App and I receive the "windows created" event. I assume therefore it's about your project/solution? – less Jul 15 '19 at 06:48
  • Ok, any idea what might be causing this issue on my systems? – user1969903 Jul 15 '19 at 06:57
  • Can not download your linked project – less Jul 15 '19 at 06:59
  • Should be working now. – user1969903 Jul 15 '19 at 07:01
  • I opened your sln and ran it, I get debug output's of the form "855142; 71; 0; 20312588; False 855142; 28; 1; 6388; False window created". So either I didn't understand the problem or I don't face it – less Jul 15 '19 at 07:14
  • Just to be clear, I want to be able to detect when windows of other applications are being opened. For example, if I open notepad I would like to detect that his new window was created. Not sure from your comment if what you got is the event for the wpf window itself. – user1969903 Jul 15 '19 at 07:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/196450/discussion-between-less-and-user1969903). – less Jul 15 '19 at 07:21

2 Answers2

1

Here is an example of a WPF window that handles external window being created and destroyed:

public partial class MainWindow : Window
{
    [DllImport("user32.dll", EntryPoint = "RegisterWindowMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    public static extern int RegisterWindowMessage(string lpString);

    [DllImport("user32.dll")]
    public static extern bool RegisterShellHookWindow(IntPtr handle);

    private static int _msg;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);

        IntPtr handle = new WindowInteropHelper(this).Handle;
        _msg = RegisterWindowMessage("SHELLHOOK");
        RegisterShellHookWindow(handle);
        HwndSource source = HwndSource.FromHwnd(handle);
        source.AddHook(new HwndSourceHook(WndProc));
    }

    private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == _msg)
        {
            switch (wParam.ToInt32())
            {
                case 1:
                    //window created
                    break;
                case 2:
                    //window destroyed
                    break;
            }
        }
        return IntPtr.Zero;
    }
}
mm8
  • 163,881
  • 10
  • 57
  • 88
0

The issue was the fact that I ran my tests after killing explorer.exe. I'm not sure why but if you kill that, you no longer receive any messages.

After looking at the source code for Cairo shell, I found this line which calls an undocumented Win32 function SetTaskmanWindow. After calling this function right when initializing my code, I can now receive messages from Windows again. The code would now be:

...    
base.OnSourceInitialized(e);
var handle = new WindowInteropHelper(this).Handle;
SetTaskmanWindow(handle);

HwndSource source = HwndSource.FromHwnd(handle);
source.AddHook(new HwndSourceHook(WndProc));
...
user1969903
  • 810
  • 13
  • 26