为什么GetWindowRect在我的WPF窗口中包含标题栏?

3

我正在尝试使用GetWindowRect()(和GetGUIThreadInfo())获取插入符位置:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Runtime.InteropServices;

namespace WpfApplication1
{
    public class CaretInfo
    {
        public double Width { get; private set; }
        public double Height { get; private set; }
        public double Left { get; private set; }
        public double Top { get; private set; }

        public CaretInfo(double width, double height, double left, double top)
        {
            Width = width;
            Height = height;
            Left = left;
            Top = top;
        }
    }

    public class CaretHelper
    {

        public static CaretInfo GetCaretPosition()
        {
            // Get GUI info containing caret poisition
            var guiInfo = new GUITHREADINFO();
            guiInfo.cbSize = (uint)Marshal.SizeOf(guiInfo);
            GetGUIThreadInfo(0, out guiInfo);

            // Get width/height
            double width = guiInfo.rcCaret.right - guiInfo.rcCaret.left;
            double height = guiInfo.rcCaret.bottom - guiInfo.rcCaret.top;

            // Get left/top relative to screen
            RECT rect;
            GetWindowRect(guiInfo.hwndFocus, out rect);

            double left = guiInfo.rcCaret.left + rect.left + 2;
            double top = guiInfo.rcCaret.top + rect.top + 2;


            int capacity = GetWindowTextLength(guiInfo.hwndFocus) * 2;
            StringBuilder stringBuilder = new StringBuilder(capacity);
            GetWindowText(guiInfo.hwndFocus, stringBuilder, stringBuilder.Capacity);
            Console.WriteLine("Window: " + stringBuilder);
            Console.WriteLine("Caret: " + guiInfo.rcCaret.left + ", " + guiInfo.rcCaret.top);
            Console.WriteLine("Rect : " + rect.left + ", " + rect.top);

            return new CaretInfo(width, height, left, top);
        }

        [DllImport("user32.dll", EntryPoint = "GetGUIThreadInfo")]
        public static extern bool GetGUIThreadInfo(uint tId, out GUITHREADINFO threadInfo);

        [DllImport("user32.dll")]
        public static extern bool ClientToScreen(IntPtr hWnd, out Point position);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetWindowRect(IntPtr handle, out RECT lpRect);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetClientRect(IntPtr hWnd, ref Rect rect);

        [StructLayout(LayoutKind.Sequential)]
        public struct GUITHREADINFO
        {
            public uint cbSize;
            public uint flags;
            public IntPtr hwndActive;
            public IntPtr hwndFocus;
            public IntPtr hwndCapture;
            public IntPtr hwndMenuOwner;
            public IntPtr hwndMoveSize;
            public IntPtr hwndCaret;
            public RECT rcCaret;
        };

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int GetWindowTextLe

在记事本和几乎任何其他地方,坐标都被正确获取:

但是,在我的 WPF 窗口(以及任何其他 WPF 窗口)中,GetWindowRect() 决定包括标题栏,并通过标题栏的高度偏移插入符顶部位置:

有什么想法吗?

我也尝试使用 DwmGetWindowAttribute(),但它为 WPF 窗口获得与 GetWindowRect() 相同的坐标。

编辑:

通过来自 Brian Reichle 的答案,我能够确定获取客户区坐标的方法:

[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr hWnd, ref System.Drawing.Point lpPoint);

System.Drawing.Point point = new System.Drawing.Point(0, 0);
ClientToScreen(guiInfo.hwndFocus, ref point)

0,0 是由 guiInfo.hwndFocus 指定的窗口客户区域的左上角坐标,它始终是 0,0,因为它相对于窗口的客户区域。 ClientToScreen() 将坐标转换为相对于屏幕而不是窗口(必须是 System.Drawing.PointSystem.Windows.Point 不起作用)。


1
标题栏是包含在窗口中的一部分,如果您不想要非客户区域,则需要请求客户区矩形(GetClientRect)。从记事本中的混淆可能是因为您正在使用文本框的窗口句柄而不是窗口本身的句柄。请记住,WPF对整个窗口使用单个句柄,而win32通常(但并不总是)对窗口内的每个控件使用单独的句柄。 - Brian Reichle
GetClientRect() 返回 0,0,但我认为你关于窗口句柄的正确性 - 至少我可以通过 Spy++ 来确认,所以如果你想把它作为答案放弃,我可以接受。 - Woodgnome
1个回答

8
标题栏包含在窗口内,如果你不想要非客户区域,则需要请求客户区矩形 (GetClientRect)。
可能来自记事本的混淆是因为您正在使用文本框的窗口句柄而不是窗口本身的句柄。请记住,在 WPF 中,整个窗口使用单个句柄,而 win32 通常(但不总是)对窗口内每个控件使用单独的句柄。
在评论中,您提到 GetClientRect '返回' 0,0,请检查它是否返回 true(成功)或 false(失败)。如果返回了 false,请检查 GetLastError() 的结果。

显然GetClientRect()的工作方式是正确的 - 它是相对于窗口客户区域的客户区域,因此left/top始终为0,0。要使用的正确函数是ClientToScreen()(我已更新我的问题以显示如何使用)。 - Woodgnome
1
没错。客户端矩形的左上角点始终在(0, 0)处。ClientToScreen()可以使用,或者使用更通用的MapWindowPoints。然而,@woodgnome,你的问题说你想要“插入符位置”。这段代码将为您提供窗口客户区域的屏幕坐标,但插入符可能不在左上角。 - Cody Gray
我使用 GUITHREADINFO.rcCaret 来偏移插入符号位置 - GetClientRect() 会导致一些问题,我已经用 ClientToScreen() 替换了它。 - Woodgnome

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接