Why does GetWindowRect include the title bar in my WPF window?

2k views Asked by At

I'm trying to get caret position using GetWindowRect() (and 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

For Notepad and almost anywhere else the coordinates are correctly fetched:

In my WPF (and any other WPF) window, however, GetWindowRect() decides to include the title bar and offsetting the caret top position by the height of the title bar:

Any idea why?

I tried using DwmGetWindowAttribute() as well, but it gets the same coordinates for the WPF window as GetWindowRect().

Edit:

With the answer from Brian Reichle I've been able to determine a way to get the coordinate of the client area:

[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 is the top left coordinate of the client area of the window specified by guiInfo.hwndFocus and it's always 0,0 because it's relative to the window's client area. ClientToScreen() converts the coordinates to be relative to screen instead (has to be System.Drawing.Point, System.Windows.Point won't work).

1

There are 1 answers

3
Brian Reichle On BEST ANSWER

The title bar is included because its part of the window, if you don't want the non-client area then you need to request the client area rect (GetClientRect).

The confusion from notepad is probably because you are using the window handle of the text box rather than of the window itself. Remember, WPF uses a single handle for the overall window while win32 will often (but not always) use a separate handle for each control within the window.

In a comment you mentioned that GetClientRect 'returned' 0,0, did you check if it returned true (success) or false (failure)? If it returned false, did you check the result of GetLastError()?