Reputation: 1
I'm trying to draw a string on the screen (attached to the mouse cursor) using C#, and the string value and its location change with the mouse movement. The problem here is when hovering the mouse, the string is drawn multiple times in multiple locations and then disappears after a while (< 1 second but it's remarkable). Is there any way to make it stable and not behave like that?Image I'm aiming to make it smooth as it behaves in this video: 30 seconds Video This is part of the code I used:
System.Drawing.Point drawPoint = new System.Drawing.Point(mouse_click_x, mouse_click_y);Graphics g = Graphics.FromHdc(desktopPtr);
g.DrawString(oState1.SmartTagText(oPath), drawFont, drawBrush, drawPoint);
Upvotes: 0
Views: 194
Reputation: 321
It sounds like your core issue is not restoring the screen area before updating the label position. Therefore, your old rendering of text still exists. You need to restore that screen area to how it was before you rendered it.
For a quick example, create a console application and make this your Main method...
static void Main(string[] args)
{
ScreenPositionLabel screenPositionLabel = new ScreenPositionLabel();
_ = screenPositionLabel.StartAsync();
Console.ReadLine();
screenPositionLabel.Stop();
}
You can exit the sample console application by hitting [Enter] once running.
The ScreenPositionLabel
class is as follows (with inline comments)...
internal class ScreenPositionLabel
{
/*
* Windows API functions we require to be able to interact
* with screen without a Graphics object nherently linked
* to only a Windows Form
*/
[DllImport("User32.dll")]
public static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("User32.dll")]
public static extern void ReleaseDC(IntPtr dc);
[DllImport("user32.dll")]
static extern bool GetCursorPos(out POINT lpPoint);
/*
* This is just a hacky flag for the while loop triggering MouseMove method.
* Ideally, since this is running as an async Task, we should just make it
* support a cancellation token
*/
private bool keepRunning = true;
// A reference to a class that can save/restore a small area of the screen
private ClipRegion clipRegion = new ClipRegion();
// A struct the Windows API uses to return mouse position
public struct POINT
{
public int X;
public int Y;
}
// An offset horizontally from cursor X position so that text doesn't seem under the cursor
private int X_OFFSET = 32;
// Gets the mouse postion and return as .NET System.Drawing.Point instance
private System.Drawing.Point GetMousePosition()
{
POINT point;
if (GetCursorPos(out point))
{
return new System.Drawing.Point(point.X + X_OFFSET, point.Y);
}
return System.Drawing.Point.Empty;
}
// start reporting the mouse/cursor position wth a label next to the cursor
public async Task StartAsync()
{
await Task.Run(MonitorCursorPositionAsync);
}
public void Stop()
{
keepRunning = false;
}
// Task that monitors for mouse movement
private Task MonitorCursorPositionAsync()
{
return Task.Run(() =>
{
Point point = Point.Empty;
Point lastPoint = Point.Empty;
while (keepRunning)
{
// read the cursor position
point = GetMousePosition();
// this is a hacky mouse-move detector - only call mouse move when position has changed
if (point != lastPoint)
{
MouseMove(point);
lastPoint = point;
}
lastPoint = point; // remember the current position for next iteration as 'lastPoint'
}
});
}
/*
* This method runs when the user moves the mouse.
* Before rendering any text label via Graphics.DrawString it
* checks to see if we have a previous capture of the underlying screen
* and redraws that to restore the screen appearance prior to drawing
* the text label at the new cursor position.
*/
private void MouseMove(Point point)
{
// grab handle of the desktop and create a Graphics instance for it
IntPtr desktopPtr = GetDC(IntPtr.Zero);
Graphics g = Graphics.FromHdc(desktopPtr);
// if we've previously rendered text, restore that region before we render text in updated position
if (clipRegion.IsValid)
clipRegion.RestoreRegion(g);
// deduce the text that will show the screen position
string text = string.Format("(X:{0}, Y:{1})", point.X, point.Y);
// create the font
Font font = new Font(FontFamily.GenericSansSerif, 10, FontStyle.Regular, GraphicsUnit.World);
// measure how big the underlying size of the region the text will use when rendered
Size textSize = g.MeasureString(text, font).ToSize();
// save text render region before rendering text
clipRegion.SaveRegion(textSize, point);
// render the text with a black background
Rectangle drawRectangle = new Rectangle(point.X, point.Y, textSize.Width, textSize.Height);
Brush bgBrush = new SolidBrush(Color.Black);
g.FillRectangle(bgBrush, drawRectangle);
Brush fontBrush = new SolidBrush(Color.Red);
g.DrawString(text, font, fontBrush, new PointF(point.X, point.Y));
// dispose of graphics objects no longer needed
bgBrush.Dispose();
fontBrush.Dispose();
font.Dispose();
g.Dispose();
}
}
...and the above makes use of a ClipRegion class, which is used to save a portion of the screen as an image, which you set before rendering text. You also redraw this image when the cursor position is updated, so that you restore the screen to how it would be without text. Then you draw the text at the updated position.
The ClipRegion class is...
internal class ClipRegion
{
private bool isValid = false; // flag for if we're storing an image or not
private Image clippedRegion; // the image of an area of screen we saved
private Point point; // the point the clippedRegion was at
public void SaveRegion(Size size, System.Drawing.Point screenPoint)
{
// create an image for stroing the region of the screen we wish to save
clippedRegion = new Bitmap(size.Width, size.Height) as Image;
// save the screen point so that RestoreRegion can use it later
point = screenPoint;
//Create the Graphic reference for region define by size wth respect to screenPoint
using (Graphics graphics = Graphics.FromImage(clippedRegion as Image))
{
graphics.CopyFromScreen(screenPoint.X, screenPoint.Y, 0, 0, size);
}
isValid = true;
}
public void RestoreRegion(Graphics g)
{
// if we have a bitmap of the screen saved, restore it
if (isValid)
{
g.DrawImage(clippedRegion, point);
isValid = false;
}
}
public bool IsValid { get { return isValid; } }
}
It is possible for the label to disappear when something else grabs focus. If the mouse is simply moved, the label will reappear.
Upvotes: 0