Sameh_Saady
Sameh_Saady

Reputation: 1

Is it possible to make the string drawn on screen stable while moving the mouse cursor (using C#)?

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

Answers (1)

majixin
majixin

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

Related Questions