Есть 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 - бесплатная версия, ее можно скачать тут