Ответ 1
Здесь другой подход: пропустите API SetWindowsHook и вместо этого используйте WinEvents, которые используют SetWinEventHook вместо этого. Они несколько похожи на переходы между окнами, поскольку в них задействована функция обратного вызова, вызываемая при определенных событиях, но WinEvents гораздо проще использовать с С#: вы можете указать, что WinEvents предоставляется "вне контекста", что означает, что события размещены вернуться к вашему собственному процессу, поэтому вам не нужна отдельная DLL. (Однако, ваш код, однако, должен запускать цикл сообщений в том же потоке, который называется SetWinEventHook.)
Оказывается, что один из типов событий, поддерживаемых WinEvent, - это событие с названием "изменение имени", которое автоматически запускается USER32 всякий раз, когда изменяется текст заголовка HWND, который, похоже, является тем, что вы ищете. (WinEvents также можно использовать для отслеживания изменений фокусировки и различных типов изменений состояния; см. MSDN для получения дополнительной информации.) Он также запускается другими элементами управления, когда их внутренний пользовательский интерфейс изменяется - например, по списку при изменении текста элемента списка, поэтому нам нужно сделать некоторую фильтрацию.
Вот пример кода, который печатает изменения названия на любом HWND на рабочем столе - вы увидите, что оно распечатывает уведомление, поскольку текст в часах на панели задач изменяется, например. Вы захотите изменить этот код, чтобы фильтровать только HWND, который вы отслеживаете в Spotify. Кроме того, этот код прослушивает имена изменений для всех процессов/потоков; вы должны получить threadID из целевого HWND, используя GetWindowThreadProcessId и только слушать события из этого потока.
Отметим также, что это что-то вроде хрупкого подхода; если Spotify изменяет способ отображения текста или изменяет его формат, вам необходимо изменить код, чтобы не отставать от его изменений.
using System;
using System.Windows;
using System.Windows.Forms;
using System.Runtime.InteropServices;
class NameChangeTracker
{
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 bool UnhookWinEvent(IntPtr hWinEventHook);
const uint EVENT_OBJECT_NAMECHANGE = 0x800C;
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 name change changes across all processes/threads on current desktop...
IntPtr hhook = SetWinEventHook(EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, IntPtr.Zero,
procDelegate, 0, 0, WINEVENT_OUTOFCONTEXT);
// MessageBox provides the necessary mesage loop that SetWinEventHook requires.
// In real-world code, use a regular message loop (GetMessage/TranslateMessage/
// DispatchMessage etc or equivalent.)
MessageBox.Show("Tracking name changes on HWNDs, close message box to exit.");
UnhookWinEvent(hhook);
}
static void WinEventProc(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
// filter out non-HWND namechanges... (eg. items within a listbox)
if(idObject != 0 || idChild != 0)
{
return;
}
Console.WriteLine("Text of hwnd changed {0:x8}", hwnd.ToInt32());
}
}