Drawing text inverting the existing colour [duplicate] - c#

I have a ProgressBar control like the following two:
The first is painted properly. As you can see, the second only has one 0, it's supposed to have two but the other cannot be seen because ProgressBar's ForeColor is the same as the TextColor. Is there a way I can paint the text in black when the ProgressBar below is painted in Lime and paint the text in Lime when the background is black?

You can first draw the background and text, then draw the foreground lime rectangle using PatBlt method with PATINVERT parameter to combine foreground drawing with background drawing:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class MyProgressBar : Control
{
public MyProgressBar()
{
DoubleBuffered = true;
Minimum = 0; Maximum = 100; Value = 50;
}
public int Minimum { get; set; }
public int Maximum { get; set; }
public int Value { get; set; }
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Draw(e.Graphics);
}
private void Draw(Graphics g)
{
var r = this.ClientRectangle;
using (var b = new SolidBrush(this.BackColor))
g.FillRectangle(b, r);
TextRenderer.DrawText(g, this.Value.ToString(), this.Font, r, this.ForeColor);
var hdc = g.GetHdc();
var c = this.ForeColor;
var hbrush = CreateSolidBrush(((c.R | (c.G << 8)) | (c.B << 16)));
var phbrush = SelectObject(hdc, hbrush);
PatBlt(hdc, r.Left, r.Y, (Value * r.Width / Maximum), r.Height, PATINVERT);
SelectObject(hdc, phbrush);
DeleteObject(hbrush);
g.ReleaseHdc(hdc);
}
public const int PATINVERT = 0x005A0049;
[DllImport("gdi32.dll")]
public static extern bool PatBlt(IntPtr hdc, int nXLeft, int nYLeft,
int nWidth, int nHeight, int dwRop);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateSolidBrush(int crColor);
}
Note: The controls is just for demonstrating the paint logic. For a real world application, you need to add some validation on Minimum, Maximum and Value properties.

Related

How do I avoid a generic error in GDI+ when creating a custom cursor

