I created a hook on a process to register when its Window moves.
I use the event constant EVENT_OBJECT_LOCATIONCHANGE, which per MSDN
An object has changed location, shape, or size. The system sends this event for the following user interface elements: caret and window objects. Server applications send this event for their accessible objects.
And it works, but it also triggers on simple mouse over of the application. Can anyone explain why?
Here is an example to reproduce it:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
static class NativeMethods
{
[DllImport("user32.dll")]
public static extern System.IntPtr SetWinEventHook(uint eventMin, uint eventMax, System.IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
public delegate void WinEventDelegate(System.IntPtr hWinEventHook, uint eventType, System.IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
}
public partial class Form1 : Form
{
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
public Form1()
{
InitializeComponent();
int processId = Process.GetCurrentProcess().Id;
NativeMethods.SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, System.IntPtr.Zero, WinEventProc, (uint)processId, (uint)0, WINEVENT_OUTOFCONTEXT);
}
private void WinEventProc(System.IntPtr hWinEventHook, uint eventType, System.IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
if (hwnd == IntPtr.Zero)
{
Debug.WriteLine("Mouse moved");
}
else
{
Debug.WriteLine("Location changed");
}
}
}
}
The header file WinUser.h has this to say about EVENT_OBJECT_LOCATIONCHANGE:
* Note also that USER will generate LOCATIONCHANGE notifications for two
* non-window sys objects:
* (1) System caret
* (2) Cursor
This explains why the event fires when the cursor moves.
As Hans says in his comment, just filter the object ID by OBJID_CURSOR.
Related
In my application I need to know when the user switches the virtual Desktop, e.g. by pressing Ctrl+Win+→.
I though it'd be a great idea to do this via Hooking. I have listed an example class I wrote to test my idea. I thought when the virtual Desktop changes, I would get a callback. However, there is no callback no matter how I change the virtual Desktop.
I also wrote a test application that creates, opens, switches and closes Desktops. It works fine, but my code below detects none of the Desktop switches.
public class SwitchDesktopMonitor
{
private delegate void CreateHookDelegate();
private delegate void SetWinEventHookCallback(IntPtr hWinEventHook, uint eventType, IntPtr hWnd, uint objectId, int childId, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, SetWinEventHookCallback lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll")]
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
private IntPtr _setWinEventHook;
private readonly SetWinEventHookCallback _hookingCallback;
private readonly Window _window;
public SwitchDesktopMonitor(Window window)
{
_window = window;
_hookingCallback = (hWinEventHook, eventType, hWnd, objectId, childId, dwEventThread, dwmsEventTime) =>
{
Console.WriteLine("-> _hookingCallback - hWinEventHook = {0}, eventType = {1}, hWnd = {2}, objectId = {3}, childId = {4}, dwEventThread = {5}, dwmsEventTime = {6}",
hWinEventHook, eventType, hWnd, objectId, childId, dwEventThread, dwmsEventTime);
};
}
public void Start()
{
Console.WriteLine("{0}.Start", this);
if (_window == null || _window.Dispatcher == null)
{
return;
}
if (_window.Dispatcher.CheckAccess())
{
CreateHook();
}
else
{
_window.Dispatcher.Invoke(new CreateHookDelegate(CreateHook));
}
}
public void Stop()
{
Console.WriteLine("{0}.Stop", this);
if (_setWinEventHook != null)
{
Console.WriteLine("\tUnhookWinEvent = {0}", UnhookWinEvent(_setWinEventHook));
}
}
private void CreateHook()
{
var windowHandle = new WindowInteropHelper(_window).Handle;
uint processId;
uint threadId = GetWindowThreadProcessId(windowHandle, out processId);
Console.WriteLine("\twindowHandle = {0}, processId = {1}, threadId = {2}", windowHandle, processId, threadId);
_setWinEventHook = SetWinEventHook(0x0020, 0x0020, IntPtr.Zero, _hookingCallback, processId, threadId, 0x0000);
Console.WriteLine("\t_setWinEventHook = {0}", _setWinEventHook);
}
}
I don't have to do this way. I am thankful or other approaches. The only important thing is that I need to detect Windows Desktop switches.
You install a hook for the event EVENT_SYSTEM_DESKTOPSWITCH (0x0020). However, the term desktop here does not refer to "virtual desktops" as a feature in Windows 10 to switch between different sets of windows, but to desktops as a system concept in Windows to separate different operating environments (Normal, Logon, Screensaver).
So, you cannot detect a switching of virtual desktops this way.
Instead, use the way as shown in this open source library https://github.com/Grabacr07/VirtualDesktop which uses a currently undocumented VirtualDesktopNotificationService COM service in the Windows Shell to be notified when the current virtual desktop changes.
I'm trying to stick my form to a window of another application (let's say Microsoft Outlook). When I move the Outlook window, my form should still stick at the right-hand side of it.
At the moment, I'm monitoring Outlook's position in a while(true) loop (with a sleep()) and adjusting my form's position to it.
Here are two problems:
If the sleep() duration is too short, it takes much performance to check Outlook's position and to adjust my form that often.
If the sleep() duration is too long, my form is too slow in adjusting to Outlook (it lags).
Isn't there a native solution for this?
you have to get a hook on the process and listen to event
this should give you a good starting point
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
private const uint EVENT_SYSTEM_MOVESIZESTART = 0x000A;
private const uint EVENT_SYSTEM_MOVESIZEEND = 0x000B;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.Width = 100;
this.Height = 100;
this.TopMost = true;
int processId = Process.GetProcessesByName("OUTLOOK")[0].Id;
//this will also be triggered by mouse moving over the process windows
//NativeMethods.SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, WinEventProc, (uint)processId, (uint)0, WINEVENT_OUTOFCONTEXT);
NativeMethods.SetWinEventHook(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, IntPtr.Zero, WinEventProc, (uint)processId, (uint)0, WINEVENT_OUTOFCONTEXT);
}
private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
Rect move = new Rect();
if (eventType == EVENT_SYSTEM_MOVESIZESTART)
{
NativeMethods.GetWindowRect(hwnd, ref move);
Debug.WriteLine("EVENT_SYSTEM_MOVESIZESTART");
}
else if (eventType == EVENT_SYSTEM_MOVESIZEEND)
{
NativeMethods.GetWindowRect(hwnd, ref move);
Debug.WriteLine("EVENT_SYSTEM_MOVESIZEEND");
}
this.Left = move.Left;
this.Top = move.Top;
}
}
public struct Rect
{
public int Left { get; set; }
public int Top { get; set; }
public int Right { get; set; }
public int Bottom { get; set; }
}
static class NativeMethods
{
[DllImport("user32.dll")]
public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
}
}
You could use any of the windows hook functions WH_GETMESSAGE, WH_GETMESSAGERET, WH_SYSMSGFILTER or WH_MSGFILTER.
In this case you would be interested in WM_MOVE and WM_MOVING, where WM_MOVE is sent after the window is moved (aka, done moving), and WM_MOVING is sent while the window is moved (and so you will get alot of those).
Start here with reading:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644959(v=vs.85).aspx
The answer from Fredou was allmost the answer.
I used it in my application, but got an exception "callbackoncollecteddelegate".
To make it work its neccessary to attach the WinEventProc on a property.
...
private NativeMethods.WinEventDelegate winEventProc;
private void Form1_Load(object sender, EventArgs e)
{
this.Width = 100;
this.Height = 100;
this.TopMost = true;
int processId = Process.GetProcessesByName("OUTLOOK")[0].Id;
//this will also be triggered by mouse moving over the process windows
//NativeMethods.SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, WinEventProc, (uint)processId, (uint)0, WINEVENT_OUTOFCONTEXT);
this.winEventProc = new NativeMethods.WinEventDelegate(WinEventProc);
NativeMethods.SetWinEventHook(EVENT_SYSTEM_MOVESIZESTART, EVENT_SYSTEM_MOVESIZEEND, IntPtr.Zero, this.winEventProc, (uint)processId, (uint)0, WINEVENT_OUTOFCONTEXT);
}
...
See CallbackOnCollectedDelegate at Application.Run(new Form1())
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?
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.
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.
I have been using the following code to detect a change of active window with C# but on the odd occasion it simply doesn't register the change of an active window when it definitely occurs. In most instances it works absolutely fine. I've had a look into it but it seems to me that the only thing I can do is turn to C++ to get a reliable solution but before doing that I thought I would ask the question of the code below.
GlobalEventHook.Start();
GlobalEventHook.WinEventActive += new EventHandler(GlobalEventHook_WinEventActive); //set up the global hook events
private void GlobalEventHook_WinEventActive(object sender, EventArgs e)
{
// do whatever
}
using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace Pro
{
public static class GlobalEventHook
{
[DllImport("user32.dll")]
internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc,
WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll")]
internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);
public static event EventHandler WinEventActive = delegate { };
public static event EventHandler WinEventContentScrolled = delegate { };
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject,
int idChild, uint dwEventThread, uint dwmsEventTime);
private static WinEventDelegate dele = null;
private static IntPtr _hookID = IntPtr.Zero;
public static void Start()
{
dele = new WinEventDelegate(WinEventProc);
_hookID = SetWinEventHook(Win32API.EVENT_SYSTEM_FOREGROUND, Win32API.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, Win32API.WINEVENT_OUTOFCONTEXT);
}
public static void stop()
{
UnhookWinEvent(_hookID);
}
public static void restart()
{
_hookID = SetWinEventHook(Win32API.EVENT_SYSTEM_FOREGROUND, Win32API.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, Win32API.WINEVENT_OUTOFCONTEXT);
}
public static void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
try
{
if (eventType == Win32API.EVENT_SYSTEM_FOREGROUND)
{
if (hwnd == Win32API.GetForegroundWindow())
{
WinEventActive(null, new EventArgs());
}
}
}
catch (Exception exc)
{
}
}
For example if the user is currently running VS2008 then I want the value VS2008.
I am assuming you want to get the name of the process owning the currently focused window. With some P/Invoke:
// The GetForegroundWindow function returns a handle to the foreground window
// (the window with which the user is currently working).
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
// The GetWindowThreadProcessId function retrieves the identifier of the thread
// that created the specified window and, optionally, the identifier of the
// process that created the window.
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern Int32 GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
// Returns the name of the process owning the foreground window.
private string GetForegroundProcessName()
{
IntPtr hwnd = GetForegroundWindow();
// The foreground window can be NULL in certain circumstances,
// such as when a window is losing activation.
if (hwnd == null)
return "Unknown";
uint pid;
GetWindowThreadProcessId(hwnd, out pid);
foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses())
{
if (p.Id == pid)
return p.ProcessName;
}
return "Unknown";
}
using System;
using System.Windows;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace FGHook
{
class ForegroundTracker
{
// Delegate and imports from pinvoke.net:
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);
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern Int32 GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll")]
static extern bool UnhookWinEvent(IntPtr hWinEventHook);
// Constants from winuser.h
const uint EVENT_SYSTEM_FOREGROUND = 3;
const uint WINEVENT_OUTOFCONTEXT = 0;
// Need to ensure delegate is not collected while we're using it,
// storing it in a class field is simplest way to do this.
static WinEventDelegate procDelegate = new WinEventDelegate(WinEventProc);
public static void Main()
{
// Listen for foreground changes across all processes/threads on current desktop...
IntPtr hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero,
procDelegate, 0, 0, WINEVENT_OUTOFCONTEXT);
// MessageBox provides the necessary mesage loop that SetWinEventHook requires.
MessageBox.Show("Tracking focus, close message box to exit.");
UnhookWinEvent(hhook);
}
static void WinEventProc(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
Console.WriteLine("Foreground changed to {0:x8}", hwnd.ToInt32());
//Console.WriteLine("ObjectID changed to {0:x8}", idObject);
//Console.WriteLine("ChildID changed to {0:x8}", idChild);
GetForegroundProcessName();
}
static void GetForegroundProcessName()
{
IntPtr hwnd = GetForegroundWindow();
// The foreground window can be NULL in certain circumstances,
// such as when a window is losing activation.
if (hwnd == null)
return;
uint pid;
GetWindowThreadProcessId(hwnd, out pid);
foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses())
{
if (p.Id == pid)
{
Console.WriteLine("Pid is: {0}",pid);
Console.WriteLine("Process name is {0}",p.ProcessName);
return;
}
//return;
}
Console.WriteLine("Unknown");
}
}
}