Страницы

Поиск по вопросам

воскресенье, 12 мая 2019 г.

Как получить коллекцию окон под курсором мыши?

Есть WinAPI функция WindowFromPoint. Она возвращает хэндл окна под курсором. А как мне получить все окна под курсором?
На самом деле мне нужно получить второе окно под курсором, но скорее всего решение этой задачи сведется к возврату всех окон и выбору из них второго.
Полагаю, что dotNet не знает, как это сделать, но на всякий случай добавил тег языка, в котором мне это потребовалось, на случай если таки можно обойтись без WinAPI.
UPD:
Написал такую функцию, но она не всегда возвращает правильный результат (часто возвращает не то окно). Посмотрите, что может быть не так. Координаты курсора определяются верно (проверил).
public static Window SecondFromCursor() { var curPos = new System.Drawing.Point(); if (EtherDragDrop.GetCursorPos(ref curPos)) { var wRect = new Int32Rect(); var k = 0; for (IntPtr hWnd = GetTopWindow(IntPtr.Zero); hWnd != IntPtr.Zero; hWnd = GetWindow(hWnd, GW_HWNDNEXT)) { if (GetWindowRect(hWnd, ref wRect)) { if ((curPos.X>wRect.X && curPos.X<(wRect.X+wRect.Width)) && (curPos.Y > wRect.Y && curPos.Y < (wRect.Y + wRect.Height))) { k++; if (k == 2) { //Console.WriteLine($"{hWnd}: ({wRect.X}; {wRect.Y}) ({wRect.Width}; {wRect.Height}). TopHwnd ({GetTopWindow(IntPtr.Zero)})"); var hs = HwndSource.FromHwnd(hWnd); return hs?.RootVisual as Window; } } } }
} return null; }
UPD2:
Вот тестовое использование функции
var wnd = EtherApp.SecondFromCursor(); if (wnd != null) { var cp = new System.Drawing.Point(); GetCursorPos(ref cp); wnd.Title = $"({cp.X}; {cp.Y})"; }
Происходит при перемещении окна (LocationChanged). Так вот, я перемещаю одно окно над другим (этого же приложения). В результате ожидаю, на нижнем окне изменение заголовка на координаты курсора. Окно занимает большую часть экрана. Двигаю в одной его области и меняется его заголовок. Двигаю в другой области, меняется заголовок того окна, которое двигаю (то есть верхнего). Двигаю еще по каким то местам и ничего не меняется. В консоль выводятся при этом неизвестные мне хэндлы (если раскомментировать вывод в рабочей функции). Я не могу найти закономерность.
UPD3:
Еще раз перечитал описание функции GetWindowRect. Она возвращает две координаты точек, а я их использовал как координату левой верхней и размеры. Исправил, но проблему это не решило. Координаты окон вычисляются теперь верно, но почему функция иногда возвращает какие то другие окна или самое верхнее окно (то окно, которое я двигаю), я не понимаю. У нее в логике прописано, что она должна возвращает результат только когда k==2
UPD4:
Когда мне возвращается верхнее окно (которое я перемещаю), его hWnd при этом отличается от того, что получено при помощи GetTopWindow. Получается, что на одно и то же визуальное окно ссылаются разные hWnd? Как так?


Ответ

В консоль выводятся при этом неизвестные мне хэндлы
Многие из этих окон невидимы или размер у них 1х1.
Для того, чтобы получить список всех окон под курсором, надо у WinAPI функции WindowFromPoint получить hwnd, а затем вызывать GetWindow. При этом надо для каждого окна получить его границы, т.е. Rectangle и выбрать только те, окна у которых границы пересекаются с границами окна под курсором мыши.
Ниже пример приложения TEST, в котором выводится список окон под курсором мыши.

// Microsoft (R) Roslyn C# Compiler version 1.1.0.51204 #r "System.Windows.Forms"
using System.Windows.Forms; using System.Runtime.InteropServices; using System.Drawing;
enum Gw : uint { GW_HWNDNEXT = 2, GW_HWNDPREV = 3 } [DllImport("user32.dll", SetLastError = true)] static extern IntPtr GetWindow(IntPtr hWnd, Gw uCmd); [DllImport("user32.dll")] static extern IntPtr WindowFromPoint(System.Drawing.Point p); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] static extern int GetWindowTextLength(IntPtr hWnd); static string GetTitle(IntPtr hwnd) { var sb = new StringBuilder(GetWindowTextLength(hwnd) * 2); GetWindowText(hwnd, sb, sb.Capacity); return sb.ToString(); } [StructLayout(LayoutKind.Sequential)] struct RECT { public int Left; public int Top; public int Right; public int Bottom; } [DllImport("user32.dll", SetLastError = true)] static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); static Rectangle GetRectangle(IntPtr hwnd) { var r = new RECT(); GetWindowRect(hwnd, out r); return Rectangle.FromLTRB(r.Left, r.Top, r.Right, r.Bottom); } [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool IsWindowVisible(IntPtr hWnd);

static IEnumerable GetWindows(IntPtr hwnd, Gw gw) { for (var h = hwnd; h != IntPtr.Zero; h = GetWindow(h, gw)) yield return h; }
class Info { public Info(IntPtr hwnd) { this.Hwnd = hwnd; } public Rectangle Bounds { get { return GetRectangle(this.Hwnd); } } public IntPtr Hwnd; public string Title { get { return GetTitle(this.Hwnd); } } public bool Visible { get { return IsWindowVisible(this.Hwnd); } } public override string ToString() { return String.Format("{0,15}\t{1,-50}\t{2}", this.Hwnd, this.Bounds, this.Title); } }

var frm = new Form() { Width = 800, Height = 500, TopMost = true, Text = "TEST" }; var rtb = new RichTextBox() { Parent = frm, Dock = DockStyle.Fill, WordWrap = false }; new System.Windows.Forms.Timer() { Interval = 500, Enabled = true } .Tick += (s, e) => { var pos = Cursor.Position; var i = new Info(WindowFromPoint(pos)); var p = GetWindows(i.Hwnd, Gw.GW_HWNDNEXT) .Select(h => new Info(h)) .Where(v => v.Visible && Control.FromHandle(v.Hwnd) != rtb) .Where(v => !v.Bounds.IsEmpty && v.Bounds.IntersectsWith(i.Bounds)); rtb.Text = String.Join("
", p); }; frm.ShowDialog();

Для компиляции кода и запуска приложения, например, в Visual Studio Community 2015 надо открыть View - Other Windows - C# Interactive, скопировать в него код и нажать Enter.
Visual Studio Community 2015 - бесплатная версия, ее можно скачать тут

Комментариев нет:

Отправить комментарий