I am using the following code to generate a bespoke cursor dependent on the mouse position inside a win forms control. The cursor becomes a line that points towards the center of the control. Everything works very well for a few seconds and then I get the very unhelpful message :-
Exception thrown: 'System.Runtime.InteropServices.ExternalException' in System.Drawing.Common.dll
An exception of type 'System.Runtime.InteropServices.ExternalException' occurred in System.Drawing.Common.dll but was not handled in user code
A generic error occurred in GDI+.
This error appears to be connected to the garbage collector trying to clean up the pointer while it is still in use (though it could be something else). As you will see I have tried to make the pointer a property so that it isn't cleaned up but that doesn't seem to help.
Any ideas on how to avoid this error would be very welcome.
public struct IconInfo
{
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
public partial class CursorTest : UserControl
{
public CursorTest()
{
InitializeComponent();
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);
[DllImport("user32.dll")]
static extern IntPtr CreateIconIndirect(ref IconInfo icon);
[DllImport("user32.dll")]
private static extern bool DestroyIcon(IntPtr hIcon);
bool IsBusy { get; set; } = false;
IntPtr ptr { get; set; }
/// <summary>
/// Create a 32x32 cursor from a bitmap, with the hot spot in the middle
/// </summary>
public void CreateCursor(Bitmap bmp)
{
ptr = bmp.GetHicon();
IconInfo tmp = new IconInfo();
GetIconInfo(ptr, ref tmp);
tmp.xHotspot = 16;
tmp.yHotspot = 16;
tmp.fIcon = false;
ptr = CreateIconIndirect(ref tmp);
this.Cursor = new Cursor(ptr);// Error Happens here
DestroyIcon(ptr);
}
private void GenerateCursorFromPostion(Point e)
{
if (IsBusy)
{
System.Diagnostics.Trace.WriteLine("Busy!!!");
return;
}
IsBusy = true;
float x = 16 * (Width / 2.0f - e.X);
float y = 16 * (Height / 2.0f - e.Y);
PointF st = new PointF(x + 16, y + 16);
PointF ed = new PointF(16 - x, 16 - y);
Bitmap bmp = new Bitmap(32, 32);
Graphics g = Graphics.FromImage(bmp);
g.DrawLine(Pens.Black, st, ed);
CreateCursor(bmp);
g.Dispose();
bmp.Dispose();
IsBusy = false;
}
private void CursorTest_MouseMove(object sender, MouseEventArgs e)
{
GenerateCursorFromPostion(e.Location);
}
}
You have a number of issues with your current code.
Your primary issue appears to be that you are destroying the icon for the cursor. When you create a Cursor using a handle, the handle is not copied, but used directly. Instead, you need to dispose it either when the control is being disposed, or when the cursor is replaced with another.
You also need to dispose the handle from GetHicon, and the Bitmap and Graphics needs a using, and you need to handle various errors.
public partial class CursorTest : UserControl
{
public CursorTest()
{
InitializeComponent();
}
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetIconInfo(IntPtr hIcon, out IconInfo pIconInfo);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr CreateIconIndirect(in IconInfo icon);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool DestroyIcon(IntPtr hIcon);
bool IsBusy { get; set; } = false;
IntPtr ptr { get; set; }
protected override Dispose(bool disposing)
{
base.Dispose(disposing);
DestroyIcon(ptr);
}
/// <summary>
/// Create a 32x32 cursor from a bitmap, with the hot spot in the middle
/// </summary>
public void CreateCursor(Bitmap bmp)
{
var original = IntPtr.Zero;
try
{
original = bmp.GetHicon();
if(!GetIconInfo(original, out var tmp))
throw new Win32Exception(Marshal.GetLastWin32Error());
tmp.xHotspot = 16;
tmp.yHotspot = 16;
tmp.fIcon = false;
var newPtr = CreateIconIndirect(in tmp);
if (newPtr != IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error());
this.Cursor = new Cursor(newPtr);
DestroyIcon(ptr);
ptr = newPtr;
}
finally
{
if(original != IntPtr.Zero)
DestroyIcon(original);
}
}
private void GenerateCursorFromPostion(Point e)
{
if (IsBusy)
{
System.Diagnostics.Trace.WriteLine("Busy!!!");
return;
}
IsBusy = true;
float x = 16 * (Width / 2.0f - e.X);
float y = 16 * (Height / 2.0f - e.Y);
PointF st = new PointF(x + 16, y + 16);
PointF ed = new PointF(16 - x, 16 - y);
using (Bitmap bmp = new Bitmap(32, 32))
{
using (Graphics g = Graphics.FromImage(bmp))
g.DrawLine(Pens.Black, st, ed);
CreateCursor(bmp);
}
IsBusy = false;
}
private void CursorTest_MouseMove(object sender, MouseEventArgs e)
{
GenerateCursorFromPostion(e.Location);
}
}

Unexpected behavior while rendering Custom Controls(Form) C#

Introduction
I am developing custom controls [Here: Form] with custom 3D shape and PathGradient colors.
everything is working very smoothly and even I had achieved what I exactly want
And the whole code to generate this form is given below :-
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.ComponentModel;
namespace CustomControls
{
public class SplashFORM : Form
{
[Description("If True then user can close the form by pressing Alt + F4 while it is focused.")]
public bool CanClose { get; set; } = false;
private readonly Timer Drawer = new Timer();
public SplashFORM()
{
FormBorderStyle = FormBorderStyle.None;
StartPosition = FormStartPosition.CenterScreen;
}
protected override void OnFormClosing(FormClosingEventArgs FCEArgs)
{
if (!CanClose) { FCEArgs.Cancel = true; return; }
base.OnFormClosing(FCEArgs);
}
protected override void OnResize(EventArgs e)
{
Invalidate();
base.OnResize(e);
}
protected override void OnLoad(EventArgs EArgs)
{
if (!DesignMode)
{
DrawForm(null, null);
}
base.OnLoad(EArgs);
}
private void DrawForm(object _, EventArgs __)
{
using (Bitmap BackIMG = new Bitmap(Width, Height))
{
using (Graphics Gfx = Graphics.FromImage(BackIMG))
{
Gfx.SmoothingMode = SmoothingMode.HighQuality;
FillRoundedRectangle(Gfx);
foreach (Control C in Controls)
{
using (Bitmap BitMP = new Bitmap(C.Width, C.Height))
{
Rectangle Rect = new Rectangle(0, 0, C.Width, C.Height);
C.DrawToBitmap(BitMP, Rect);
Gfx.DrawImage(BitMP, C.Location);
}
}
SetBitmap(BackIMG, Left, Top, Handle);
}
}
}
protected override void OnPaint(PaintEventArgs PEArgs)
{
if (DesignMode)
{
Graphics Gfx = PEArgs.Graphics;
Gfx.SmoothingMode = SmoothingMode.HighQuality;
FillRoundedRectangle(Gfx);
}
base.OnPaint(PEArgs);
}
protected override CreateParams CreateParams
{
get
{
CreateParams Params = base.CreateParams;
Params.ClassStyle = 0x00020000;
Params.Style |= 0x00020000;
if (!DesignMode) { Params.ExStyle |= 0x00080000; }
return Params;
}
}
protected override void OnPaintBackground(PaintEventArgs PEArgs)
{
using (Brush GPBrush = Helper.Get_SplashBrush(new Rectangle(0, 0, Width - 1, Height - 1)))
{ PEArgs.Graphics.FillRectangle(GPBrush, ClientRectangle); }
}
private void SetBitmap(Bitmap BitMP, int CLeft, int CTop, IntPtr CHndl)
{
if (BitMP.PixelFormat != PixelFormat.Format32bppArgb) throw new ApplicationException("The BitMP must be 32ppp with alpha-channel.");
IntPtr ScrnDC = Win32.GetDC(IntPtr.Zero);
IntPtr MemDC = Win32.CreateCompatibleDC(ScrnDC);
IntPtr HBitMP = IntPtr.Zero;
IntPtr OBitMP = IntPtr.Zero;
byte OPCity = 255;
try
{
HBitMP = BitMP.GetHbitmap(Color.FromArgb(0));
OBitMP = Win32.SelectObject(MemDC, HBitMP);
Win32.Size _Size = new Win32.Size(BitMP.Width, BitMP.Height);
Win32.Point _PointSource = new Win32.Point(0, 0);
Win32.Point _TopPos = new Win32.Point(CLeft, CTop);
Win32.BLENDFUNCTION _Blend = new Win32.BLENDFUNCTION
{
BlendOp = Win32.AC_SRC_OVER,
BlendFlags = 0,
SourceConstantAlpha = OPCity,
AlphaFormat = Win32.AC_SRC_ALPHA
};
Win32.UpdateLayeredWindow(CHndl, ScrnDC, ref _TopPos, ref _Size, MemDC, ref _PointSource, 0, ref _Blend, Win32.ULW_ALPHA);
}
finally
{
Win32.ReleaseDC(IntPtr.Zero, ScrnDC);
if (HBitMP != IntPtr.Zero)
{
Win32.SelectObject(MemDC, OBitMP);
Win32.DeleteObject(HBitMP);
}
Win32.DeleteDC(MemDC);
}
}
private GraphicsPath RoundedRect()
{
Rectangle _2DSize = new Rectangle(0, 0, Width - 1, Height - 1);
int Diameter = 50 * 2;
Size _Size = new Size(Diameter, Diameter);
Rectangle _Arc = new Rectangle(_2DSize.Location, _Size);
GraphicsPath _Path = new GraphicsPath();
_Path.AddArc(_Arc, 180, 90);
_Arc.X = _2DSize.Right - 50;
_Path.AddArc(_Arc, 270, 90);
_Arc.Y = _2DSize.Bottom - 5;
_Path.AddArc(_Arc, 0, 90);
_Arc.X = _2DSize.Left - Diameter;
_Path.AddArc(_Arc, 90, 90);
_Path.CloseFigure();
return _Path;
}
private void FillRoundedRectangle(Graphics Gfx)
{
if (Gfx == null) throw new ArgumentNullException("Graphics supplied is null");
using (GraphicsPath GPth = RoundedRect())
{
var Bnds = new Rectangle(0, 0, Width - 1, Height - 1);
PointF[] PT = new PointF[]
{
new PointF(-50, -50),
new PointF(Bnds.Right, 0),
new PointF(Bnds.Right, Bnds.Bottom),
new PointF(40, Bnds.Bottom),
new PointF((float)((float)Bnds.Right / 2 - ((float)Bnds.Bottom * 0.15)), (float)((float)Bnds.Bottom / 2 + ((float)Bnds.Bottom * 0.35)))
};
Brush GPBrush = Helper.Get_SplashBrush(Bnds);
//
Gfx.FillPath(GPBrush, GPth);
Region = new Region(GPth);
//using (Brush GPBrush = Helper.Get_SplashBrush(new Rectangle(0, 0, Width - 1, Height - 1)))
//{ Gfx.FillPath(GPBrush, GPth); }
}
}
}
internal class Win32
{
public const Int32 ULW_COLORKEY = 0x00000001;
public const Int32 ULW_ALPHA = 0x00000002;
public const Int32 ULW_OPAQUE = 0x00000004;
public const byte AC_SRC_OVER = 0x00;
public const byte AC_SRC_ALPHA = 0x01;
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", ExactSpelling = true)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern Bool DeleteObject(IntPtr hObject);
public enum Bool
{
False = 0,
True
};
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
public Int32 x;
public Int32 y;
public Point(Int32 x, Int32 y) { this.x = x; this.y = y; }
}
[StructLayout(LayoutKind.Sequential)]
public struct Size
{
public Int32 cx;
public Int32 cy;
public Size(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; }
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
}
internal class Helper
{
public static Brush Get_SplashBrush(Rectangle Bnds)
{
PointF[] PTsGH;
Color[] CLRsGH;
PathGradientBrush PGB;
PTsGH = new PointF[]
{
new PointF(-50, -50),
new PointF(Bnds.Right, 0),
new PointF(Bnds.Right, Bnds.Bottom),
new PointF(40, Bnds.Bottom),
new PointF((float)((float)Bnds.Right / 2 - ((float)Bnds.Bottom * 0.15)), (float)((float)Bnds.Bottom / 2 + ((float)Bnds.Bottom * 0.35)))
};
CLRsGH = new Color[]
{
Color.FromArgb(120,40,40),
Color.FromArgb(60, 100, 40),
Color.FromArgb(50, 50, 120),
Color.FromArgb(0, 60, 100),
Color.FromArgb(240, 120, 20, 40)
};
PGB = new PathGradientBrush(PTsGH)
{
SurroundColors = CLRsGH,
CenterColor = Color.FromArgb(160, 124, 20)
};
return PGB;
}
public enum BrushType
{
Linear, Path, Solid
}
public enum ShapeType
{
Rectangular, Circular, Triangular, SplashSpecial
}
}
}
The Problem
Everything is very fine till now but when I try to change the opacity of the form by using this code
protected override void OnClick(EventArgs e)
{
Opacity -= 0.05;
base.OnClick(e);
}
then the form becomes opaque and looks very dirty
I want to change the opacity of the form using a timer to show a fade in and fade out effect!
I tried to use Invalidate(), Update() and Refresh() on the form after changing the opacity but still no luck :(
Is there a way to solve this issue, or is there any alternative to what I want to achieve?

Add static method to current custom class

I have custom progress bar (is a progress bar with text)
Custom progressbar code:
public enum ProgressBarDisplayText
{
Percentage,
CustomText
}
public class CustomProgressBar : ProgressBar
{
[DllImportAttribute("uxtheme.dll")]
private static extern int SetWindowTheme(IntPtr hWnd, string appname, string idlist);
protected override void OnHandleCreated(EventArgs e)
{
SetWindowTheme(this.Handle, "", "");
base.OnHandleCreated(e);
}
//Property to set to decide whether to print a % or Text
public ProgressBarDisplayText DisplayStyle { get; set; }
//Property to hold the custom text
public String CustomText { get; set; }
public CustomProgressBar()
{
// Modify the ControlStyles flags
//http://msdn.microsoft.com/en-us/library/system.windows.forms.controlstyles.aspx
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rect = ClientRectangle;
Graphics g = e.Graphics;
ProgressBarRenderer.DrawHorizontalBar(g, rect);
rect.Inflate(-3, -3);
if (Value > 0)
{
// As we doing this ourselves we need to draw the chunks on the progress bar
Rectangle clip = new Rectangle(rect.X, rect.Y, (int)Math.Round(((float)Value / Maximum) * rect.Width), rect.Height);
ProgressBarRenderer.DrawHorizontalChunks(g, clip);
}
// Set the Display text (Either a % amount or our custom text
int percent = (int)(((double)this.Value / (double)this.Maximum) * 100);
string text = DisplayStyle == ProgressBarDisplayText.Percentage ? percent.ToString() + '%' : CustomText;
//string text = DisplayStyle == ProgressBarDisplayText.Percentage ? Value.ToString() + '%' : CustomText;
using (Font f = new Font(FontFamily.GenericSerif, 10, FontStyle.Bold))
{
SizeF len = g.MeasureString(text, f);
// Calculate the location of the text (the middle of progress bar)
// Point location = new Point(Convert.ToInt32((rect.Width / 2) - (len.Width / 2)), Convert.ToInt32((rect.Height / 2) - (len.Height / 2)));
Point location = new Point(Convert.ToInt32((Width / 2) - len.Width / 2), Convert.ToInt32((Height / 2) - len.Height / 2));
// The commented-out code will centre the text into the highlighted area only. This will centre the text regardless of the highlighted area.
// Draw the custom text
g.DrawString(text, f, Brushes.Black, location);
}
}
}
Now I want to change color of progress bar so I saw this solution
So I try to add to my code
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr w, IntPtr l);
public static void SetState(this ProgressBar pBar, int state)
{
SendMessage(pBar.Handle, 1040, (IntPtr)state, IntPtr.Zero);
}
But as you can see this code is for an extension method, but my current CustomProgressBar inherits ProgressBar and it does not permit to use static classes. So it throw me:
Extension method must be defined in a non-generic static class
how can I add this method to my current class? Regards
That's an extension method and those need to go in a static class. If you want to just make it work in your existing non-static class, just get rid of the this on the first parameter:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr w, IntPtr l);
public static void SetState(ProgressBar pBar, int state)
{
SendMessage(pBar.Handle, 1040, (IntPtr)state, IntPtr.Zero);
}
The parameter can still be of type ProgressBar since CustomProgressBar inherits from it.

Retrieve color of windows 10 taskbar

I found out, that there is a static property in the System.Windows.SystemParameters class that declares the color the user chose for his Windows overall.
But there is a second possibility for the user that enables him to enable or disable, whether the taskbar/windows bar should use that same color.
I was unable to find a key for that in the SystemParameters-class.
I believe there is a registry value to find the colour and it is probably inside:
HKEY_CURRENT_USER\Control Panel\Colors
However on my system I have colours on the taskbar disabled and that colour value doesn't seem to appear in this key.
A work around would be to combine the answers to the following two questions:
TaskBar Location
How to Read the Colour of a Screen Pixel
You need the following imports:
[DllImport("shell32.dll")]
private static extern IntPtr SHAppBarMessage(int msg, ref APPBARDATA data);
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
private static extern int BitBlt(IntPtr hDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);
The following structs:
private struct APPBARDATA
{
public int cbSize;
public IntPtr hWnd;
public int uCallbackMessage;
public int uEdge;
public RECT rc;
public IntPtr lParam;
}
private struct RECT
{
public int left, top, right, bottom;
}
And the following constant:
private const int ABM_GETTASKBARPOS = 5;
Then you can call the following two methods:
public static Rectangle GetTaskbarPosition()
{
APPBARDATA data = new APPBARDATA();
data.cbSize = Marshal.SizeOf(data);
IntPtr retval = SHAppBarMessage(ABM_GETTASKBARPOS, ref data);
if (retval == IntPtr.Zero)
{
throw new Win32Exception("Please re-install Windows");
}
return new Rectangle(data.rc.left, data.rc.top, data.rc.right - data.rc.left, data.rc.bottom - data.rc.top);
}
public static Color GetColourAt(Point location)
{
using (Bitmap screenPixel = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
using (Graphics gdest = Graphics.FromImage(screenPixel))
{
using (Graphics gsrc = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hSrcDC = gsrc.GetHdc();
IntPtr hDC = gdest.GetHdc();
int retval = BitBlt(hDC, 0, 0, 1, 1, hSrcDC, location.X, location.Y, (int)CopyPixelOperation.SourceCopy);
gdest.ReleaseHdc();
gsrc.ReleaseHdc();
}
return screenPixel.GetPixel(0, 0);
}
}
Like this:
Color taskBarColour = GetColourAt(GetTaskbarPosition().Location);

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

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).
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()?

Categories

Resources