#c_sharp #winapi
Есть 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х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 IEnumerableGetWindows(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("\n", p); }; frm.ShowDialog(); Для компиляции кода и запуска приложения, например, в Visual Studio Community 2015 надо открыть View - Other Windows - C# Interactive, скопировать в него код и нажать Enter. Visual Studio Community 2015 - бесплатная версия, ее можно скачать тут. Ответ 2
Сделал так. Напомню, мне нужно было при перетаскивании окна получить другое окно моего приложения, если положение курсора находится над ним, исключая само перетаскиваемое окно, но при этом не перекрыто другими окнами (других приложений). public static Window GetSecondUnderWindow(Window firstWindow, System.Drawing.Point cursorPos) { var startHwnd = ((HwndSource)PresentationSource.FromVisual(firstWindow)).Handle; var k = 0; var wRect = new Int32Rect(); for (IntPtr hWnd = startHwnd; hWnd != IntPtr.Zero; hWnd = GetWindow(hWnd, GW_HWNDNEXT)) { if (GetWindowRect(hWnd, ref wRect)) { if ((cursorPos.X > wRect.X && cursorPos.X < wRect.Width) && (cursorPos.Y > wRect.Y && cursorPos.Y < wRect.Height)) { k++; if (k == 2) { var hs = HwndSource.FromHwnd(hWnd); return hs?.RootVisual as Window; } } } } return null; } Передаю в функцию перетаскиваемое окно и глобальное положение курсора.
Комментариев нет:
Отправить комментарий