【问题标题】:Why does GetWindowRect include the title bar in my WPF window?为什么 GetWindowRect 在我的 WPF 窗口中包含标题栏?
【发布时间】:2017-05-16 16:44:43
【问题描述】:

我正在尝试使用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,0guiInfo.hwndFocus 指定的窗口客户区的左上角坐标,它始终是0,0,因为它是相对于窗口的客户区的。 ClientToScreen() 将坐标转换为相对于屏幕的坐标(必须为 System.Drawing.PointSystem.Windows.Point 将不起作用)。

【问题讨论】:

  • 包含标题栏是因为它是窗口的一部分,如果您不想要非客户区,那么您需要请求客户区矩形(GetClientRect)。记事本的混乱可能是因为您使用的是文本框的窗口句柄而不是窗口本身。请记住 WPF 对整个窗口使用单个句柄,而 win32 经常(但不总是)为窗口中的每个控件使用单独的句柄。
  • GetClientRect() 返回 0,0,但我认为您对窗口句柄是正确的 - 我至少可以用 Spy++ 确认,所以如果您想放弃它作为答案,我可以接受.

标签: c# wpf pinvoke caret


【解决方案1】:

包含标题栏是因为它是窗口的一部分,如果您不想要非客户区,那么您需要请求客户区 rect (GetClientRect)。

记事本的混乱可能是因为您使用的是文本框的窗口句柄而不是窗口本身。请记住,WPF 为整个窗口使用单个句柄,而 win32 经常(但不总是)为窗口中的每个控件使用单独的句柄。

在你提到GetClientRect'returned'0,0的评论中,你检查它是返回true(成功)还是false(失败)?如果返回false,你检查GetLastError()的结果了吗?

【讨论】:

  • 显然GetClientRect() 正在按预期工作-它与窗口客户区的客户区相关,因此left/top 始终为0,0。正确使用的函数是ClientToScreen()(我已经更新了我的问题以说明如何使用)。
  • 没错。客户矩形的左上角始终位于 (0, 0)。 ClientToScreen() 可以使用,或者使用更通用的MapWindowPoints。但是,@woodgnome,您的问题是您想要“插入符号位置”。此代码将为您提供窗口客户区的屏幕坐标,但插入符号可能不在左上角。
  • 我使用 GUITHREADINFO.rcCaret 来抵消插入符号的位置 - GetClientRect() 只是给我带来了一些问题,我已将其替换为 ClientToScreen()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-09
  • 2013-08-05
  • 2017-10-25
  • 2013-03-03
  • 1970-01-01
  • 2016-08-31
  • 2012-02-19
相关资源
最近更新 更多