How do I use PrintDocument with a scrollable panel`?
Here is some of my code:
MemoryImage = new Bitmap(pnl.Width, pnl.Height);
Rectangle rect = new Rectangle(0, 0, pnl.Width, pnl.Height);
pnl.DrawToBitmap(MemoryImage, new Rectangle(0, 0, pnl.Width,
pnl.Height));
Rectangle pagearea = e.PageBounds;
e.Graphics.DrawImage(MemoryImage, (pagearea.Width / 2) -
(pannel.Width / 2), pannel.Location.Y);
These sets of methods allow to print the content of a ScrollableControl to a Bitmap.
A description of the procedure:
The control is first scrolled back to the origin (control.AutoScrollPosition = new Point(0, 0); (an exception is raised otherwise: the Bitmap has a wrong size. You may want to store the current scroll position and restore it after).
Verifies and stores the actual size of the Container, returned by the PreferredSize or DisplayRectangle properties (depending on the conditions set by the method arguments and the type of container printed). This property considers the full extent of a container.
This will be the size of the Bitmap.
Clears the Bitmap using the background color of the Container.
Iterates the ScrollableControl.Controls collection and prints all first-level child controls in their relative position (a child Control's Bounds rectangle is relative to the container ClientArea.)
If a first-level Control has children, calls the DrawNestedControls recursive method, which will enumerate and draw all nested child Containers/Controls, preserving the internal clip bounds.
Includes support for RichTextBox controls.
The RichEditPrinter class contains the logic required to print the content of a RichTextBox/RichEdit control. The class sends an EM_FORMATRANGE message to the RichTextBox, using the Device context of the Bitmap where the control is being printed.
More details available in the MSDN Docs: How to Print the Contents of Rich Edit Controls.
The ScrollableControlToBitmap() method takes only a ScrollableControl type as argument: you cannot pass a TextBox control, even if it uses ScrollBars.
▶ Set the fullSize argument to true or false to include all child controls inside a Container or just those that are visible. If set to true, the Container's ClientRectangle is expanded to include and print all its child Controls.
▶ Set the includeHidden argument to true or false to include or exclude the hidden control, if any.
Note: this code uses the Control.DeviceDpi property to evaluate the current Dpi of the container's Device Context. This property requires .Net Framework 4.7+. If this version is not available, you can remove:
bitmap.SetResolution(canvas.DeviceDpi, canvas.DeviceDpi);
or derive the value with other means. See GetDeviceCaps.
Possibly, update the Project's Framework version :)
// Prints the content of the current Form instance,
// include all child controls and also those that are not visible
var bitmap = ControlPrinter.ScrollableControlToBitmap(this, true, true);
// Prints the content of a ScrollableControl inside a Form
// include all child controls except those that are not visible
var bitmap = ControlPrinter.ScrollableControlToBitmap(this.panel1, true, false);
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ControlPrinter
{
public static Bitmap ScrollableControlToBitmap(ScrollableControl canvas, bool fullSize, bool includeHidden)
{
canvas.AutoScrollPosition = new Point(0, 0);
if (includeHidden) {
canvas.SuspendLayout();
foreach (Control child in canvas.Controls) {
child.Visible = true;
}
canvas.ResumeLayout(true);
}
canvas.PerformLayout();
Size containerSize = canvas.DisplayRectangle.Size;
if (fullSize) {
containerSize.Width = Math.Max(containerSize.Width, canvas.ClientSize.Width);
containerSize.Height = Math.Max(containerSize.Height, canvas.ClientSize.Height);
}
else {
containerSize = canvas.ClientSize;;
}
var bitmap = new Bitmap(containerSize.Width, containerSize.Height, PixelFormat.Format32bppArgb);
bitmap.SetResolution(canvas.DeviceDpi, canvas.DeviceDpi);
var graphics = Graphics.FromImage(bitmap);
if (canvas.BackgroundImage != null) {
graphics.DrawImage(canvas.BackgroundImage, new Rectangle(Point.Empty, containerSize));
}
else {
graphics.Clear(canvas.BackColor);
}
var rtfPrinter = new RichEditPrinter(graphics);
try {
DrawNestedControls(canvas, canvas, new Rectangle(Point.Empty, containerSize), bitmap, rtfPrinter);
return bitmap;
}
finally {
rtfPrinter.Dispose();
graphics.Dispose();
}
}
private static void DrawNestedControls(Control outerContainer, Control parent, Rectangle parentBounds, Bitmap bitmap, RichEditPrinter rtfPrinter)
{
for (int i = parent.Controls.Count - 1; i >= 0; i--) {
var ctl = parent.Controls[i];
if (!ctl.Visible || (ctl.Width < 1 || ctl.Height < 1)) continue;
var clipBounds = Rectangle.Empty;
if (parent.Equals(outerContainer)) { clipBounds = ctl.Bounds; }
else {
Size scrContainerSize = parentBounds.Size;
if ((parent != ctl) && parent is ScrollableControl scrctl) {
if (scrctl.VerticalScroll.Visible) scrContainerSize.Width -= (SystemInformation.VerticalScrollBarWidth + 1);
if (scrctl.HorizontalScroll.Visible) scrContainerSize.Height -= (SystemInformation.HorizontalScrollBarHeight + 1);
}
clipBounds = Rectangle.Intersect(new Rectangle(Point.Empty, scrContainerSize), ctl.Bounds);
}
if (clipBounds.Width < 1 || clipBounds.Height < 1) continue;
var bounds = outerContainer.RectangleToClient(parent.RectangleToScreen(clipBounds));
if (ctl is RichTextBox rtb) {
rtfPrinter.DrawRtf(rtb.Rtf, outerContainer.Bounds, bounds, ctl.BackColor);
}
else {
ctl.DrawToBitmap(bitmap, bounds);
}
if (ctl.HasChildren) {
DrawNestedControls(outerContainer, ctl, clipBounds, bitmap, rtfPrinter);
}
}
}
internal class RichEditPrinter : IDisposable
{
Graphics dc = null;
RTBPrinter rtb = null;
public RichEditPrinter(Graphics graphics)
{
this.dc = graphics;
this.rtb = new RTBPrinter() { ScrollBars = RichTextBoxScrollBars.None };
}
public void DrawRtf(string rtf, Rectangle canvas, Rectangle layoutArea, Color color)
{
rtb.Rtf = rtf;
rtb.Draw(dc, canvas, layoutArea, color);
rtb.Clear();
}
public void Dispose() => this.rtb.Dispose();
private class RTBPrinter : RichTextBox
{
public void Draw(Graphics g, Rectangle hdcArea, Rectangle layoutArea, Color color)
{
using (var brush = new SolidBrush(color)) {
g.FillRectangle(brush, layoutArea);
};
IntPtr hdc = g.GetHdc();
var canvasAreaTwips = new RECT().ToInches(hdcArea);
var layoutAreaTwips = new RECT().ToInches(layoutArea);
var formatRange = new FORMATRANGE() {
charRange = new CHARRANGE() { cpMax = -1, cpMin = 0 },
hdc = hdc,
hdcTarget = hdc,
rect = layoutAreaTwips,
rectPage = canvasAreaTwips
};
IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(formatRange));
Marshal.StructureToPtr(formatRange, lParam, false);
SendMessage(this.Handle, EM_FORMATRANGE, (IntPtr)1, lParam);
Marshal.FreeCoTaskMem(lParam);
g.ReleaseHdc(hdc);
}
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);
internal const int WM_USER = 0x0400;
// https://learn.microsoft.com/en-us/windows/win32/controls/em-formatrange
internal const int EM_FORMATRANGE = WM_USER + 57;
[StructLayout(LayoutKind.Sequential)]
internal struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
public RECT ToInches(Rectangle rectangle)
{
float inch = 14.92f;
return new RECT() {
Left = (int)(rectangle.Left * inch),
Top = (int)(rectangle.Top * inch),
Right = (int)(rectangle.Right * inch),
Bottom = (int)(rectangle.Bottom * inch)
};
}
}
// https://learn.microsoft.com/en-us/windows/win32/api/richedit/ns-richedit-formatrange?
[StructLayout(LayoutKind.Sequential)]
internal struct FORMATRANGE
{
public IntPtr hdcTarget; // A HDC for the target device to format for
public IntPtr hdc; // A HDC for the device to render to, if EM_FORMATRANGE is being used to send the output to a device
public RECT rect; // The area within the rcPage rectangle to render to. Units are measured in twips.
public RECT rectPage; // The entire area of a page on the rendering device. Units are measured in twips.
public CHARRANGE charRange; // The range of characters to format (see CHARRANGE)
}
[StructLayout(LayoutKind.Sequential)]
internal struct CHARRANGE
{
public int cpMin; // First character of range (0 for start of doc)
public int cpMax; // Last character of range (-1 for end of doc)
}
}
}
}
This is how it works:
VB.Net version of the same procedure
I have a WPF application that takes a screen shot of the running Handbrake executable using a class called ScreenCapture that I copied from stack overflow.
public class ScreenCapture
{
[DllImport("user32.dll")]
static extern int GetWindowRgn(IntPtr hWnd, IntPtr hRgn);
//Region Flags - The return value specifies the type of the region that the function obtains. It can be one of the following values.
const int ERROR = 0;
const int NULLREGION = 1;
const int SIMPLEREGION = 2;
const int COMPLEXREGION = 3;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);
[DllImport("gdi32.dll")]
static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RECT(System.Drawing.Rectangle r) : this(r.Left, r.Top, r.Right, r.Bottom) { }
public int X
{
get { return Left; }
set { Right -= (Left - value); Left = value; }
}
public int Y
{
get { return Top; }
set { Bottom -= (Top - value); Top = value; }
}
public int Height
{
get { return Bottom - Top; }
set { Bottom = value + Top; }
}
public int Width
{
get { return Right - Left; }
set { Right = value + Left; }
}
public System.Drawing.Point Location
{
get { return new System.Drawing.Point(Left, Top); }
set { X = value.X; Y = value.Y; }
}
public System.Drawing.Size Size
{
get { return new System.Drawing.Size(Width, Height); }
set { Width = value.Width; Height = value.Height; }
}
public static implicit operator System.Drawing.Rectangle(RECT r)
{
return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
}
public static implicit operator RECT(System.Drawing.Rectangle r)
{
return new RECT(r);
}
public static bool operator ==(RECT r1, RECT r2)
{
return r1.Equals(r2);
}
public static bool operator !=(RECT r1, RECT r2)
{
return !r1.Equals(r2);
}
public bool Equals(RECT r)
{
return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
}
public override bool Equals(object obj)
{
if (obj is RECT)
return Equals((RECT)obj);
else if (obj is System.Drawing.Rectangle)
return Equals(new RECT((System.Drawing.Rectangle)obj));
return false;
}
public override int GetHashCode()
{
return ((System.Drawing.Rectangle)this).GetHashCode();
}
public override string ToString()
{
return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
}
}
public Bitmap GetScreenshot(IntPtr ihandle)
{
IntPtr hwnd = ihandle;//handle here
RECT rc;
GetWindowRect(new HandleRef(null, hwnd), out rc);
Bitmap bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap;
try
{
hdcBitmap = gfxBmp.GetHdc();
}
catch
{
return null;
}
bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!succeeded)
{
gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
}
IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hRgn);
Region region = Region.FromHrgn(hRgn);//err here once
if (!region.IsEmpty(gfxBmp))
{
gfxBmp.ExcludeClip(region);
gfxBmp.Clear(Color.Transparent);
}
gfxBmp.Dispose();
return bmp;
}
public void WriteBitmapToFile(string filename, Bitmap bitmap)
{
bitmap.Save(filename, ImageFormat.Bmp);
}
So when the button click handler below is called a screenshot of the handbrake window is taken.
I write it to the harddrive to make sure its ok:
handbrake screen shot.
I create an instance of a CLR class library ClassLibrary1::Class1 and call the method "DoSomething" passing it the System.Drawing.Bitmap object.
private void button4_Click(object sender, RoutedEventArgs e)
{
string wName = "HandBrake";
IntPtr hWnd = IntPtr.Zero;
foreach (Process pList in Process.GetProcesses())
{
if (pList.MainWindowTitle.Contains(wName))
{
hWnd = pList.MainWindowHandle;
var sc = new ScreenCapture();
SetForegroundWindow(hWnd);
var bitmap = sc.GetScreenshot(hWnd);
sc.WriteBitmapToFile("handbrake.bmp", bitmap);
Bitmap image1 = (Bitmap)System.Drawing.Image.FromFile("handbrake.bmp", true);
ClassLibrary1.Class1 opencv = new ClassLibrary1.Class1();
opencv.DoSomething(image1);
}
}
}
Inside DoSomething I attempt to convert the System.Drawing.Bitmap to a OpenCV class cv::Mat. I call cv::imwrite to make sure the bitmap is still ok, unfortunately somethings gone wrong: mangled handbrake screenshot
void Class1::DoSomething(Bitmap ^mybitmap)
{
cv::Mat *imgOriginal;
// Lock the bitmap's bits.
Rectangle rect = Rectangle(0, 0, mybitmap->Width, mybitmap->Height);
Imaging::BitmapData^ bmpData = mybitmap->LockBits(rect, Imaging::ImageLockMode::ReadWrite, mybitmap->PixelFormat);
try
{
// Get the address of the first line.
IntPtr ptr = bmpData->Scan0;
// Declare an array to hold the bytes of the bitmap.
// This code is specific to a bitmap with 24 bits per pixels.
int bytes = Math::Abs(bmpData->Stride) * mybitmap->Height;
array<Byte>^rgbValues = gcnew array<Byte>(bytes);
// Copy the RGB values into the array.
System::Runtime::InteropServices::Marshal::Copy(ptr, rgbValues, 0, bytes);
imgOriginal = new cv::Mat(mybitmap->Height, mybitmap->Width, CV_8UC3, (void *)ptr, std::abs(bmpData->Stride));
}
finally { mybitmap->UnlockBits(bmpData); }//Remember to unlock!!!
cv::imwrite("from_mat.bmp", *imgOriginal);
}
Can anybody spot my error?
Since your image is stretched horizontally, I'm betting that you have the wrong pixel format selected. (It's not stretched vertically, nor skewed diagonally, so the stride is correct.) CV_8UC3 specifies 24 bits per pixel, but I think that your BMP file is using 32 bits per pixel.
Switch your pixel format to CV_8UC4, or better yet, read the number of bits per pixel from the image and select the correct CV format based on that.
Side note: Since you're doing sc.WriteBitmapToFile() followed by opencv.DoSomething(Image.FromFile(), the entire bit about how you're capturing the screenshot is irrelevant. You're reading the bitmap from a file; that's all that matters.
I am attempting to create a custom ListBox control in WinForms.
I have subclassed it, set DrawMode.OwnerDrawFixed, and am overriding OnDrawItem. I have created a custom ColoredListBoxItem class that has additional properties to deal with the highlighting.
This is all fine. My issue is that this functionality needs to highlight words within the text of a list item.
Here is as far as I've gotten, and it doesn't work, because the X coordinate of the highlight remains constant and does not correspond to the actual X coordinate of the text.
How can I get a Point value (or Rectangle) to use with DrawText that will overlay the highlighted text? I've tried doing some math with the bounds of the original text Rectangle versus the highlight Rectangle but it is not working as expected.
protected override void OnDrawItem(DrawItemEventArgs e) {
ColoredListBoxItem item = this.Items[e.Index] as ColoredListBoxItem;
e.DrawBackground();
e.DrawFocusRectangle();
Rectangle fullMessageRect = e.Bounds;
// Draw the original, full text
TextRenderer.DrawText(e.Graphics, item.Message, e.Font,
new Point(fullMessageRect.X, fullMessageRect.Y),
this.ForeColor);
// Check if we have any text to be highlighted
if (SomethingToHighlight(item)) {
// Find the text to highlight, and get its area
SizeF highlightedAreaSize =
e.Graphics.MeasureString(item.TextToHightlight, e.Font);
PointF highlightAreaPoint = highlightedAreaSize.ToPointF();
Point point = new Point(Convert.ToInt32(highlightAreaPoint.X),
Convert.ToInt32(fullMessageRect.Y));
TextRenderer.DrawText(e.Graphics, item.TextToHightlight, e.Font,
point, this.ForeColor, item.HighlightColor);
}
}
Here is what I'm seeing in a demo app, where the output just shows work being done, and I am trying to highlight one particular word .. in this case "height".
Don't pay any attention to the actual output, it's a bunch of nonsense so I can see exactly how another part of the system is adjusting PictureBox images on the fly.
Lines that it thinks should be highlighted are shown twice, once in the original format and then again with the highlight applied. Notice how the highlighted part is correct in the Y coordinate, but does not change in the X.
Here's what I am seeing in the Watch window when I set a break point prior to writing the highlighted text:
Clearly, I don't need the variable highlightAreaPoint, because it's the same as highlightedAreaSize.
Probably something obvious here but I'm tired of fiddling with it at this point!
I can feel your pain as I have been there before. Actually, I wanted to design my own Textbox not inheriting from Microsoft.Textbox control and when I researched on-line, I sort of discouraged to learn 1000 reasons why one must not reinvent the wheel and why it is so difficult to do from scratch. Highlighting selection text was one of the major challenge among the others like right-to-left, caret positioning, non-fixed fonts etc. But I decided to fly against the wind because I had my reasons to do so and finally got what I wanted. Since my text selection code was for TextBox, I had to change it to suit your requirement as you are dealing with ListBox.
Following is the code snippet:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace HowToHighlightPortionOfText
{
public static class Helper
{
private static Rectangle dummy
{
get
{
return new Rectangle(0, 0, 10, 10);
}
}
const uint H = 0x00000000;
const uint V = 0x00000001;
const uint T = 0x00000002;
#region api functions
[DllImport("user32.dll")]
static extern int DrawText(IntPtr hdc, string lpStr, int nCount, ref Dimension lpRect, int wFormat);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(this IntPtr hdc, IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern int DeleteObject(this IntPtr hObject);
[DllImport("gdi32.dll", EntryPoint = "GdiGradientFill", ExactSpelling = true)]
static extern bool GradientFill(IntPtr hdc, Trivertex[] pVertex,
uint dwNumVertex, uint[] pMesh, uint dwNumMesh, uint dwMode);
[DllImport("gdi32")]
public static extern int SetBkMode(this IntPtr hdc, int nBkMode);
[DllImport("gdi32.dll")]
public static extern uint SetTextColor(this IntPtr hdc, int crColor);
[DllImport("gdi32.dll")]
public static extern uint SetBkColor(this IntPtr hdc, int crColor);
#endregion
#region public methods
//use this function to hilight portion of listbox item text
public static void HilightItemText(this ListBox control, int itemIndex, int startIndex, int endIndex,
Color highlightForeColor, Color highlightBackColorStart, Color? highlightBackColorEnd = null)
{
var container = control.GetItemRectangle(itemIndex);
var text = control.GetItemText(itemIndex);
using (Graphics g = control.CreateGraphics())
{
g.HighlightText(control.Font, text, container, startIndex, endIndex,
highlightForeColor, highlightBackColorStart, highlightBackColorEnd);
}
}
public static void HighlightText(this IDeviceContext dc, Font font, string text,
Rectangle container, int start, int end, Color highlightForeColor, Color highlightBackColorStart,
Color? highlightBackColorEnd, DrawTextFlags? flags = null)
{
IntPtr hdc = dc.GetHdc();
IntPtr _font = SelectObject(hdc, font.ToHfont());
Dimension dm = container;
var flag = flags.getMeasureFlag(false);
SetBkMode(hdc, ColorTranslator.ToWin32(Color.Transparent));
//first draw whole text
DrawText(hdc, text, text.Length, ref dm, 0);
//now get the highlight rectangle which will draw the highlighted text
Rectangle textBound, uptoIndex;
var rect = hdc.rangeBound(text, container, start, end, out textBound, out uptoIndex, flags: flags);
dm = rect;
var _backColorEnd = highlightBackColorEnd ?? highlightBackColorStart;
hdc.Fill(rect, highlightBackColorStart, _backColorEnd, Angle.A0);
SetTextColor(hdc, ColorTranslator.ToWin32(highlightForeColor));
if (start < 0 || start > text.Length - 1 || end < 0 || end > text.Length - 1)
throw new Exception("start and end value must be with in text length");
var _text = text.Substring(start, end - start + 1);
DrawText(hdc, _text, _text.Length, ref dm, 0);
DeleteObject(SelectObject(hdc, _font));
dc.ReleaseHdc();
}
public static Rectangle RangeBound(this IDeviceContext dc, Font font, string text,
Rectangle container, int start, int end, DrawTextFlags? flags = null)
{
Rectangle textBound, uptoIndex;
return dc.RangeBound(font, text, container, start, end, out textBound, out uptoIndex, flags);
}
public static Rectangle GetPortionRectangleToHighlight(this ListBox control, int itemIndex, int startIndex, int endIndex)
{
var container = control.GetItemRectangle(itemIndex);
var text = control.GetItemText(itemIndex);
Rectangle rect;
using (Graphics g = control.CreateGraphics())
{
rect = g.RangeBound(control.Font, text, container, startIndex, endIndex);
}
return rect;
}
public static bool Fill(this IntPtr hdc, Rectangle rc, Color c1,
Color c2, Angle angle)
{
return hdc.Fill(rc.X, rc.Y, rc.Right, rc.Bottom, c1, c2, angle);
}
public static bool Fill(this IntPtr hdc, int x0, int y0, int x1, int y1, Color c1, Color c2, Angle angle)
{
Trivertex[] t = new Trivertex[4]
{
new Trivertex(x0, y0, c1),
new Trivertex(x1, y1, c2),
new Trivertex(x0, y1, c1, c2),
new Trivertex(x1, y0, c1, c2)
};
uint[] pMesh = new uint[] { 0, 1, 2, 0, 1, 3 };
switch ((int)angle % 180)
{
case 0:
return GradientFill(hdc, t, 2, pMesh, 1, H);
case 45:
return GradientFill(hdc, t, 4, pMesh, 2, T);
case 90:
return GradientFill(hdc, t, 2, pMesh, 1, V);
case 135:
t[0].x = x1;
t[3].x = x0;
t[1].x = x0;
t[2].x = x1;
return GradientFill(hdc, t, 4, pMesh, 2, T);
default:
return false;
}
}
#endregion
#region get the highlight rectangle
static Rectangle RangeBound(this IDeviceContext dc, Font font, string text,
Rectangle container, int start, int end, out Rectangle textBound, out Rectangle uptoIndex, DrawTextFlags? flags = null)
{
textBound = Rectangle.Empty;
uptoIndex = Rectangle.Empty;
if (string.IsNullOrEmpty(text)) return Rectangle.Empty;
IntPtr hdc = dc.GetHdc();
IntPtr _font = SelectObject(hdc, font.ToHfont());
var rc = hdc.rangeBound(text, container, start, end, out textBound, out uptoIndex, flags: flags);
DeleteObject(SelectObject(hdc, _font));
dc.ReleaseHdc();
return rc;
}
static TextMeasurement charRectangle(this IntPtr hdc, string text, Rectangle container,
string wholeText = null, Point? point = null, bool adjustByPoint = false, DrawTextFlags? flags = null)
{
if (string.IsNullOrEmpty(text)) return TextMeasurement.Default;
TextMeasurement measurement = new TextMeasurement();
Rectangle textBound;
wholeText = (wholeText ?? text);
var location = container.Location;
var measureWholeText = point == null;
measurement.UserPoint = point ?? Point.Empty;
textBound = hdc.textBound(wholeText, container, flags: flags);
var rect = textBound;
var p = point ?? new Point(container.Right, container.Y);
if (!measureWholeText)
{
if (p.X > textBound.Right)
p.X = textBound.Right;
else if (p.X < textBound.Left)
p.X = textBound.X;
}
var charIndex = 0;
var result = hdc.charRectangle(text, ref p, rect, flags, measureWholeText);
charIndex = Math.Max(0, result.Item2);
var rectangles = result.Item1;
measurement.Bounds = rectangles[0];
measurement.TextBounds = (measureWholeText) ? rectangles[1] : textBound;
rectangles[1] = measurement.TextBounds;
if (!measureWholeText && adjustByPoint && charIndex > 0)
{
float middle = (float)measurement.Bounds.Left +
measurement.Bounds.Width / 2;
if (p.X > middle - 1)
{
Rectangle r;
Dimension r1 = measurement.TextBounds;
var newresult = hdc.charBound(text, charIndex + 2, ref r1,
(int)flags.getMeasureFlag(false), out r);
if (!newresult.Equals(measurement.Bounds) &&
newresult.X > measurement.Bounds.X)
{
charIndex++;
measurement.Bounds = newresult;
}
}
}
if (measurement.Bounds.Size.Width<=0)
measurement.Bounds = new Rectangle(measurement.Bounds.Location, new Size(2, 2));
measurement.CharIndex = charIndex;
measurement.Char = '\0';
measurement.Char = text[Math.Min(charIndex, text.Length - 1)];
return measurement;
}
static Tuple<Rectangle[], int> charRectangle(this IntPtr hdc, string text, ref Point p, Rectangle rect,
DrawTextFlags? flags, bool measureWholeText = false)
{
int i = 0;
int middle = text.Length / 2, start = 0;
bool first = true;
do
{
var upto = hdc.Measure(text.Substring(0, middle), null, rect, flags);
bool found = upto.Has(p);
if (!found)
{
start = middle;
middle += (text.Length - middle) / 2;
first = false;
if (start == middle) break;
}
else break;
} while (middle > 1 && text.Length - middle > 1);
if (first)
{
return hdc.charRectangle(text.Substring(0, middle),
ref p, rect, flags);
}
else
{
Rectangle[] list = new Rectangle[2];
for (i = start; i <= middle; i++)
{
if (hdc.Measure(text, out list, p, i + 1, rect, flags))
break;
}
i = Math.Max(i, 0);
return new Tuple<Rectangle[], int>(list, i);
}
}
static Rectangle charBound(this IntPtr hdc, string text, int len,
ref Dimension bounds, int flag, out Rectangle whole)
{
DrawText(hdc, text, len, ref bounds, flag);
whole = bounds;
var rc = bounds;
if (len - 1 > 0 && len <= text.Length)
{
DrawText(hdc, text.Substring(0, len - 1), len - 1, ref rc, flag);
rc = Rectangle.FromLTRB(rc.Right, bounds.Top, bounds.Right, bounds.Bottom);
}
return rc;
}
static Rectangle rangeBound(this IntPtr hdc, string text, Rectangle container, int start, int end,
out Rectangle textBound, out Rectangle uptoIndex, DrawTextFlags? flags = null)
{
textBound = Rectangle.Empty;
uptoIndex = Rectangle.Empty;
if (string.IsNullOrEmpty(text)) return Rectangle.Empty;
var location = container.Location;
textBound = hdc.textBound(text, container, flags);
Dimension rect = textBound;
var flag = flags.getMeasureFlag(false);
start++;
var text1 = text.Substring(0, start);
var rc = hdc.charBound(text1, text1.Length, ref rect, (int)flag, out uptoIndex);
end++;
var text2 = text.Substring(0, end);
DrawText(hdc, text2, text2.Length, ref rect, (int)flag);
return Rectangle.FromLTRB(rc.Left, rect.Top, rect.Right, rect.Bottom);
}
static Rectangle textBound(this IntPtr hdc, string text, Rectangle container, DrawTextFlags? flags = null)
{
Rectangle rc = Rectangle.Empty;
if (string.IsNullOrEmpty(text)) return rc;
Point p = container.Location;
var r = hdc.Measure(text, text.Length, flags: flags);
return new Rectangle(p, r.Size);
}
static DrawTextFlags getMeasureFlag(this DrawTextFlags? flags, bool textboxControl = false)
{
DrawTextFlags flag = DrawTextFlags.CalculateArea;
if (flags != null) flag |= flags.Value;
flag |= DrawTextFlags.WordBreak | DrawTextFlags.NoPrefix
| DrawTextFlags.NoPadding | DrawTextFlags.NoClipping;
if (textboxControl) flag |= DrawTextFlags.TextBoxControl;
else flag |= DrawTextFlags.SingleLine;
return flag;
}
static Rectangle RangeBound(this IntPtr hdc, string text,
Rectangle container, int start, int end, DrawTextFlags? flags = null)
{
Rectangle textBound, uptoIndex;
return hdc.rangeBound(text, container, start, end, out textBound, out uptoIndex, flags);
}
static Rectangle Measure(this IntPtr hdc, string text, int? length = null,
Rectangle? rect = null, DrawTextFlags? flags = null)
{
if (string.IsNullOrEmpty(text)) return Rectangle.Empty;
Dimension bounds = rect ?? dummy;
var len = length ?? text.Length;
var flag = flags.getMeasureFlag(false);
var i = DrawText(hdc, text, len, ref bounds, (int)flag);
return bounds;
}
static bool Measure(this IntPtr hdc, string text, out Rectangle[] rectangles, Point p,
int? length = null, Rectangle? rect = null, DrawTextFlags? flags = null)
{
rectangles = new Rectangle[2];
if (string.IsNullOrEmpty(text)) return true;
Dimension bounds = rect ?? dummy;
var len = length ?? text.Length;
var flag = flags.getMeasureFlag(false);
Rectangle rc, rc1;
rc1 = hdc.charBound(text, len, ref bounds, (int)flag, out rc);
rectangles = new Rectangle[] { rc1, rc };
return (rectangles[0].Left < bounds.Left || rectangles[0].Has(p.X));
}
static bool Has(this Rectangle rect, int x = -1,
int y = -1, int checkRightUpto = -1, int checkBottomUpto = -1)
{
if (x == -1 && y == -1)
{
x = 0;
y = 0;
}
else
{
x = x == -1 ? rect.X : x;
y = y == -1 ? rect.Y : y;
}
if (checkRightUpto == -1)
{
checkRightUpto = rect.Width;
}
if (checkBottomUpto == -1)
{
checkBottomUpto = rect.Height;
}
return x >= rect.Left && x <= rect.Left +
checkRightUpto && y >= rect.Top &&
y <= rect.Top + checkBottomUpto;
}
static bool Has(this Rectangle rect, Point p,
int checkRightUpto = -1, int checkBottomUpto = -1)
{
return rect.Has(p.X, p.Y, checkRightUpto, checkBottomUpto);
}
#endregion
}
#region structs
[StructLayout(LayoutKind.Sequential)]
public struct Dimension
{
public int Left, Top, Right, Bottom;
public Dimension(int left, int top, int right, int bottom)
{
this.Left = left;
this.Right = right;
this.Top = top;
this.Bottom = bottom;
}
public Dimension(Rectangle r)
{
this.Left = r.Left;
this.Top = r.Top;
this.Bottom = r.Bottom;
this.Right = r.Right;
}
public static implicit operator Rectangle(Dimension rc)
{
return Rectangle.FromLTRB(rc.Left, rc.Top, rc.Right, rc.Bottom);
}
public static implicit operator Dimension(Rectangle rc)
{
return new Dimension(rc);
}
public static Dimension Default
{
get { return new Dimension(0, 0, 1, 1); }
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Trivertex
{
public int x;
public int y;
public ushort Red;
public ushort Green;
public ushort Blue;
public ushort Alpha;
public Trivertex(int x, int y, Color color)
: this(x, y, color.R, color.G, color.B, color.A)
{
}
public Trivertex(int x, int y, Color color, Color other)
: this(x, y, color.R, color.G, color.B, color.A, other)
{
}
public Trivertex(int x, int y, ushort red, ushort green, ushort blue, ushort alpha)
{
this.x = x;
this.y = y;
Red = (ushort)(red << 8);
Green = (ushort)(green << 8);
Blue = (ushort)(blue << 8);
Alpha = (ushort)(alpha << 8);
}
public Trivertex(int x, int y, ushort red, ushort green, ushort blue, ushort alpha, Color other)
{
this.x = x;
this.y = y;
Red = (ushort)((red + other.R / 2) << 8);
Green = (ushort)((green + other.G / 2) << 8);
Blue = (ushort)((blue + other.B / 2) << 8);
Alpha = (ushort)((alpha + other.A / 2) << 8);
}
public static ushort R(Color c)
{
return (ushort)(c.R << 8);
}
public static ushort G(Color c)
{
return (ushort)(c.G << 8);
}
public static ushort B(Color c)
{
return (ushort)(c.B << 8);
}
public static ushort R(Color c, Color c1)
{
return (ushort)(((c.R + c1.R / 2)) << 8);
}
public static ushort G(Color c, Color c1)
{
return (ushort)(((c.G + c1.G / 2)) << 8);
}
public static ushort B(Color c, Color c1)
{
return (ushort)(((c.B + c1.B / 2)) << 8);
}
}
#endregion
#region textmeasurement interface + class
public interface ITextMeasurement : ICloneable
{
int CharIndex { get; set; }
int PreviousIndex { get; }
Rectangle Bounds { get; }
Rectangle TextBounds { get; }
char Char { get; }
Point UserPoint { get; }
void CopyFrom(ITextMeasurement other);
}
public class TextMeasurement : ITextMeasurement
{
Rectangle now, textBound;
public virtual Rectangle Bounds
{
get
{
return now;
}
set { now = value; }
}
public virtual Rectangle TextBounds
{
get
{
return textBound; ;
}
set { textBound = value; }
}
public virtual int CharIndex { get; set; }
public virtual int PreviousIndex { get; set; }
public virtual char Char { get; set; }
public Point UserPoint { get; set; }
public virtual void CopyFrom(ITextMeasurement tm)
{
PreviousIndex = tm.PreviousIndex;
CharIndex = tm.CharIndex;
Bounds = tm.Bounds;
Char = tm.Char;
TextBounds = tm.TextBounds;
UserPoint = tm.UserPoint;
if (UserPoint.IsEmpty) UserPoint = Bounds.Location;
}
public virtual object Clone()
{
var tm = new TextMeasurement();
tm.CopyFrom(this);
return tm;
}
protected virtual void ResetBounds(Point p)
{
ResetBounds(p.X, p.Y);
}
protected virtual void ResetBounds(int? lineX = null, int? lineY = null)
{
if (lineX.HasValue)
{
now.X = lineX.Value;
textBound.X = lineX.Value;
}
if (lineY.HasValue)
{
now.Y = lineY.Value;
textBound.Y = lineY.Value;
}
}
protected virtual void ResetEmptyBounds(Rectangle rc)
{
now = rc;
textBound = rc;
}
public static TextMeasurement Default
{
get { return new TextMeasurement(); }
}
}
#endregion
#region enums
public enum DrawTextFlags
{
CalculateArea = 0x00000400,
WordBreak = 0x00000010,
TextBoxControl = 0x00002000,
Top = 0x00000000,
Left = 0x00000000,
HorizontalCenter = 0x00000001,
Right = 0x00000002,
VerticalCenter = 0x00000004,
Bottom = 0x00000008,
SingleLine = 0x00000020,
ExpandTabs = 0x00000040,
TabStop = 0x00000080,
NoClipping = 0x00000100,
ExternalLeading = 0x00000200,
NoPrefix = 0x00000800,
Internal = 0x00001000,
PathEllipsis = 0x00004000,
EndEllipsis = 0x00008000,
WordEllipsis = 0x00040000,
ModifyString = 0x00010000,
RightToLeft = 0x00020000,
NoFullWidthCharacterBreak = 0x00080000,
HidePrefix = 0x00100000,
PrefixOnly = 0x00200000,
NoPadding = 0x10000000,
}
public enum Angle
{
A0 = 0,
A45 = 45,
A90 = 90,
A135 = 135,
A180 = 180
}
#endregion
}
Suppose your ItemText at index 2 is "StackOverFlow is a wonderful site" and you want to highlight "StackOverFlow" then your startIndex =0 and endIndex = 12.
To highlight portion of text use HighlightItemText method:
listBox.HilightItemText(2, 0, 12, Color.Black, Color.Gold, Color.Yellow);
To get highlighted coordinates use GetPortionRectangleToHighlight method to get co-ordinates of text portion to highlight. Please note that you just need to pass start and end index as well of portion text.
so call the function like:
var portionRectangle = listBox1.GetPortionRectangleToHighlight (2, 0, 12);
Have a look at the attached image as working proof of concept.
A simple example would be something like this:
private string[] _sentences = {
"Old height on pictureOne: 766",
"New height on pictureOne: 900",
"",
"Forcing width on objectX"
};
private void Form1Paint(object sender, PaintEventArgs e) {
int y = 10; //Start position
int x;
foreach (string s in _sentences) {
x = 0; //Start position
foreach (string word in s.Split(' ')) {
if (ShouldHeighlightWord(word)) {
e.Graphics.DrawString(word + " ", this.Font, new SolidBrush(Color.Red), x, y);
}
else {
e.Graphics.DrawString(word + " ", this.Font, new SolidBrush(Color.Black), x, y);
}
x += (int)e.Graphics.MeasureString(word + " ", this.Font).Width;
}
y += (int)Math.Ceiling(e.Graphics.MeasureString("I", this.Font).Height);
}
}
private bool ShouldHeighlightWord(string word) {
switch (word) {
case "on":
case "Old":
return true;
default:
return false;
}
}
This code is just drawing the strings onto an empty form and instead of highlighting it just changes the color to Red.
But i think you understand what i mean.
Since i dont have more code its hard to make a better example for you.
When you check:
if (SomethingToHighlight(item)) {
That only returns true/false i guess and you need something that returns all words to be highlighted, but since a word can occur twice (or more) in one sentence you need to be able to get a position in the string as well. Or just take one word at a time and check if it should be highlighted or not and then draw it to the control.
BACKGROUND
I am writing a screen capture application
My code is based derived from this project: http://www.codeproject.com/KB/cs/DesktopCaptureWithMouse.aspx?display=Print
Note that the code captures the the mouse cursor also (which is desirable for me)
MY PROBLEM
Code works fine when the mouse cursor is the normal pointer or hand icon - the mouse is rendered correctly on the screenshot
However, when the mouse cursor is changed to the insertion point (the "I-beam" cursor) - for example typing in NOTEPAD - then code doesn't work - the result is that I get a faint image of the cursor - like a very translucent (gray) version of it instead of the blank & white one would expect.
MY QUESTION
How can I capture the mouse cursor image when the image is one of these "I-beam"-type images
NOTE: If you click on the original article someone offers a suggestion - it doesn't work
SOURCE
This is from the original article.
static Bitmap CaptureCursor(ref int x, ref int y)
{
Bitmap bmp;
IntPtr hicon;
Win32Stuff.CURSORINFO ci = new Win32Stuff.CURSORINFO();
Win32Stuff.ICONINFO icInfo;
ci.cbSize = Marshal.SizeOf(ci);
if (Win32Stuff.GetCursorInfo(out ci))
{
if (ci.flags == Win32Stuff.CURSOR_SHOWING)
{
hicon = Win32Stuff.CopyIcon(ci.hCursor);
if (Win32Stuff.GetIconInfo(hicon, out icInfo))
{
x = ci.ptScreenPos.x - ((int)icInfo.xHotspot);
y = ci.ptScreenPos.y - ((int)icInfo.yHotspot);
Icon ic = Icon.FromHandle(hicon);
bmp = ic.ToBitmap();
return bmp;
}
}
}
return null;
}
While I can't explain exactly why this happens, I think I can show how to get around it.
The ICONINFO struct contains two members, hbmMask and hbmColor, that contain the mask and color bitmaps, respectively, for the cursor (see the MSDN page for ICONINFO for the official documentation).
When you call GetIconInfo() for the default cursor, the ICONINFO struct contains both valid mask and color bitmaps, as shown below (Note: the red border has been added to clearly show the image boundaries):
Default Cursor Mask Bitmap
Default Cursor Color Bitmap
When Windows draws the default cursor, the mask bitmap is first applied with an AND raster operation, then the color bitmap is applied with an XOR raster operation. This results in an opaque cursor and a transparent background.
When you call GetIconInfo() for the I-Beam cursor, though, the ICONINFO struct only contains a valid mask bitmap, and no color bitmap, as shown below (Note: again, the red border has been added to clearly show the image boundaries):
I-Beam Cursor Mask Bitmap
According to the ICONINFO documentation, the I-Beam cursor is then a monochrome cursor. The top half of the mask bitmap is the AND mask, and the bottom half of the mask bitmap is the XOR bitmap. When Windows draws the I-Beam cursor, the top half of this bitmap is first drawn over the desktop with an AND raster operation. The bottom half of the bitmap is then drawn over top with an XOR raster operation. Onscreen, The cursor will appear as the inverse of the content behind it.
One of the comments for the original article that you linked mentions this. On the desktop, since the raster operations are applied over the desktop content, the cursor will appear correct. However, when the image is drawn over no background, as in your posted code, the raster operations that Windows performs result in a faded image.
That being said, this updated CaptureCursor() method will handle both color and monochrome cursors, supplying a plain black cursor image when the cursor is monochrome.
static Bitmap CaptureCursor(ref int x, ref int y)
{
Win32Stuff.CURSORINFO cursorInfo = new Win32Stuff.CURSORINFO();
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
if (!Win32Stuff.GetCursorInfo(out cursorInfo))
return null;
if (cursorInfo.flags != Win32Stuff.CURSOR_SHOWING)
return null;
IntPtr hicon = Win32Stuff.CopyIcon(cursorInfo.hCursor);
if (hicon == IntPtr.Zero)
return null;
Win32Stuff.ICONINFO iconInfo;
if (!Win32Stuff.GetIconInfo(hicon, out iconInfo))
return null;
x = cursorInfo.ptScreenPos.x - ((int)iconInfo.xHotspot);
y = cursorInfo.ptScreenPos.y - ((int)iconInfo.yHotspot);
using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask))
{
// Is this a monochrome cursor?
if (maskBitmap.Height == maskBitmap.Width * 2)
{
Bitmap resultBitmap = new Bitmap(maskBitmap.Width, maskBitmap.Width);
Graphics desktopGraphics = Graphics.FromHwnd(Win32Stuff.GetDesktopWindow());
IntPtr desktopHdc = desktopGraphics.GetHdc();
IntPtr maskHdc = Win32Stuff.CreateCompatibleDC(desktopHdc);
IntPtr oldPtr = Win32Stuff.SelectObject(maskHdc, maskBitmap.GetHbitmap());
using (Graphics resultGraphics = Graphics.FromImage(resultBitmap))
{
IntPtr resultHdc = resultGraphics.GetHdc();
// These two operation will result in a black cursor over a white background.
// Later in the code, a call to MakeTransparent() will get rid of the white background.
Win32Stuff.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 32, Win32Stuff.TernaryRasterOperations.SRCCOPY);
Win32Stuff.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 0, Win32Stuff.TernaryRasterOperations.SRCINVERT);
resultGraphics.ReleaseHdc(resultHdc);
}
IntPtr newPtr = Win32Stuff.SelectObject(maskHdc, oldPtr);
Win32Stuff.DeleteObject(newPtr);
Win32Stuff.DeleteDC(maskHdc);
desktopGraphics.ReleaseHdc(desktopHdc);
// Remove the white background from the BitBlt calls,
// resulting in a black cursor over a transparent background.
resultBitmap.MakeTransparent(Color.White);
return resultBitmap;
}
}
Icon icon = Icon.FromHandle(hicon);
return icon.ToBitmap();
}
There are some issues with the code that may or may not be a problem.
The check for a monochrome cursor simply tests whether the height is twice the width. While this seems logical, the ICONINFO documentation does not mandate that only a monochrome cursor is defined by this.
There is probably a better way to render the cursor that the BitBlt() - BitBlt() - MakeTransparent() combination of method calls I used.
[StructLayout(LayoutKind.Sequential)]
struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public POINTAPI ptScreenPos;
}
[StructLayout(LayoutKind.Sequential)]
struct POINTAPI
{
public int x;
public int y;
}
[DllImport("user32.dll")]
static extern bool GetCursorInfo(out CURSORINFO pci);
[DllImport("user32.dll")]
static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);
const Int32 CURSOR_SHOWING = 0x00000001;
public static Bitmap CaptureScreen(bool CaptureMouse)
{
Bitmap result = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format24bppRgb);
try
{
using (Graphics g = Graphics.FromImage(result))
{
g.CopyFromScreen(0, 0, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
if (CaptureMouse)
{
CURSORINFO pci;
pci.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(CURSORINFO));
if (GetCursorInfo(out pci))
{
if (pci.flags == CURSOR_SHOWING)
{
DrawIcon(g.GetHdc(), pci.ptScreenPos.x, pci.ptScreenPos.y, pci.hCursor);
g.ReleaseHdc();
}
}
}
}
}
catch
{
result = null;
}
return result;
}
Here's a modified version of Dimitar's response (using DrawIconEx) that worked for me on multiple screens:
public class ScreenCapturePInvoke
{
[StructLayout(LayoutKind.Sequential)]
private struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public POINTAPI ptScreenPos;
}
[StructLayout(LayoutKind.Sequential)]
private struct POINTAPI
{
public int x;
public int y;
}
[DllImport("user32.dll")]
private static extern bool GetCursorInfo(out CURSORINFO pci);
[DllImport("user32.dll", SetLastError = true)]
static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags);
private const Int32 CURSOR_SHOWING = 0x0001;
private const Int32 DI_NORMAL = 0x0003;
public static Bitmap CaptureFullScreen(bool captureMouse)
{
var allBounds = Screen.AllScreens.Select(s => s.Bounds).ToArray();
Rectangle bounds = Rectangle.FromLTRB(allBounds.Min(b => b.Left), allBounds.Min(b => b.Top), allBounds.Max(b => b.Right), allBounds.Max(b => b.Bottom));
var bitmap = CaptureScreen(bounds, captureMouse);
return bitmap;
}
public static Bitmap CapturePrimaryScreen(bool captureMouse)
{
Rectangle bounds = Screen.PrimaryScreen.Bounds;
var bitmap = CaptureScreen(bounds, captureMouse);
return bitmap;
}
public static Bitmap CaptureScreen(Rectangle bounds, bool captureMouse)
{
Bitmap result = new Bitmap(bounds.Width, bounds.Height);
try
{
using (Graphics g = Graphics.FromImage(result))
{
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
if (captureMouse)
{
CURSORINFO pci;
pci.cbSize = Marshal.SizeOf(typeof (CURSORINFO));
if (GetCursorInfo(out pci))
{
if (pci.flags == CURSOR_SHOWING)
{
var hdc = g.GetHdc();
DrawIconEx(hdc, pci.ptScreenPos.x-bounds.X, pci.ptScreenPos.y-bounds.Y, pci.hCursor, 0, 0, 0, IntPtr.Zero, DI_NORMAL);
g.ReleaseHdc();
}
}
}
}
}
catch
{
result = null;
}
return result;
}
}
Based on the other answers I made a version without all the Windows API stuff (for the monochrome part) because the solutions did not work for all monochrome cursors. I create the cursor from the mask by combining the two mask parts.
My solution:
Bitmap CaptureCursor(ref Point position)
{
CURSORINFO cursorInfo = new CURSORINFO();
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
if (!GetCursorInfo(out cursorInfo))
return null;
if (cursorInfo.flags != CURSOR_SHOWING)
return null;
IntPtr hicon = CopyIcon(cursorInfo.hCursor);
if (hicon == IntPtr.Zero)
return null;
ICONINFO iconInfo;
if (!GetIconInfo(hicon, out iconInfo))
return null;
position.X = cursorInfo.ptScreenPos.x - iconInfo.xHotspot;
position.Y = cursorInfo.ptScreenPos.y - iconInfo.yHotspot;
using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask))
{
// check for monochrome cursor
if (maskBitmap.Height == maskBitmap.Width * 2)
{
Bitmap cursor = new Bitmap(32, 32, PixelFormat.Format32bppArgb);
Color BLACK = Color.FromArgb(255, 0, 0, 0); //cannot compare Color.Black because of different names
Color WHITE = Color.FromArgb(255, 255, 255, 255); //cannot compare Color.White because of different names
for (int y = 0; y < 32; y++)
{
for (int x = 0; x < 32; x++)
{
Color maskPixel = maskBitmap.GetPixel(x, y);
Color cursorPixel = maskBitmap.GetPixel(x, y + 32);
if (maskPixel == WHITE && cursorPixel == BLACK)
{
cursor.SetPixel(x, y, Color.Transparent);
}
else if (maskPixel == BLACK)
{
cursor.SetPixel(x, y, cursorPixel);
}
else
{
cursor.SetPixel(x, y, cursorPixel == BLACK ? WHITE : BLACK);
}
}
}
return cursor;
}
}
Icon icon = Icon.FromHandle(hicon);
return icon.ToBitmap();
}
This is the patched version with all fixes for the bugs presented on this page:
public static Bitmap CaptureImageCursor(ref Point point)
{
try
{
var cursorInfo = new CursorInfo();
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
if (!GetCursorInfo(out cursorInfo))
return null;
if (cursorInfo.flags != CursorShowing)
return null;
var hicon = CopyIcon(cursorInfo.hCursor);
if (hicon == IntPtr.Zero)
return null;
Iconinfo iconInfo;
if (!GetIconInfo(hicon, out iconInfo))
{
DestroyIcon(hicon);
return null;
}
point.X = cursorInfo.ptScreenPos.X - iconInfo.xHotspot;
point.Y = cursorInfo.ptScreenPos.Y - iconInfo.yHotspot;
using (var maskBitmap = Image.FromHbitmap(iconInfo.hbmMask))
{
//Is this a monochrome cursor?
if (maskBitmap.Height == maskBitmap.Width * 2 && iconInfo.hbmColor == IntPtr.Zero)
{
var final = new Bitmap(maskBitmap.Width, maskBitmap.Width);
var hDesktop = GetDesktopWindow();
var dcDesktop = GetWindowDC(hDesktop);
using (var resultGraphics = Graphics.FromImage(final))
{
var resultHdc = resultGraphics.GetHdc();
BitBlt(resultHdc, 0, 0, final.Width, final.Height, dcDesktop, (int)point.X + 3, (int)point.Y + 3, CopyPixelOperation.SourceCopy);
DrawIconEx(resultHdc, 0, 0, cursorInfo.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003);
//TODO: I have to try removing the background of this cursor capture.
//Native.BitBlt(resultHdc, 0, 0, final.Width, final.Height, dcDesktop, (int)point.X + 3, (int)point.Y + 3, Native.CopyPixelOperation.SourceErase);
resultGraphics.ReleaseHdc(resultHdc);
ReleaseDC(hDesktop, dcDesktop);
}
DeleteObject(iconInfo.hbmMask);
DeleteDC(dcDesktop);
DestroyIcon(hicon);
return final;
}
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
DestroyIcon(hicon);
}
var icon = Icon.FromHandle(hicon);
return icon.ToBitmap();
}
catch (Exception ex)
{
//You should catch exception with your method here.
//LogWriter.Log(ex, "Impossible to get the cursor.");
}
return null;
}
This version works with:
I-Beam cursors.
Black cursors.
Normal cursors.
Inverted cursors.
See working, here: https://github.com/NickeManarin/ScreenToGif/blob/master/ScreenToGif/Util/Native.cs#L991
Your description of a translucent 'gray' version of the I-beam cursor makes me wonder if you're encountering an issue with image scaling or mispositioning of the cursor.
One of the people posting on that site provided a (broken) link to a report with peculiar behavior that I've tracked down to: http://www.efg2.com/Lab/Graphics/CursorOverlay.htm
The examples on that page are not in C# but the author of the codeproject solution may have been doing something similar and I know I've screwed up my scaling when using the graphics object on plenty of occassions myself:
In any ImageMouseDown event once an
image is loaded, the CusorBitmap is
drawn with transparency on top of the
bitmap using the Canvas.Draw method.
Note some coordinate adjustments
(rescaling) are needed in case the
bitmap is stretched to fit in the
TImage.