Draw and Move Line with Mouse Move Event - c#

I'm able to draw vertical line anywhere on the screen with following code:
[DllImport("User32.dll")]
public static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("User32.dll")]
private void Form2_Load(object sender, EventArgs e)
{
// DRAWING
IntPtr desktopPtr = GetDC(IntPtr.Zero);
Graphics f = Graphics.FromHdc(desktopPtr);
Pen pen = new Pen(Color.FromArgb(255, 255, 111, 0));
f.DrawLine(pen, this.Location.X, this.Location.Y, this.Location.X, 100);
f.Dispose();
ReleaseDC(IntPtr.Zero, desktopPtr);
}
Above sample code draw the vertical line on coordinates where form is open but however this line disappear when i move anything over it (cursor, form, etc).
I want to keep this line until application is closed.
Next what i Want to achieve is make this line movable with mouse move events. Is it possible?

Related

Preview graphic shape before applying [duplicate]

My goal is very simple. Imagine opening MSPaint, clicking the line tool, holding mouse down, and dragging it around. It anchors the starting coordinates where you clicked mouse down and constantly draws and redraws a line to your current position.
Except me trying to do this in C# isn't working as well as I would hope.
[DllImport("user32.dll")]
static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("User32.dll")]
static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);
protected override void OnPaint(PaintEventArgs e)
{
endingPoint = GetMouseCoords();
DrawLine(startingPoint, endingPoint);
}
private void DrawLine(Point startingCoords, Point endingCoords)
{
IntPtr desktop = GetDC(IntPtr.Zero);
Pen pen = new Pen(Brushes.Red, 3);
using (Graphics g = Graphics.FromHdc(desktop))
{
g.DrawLine(pen, startingCoords.X, startingCoords.Y, endingCoords.X, endingCoords.Y);
g.Dispose();
}
ReleaseDC(IntPtr.Zero, desktop);
}
Using it this way, I only get the line drawn once. However, if I move the DrawLine() to a more static event like MouseUp, it will draw it, then disappear after about a quarter of a second.
What would be the best way to accomplish my goal here?
I would think that whatever event is being used to make the line disappear is what I would want to attach the drawing of the line to in the first place.
You need to have two drawing calls:
One for the non-persistent line that follows the cursor in the MouseMove using someControls.CreateGraphics
the other for the persisting line, triggered in the MouseUp, where
you store the coordinates and
call Invalidate on your canvas control and
draw in the Paint event of your canvas using its e.Graphics object.
Here is a minimal example code:
List<Point> allPoints = new List<Point>();
Point mDown = Point.Empty;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
mDown = e.Location;
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
allPoints.Add(e.Location);
pictureBox1.Invalidate();
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left))
{
pictureBox1.Refresh();
using (Graphics G = pictureBox1.CreateGraphics())
G.DrawLine(Pens.Red, mDown, e.Location);
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (allPoints.Count > 1) e.Graphics.DrawLines(Pens.Black, allPoints.ToArray());
}
Note that this uses a PictureBox as the canvas control. It is the control meant for this kind of interaction. Your code seems to draw onto the desktop, which doesn't belong to you. Drawing onto it in a persistent manner is not anything like what you would do with the/any Paint application.
Also note that my example stores a list of points and draws them as one non-closed Polyline. To draw them closed exchange DrawLines for DrawPolygon! To draw several such polylines or polygons you need to..
..decide on the user interface for it, maybe add segment points only while the control-key is pressed and otherwise finish the current polyline
store the points in a List<List, Point>>
Also note that this is one of the rare examples where using control.CreateGraphics is called for, as you actually do want a non-persistent drawing while the user moves the mouse.
In most other cases the Winforms graphics basic rule #1 applies:
Never use control.CreateGraphics! Never try to cache a Graphics object! Either draw into a Bitmap bmp using a Graphics g = Graphics.FromImage(bmp) or in the Paint event of a control, using the e.Graphics parameter..

this.Region Path data leads to a tiny window

So I created a simple form to test out using an SVG image to draw a custom shaped window. Inspiration found here
It seems to work fine, but no matter what I do my window size is too small to put any controls on.
Reasons for doing this: It's cool? Windows needs better themeing support. I was bored!
I am using Svg from nuget.com from within Visual Studio
Code:
using Svg;
public const int WM_NCLBUTTONDOWN = 0xA1; public const int HT_CAPTION = 0x2;
internal class NativeMethods
{
// Allows forms with Toolbox property set to false to be moved
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.DllImportAttribute("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool ReleaseCapture();
}
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
System.Drawing.Drawing2D.GraphicsPath frmshp = new System.Drawing.Drawing2D.GraphicsPath();
//frmshp.AddEllipse(0, 0, this.Width, this.Height);
//SvgDocument.Open(#"TestWindowsshape.svg");
SvgDocument newshp = SvgDocument.Open(#"TestWindowsshape.svg");
frmshp = (newshp.Path);
this.Region = new Region(frmshp);
}
private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Make settings window movable without a titlebar
if (e.Button == MouseButtons.Left)
{
NativeMethods.ReleaseCapture();
NativeMethods.SendMessage(Handle, WM_NCLBUTTONDOWN (IntPtr)HT_CAPTION, (IntPtr)0);
}
}
I have tried to increase the svg size, played with the code some, but nothing I do will make the drawn window bigger. I know I can do this with a BMP, and the TransparancyKey option, but I would like to not do it that way, since the BMP & transparency method has the drawback of not being able to use one color in the bitmap itself. Any advice would be appreciated
Edit:
Matrix m = new Matrix();
m.Scale(100, 100, MatrixOrder.Append);
m.Translate(100, 100, MatrixOrder.Append);
newshp.Path.Transform(m);
Has been tried, with no effect. I would assume that this should have worked does that mean the problem is within my SVG?
The problem seems to be in the SVG file, i adjusted the size of a Rectangle in Notepad++ and got a bigger windows, however more complex shapes will be a hassle. It seems Inkscape cannot create SVGs of a reliable size... I have the "Document properties" set to my screen resolution, but the vectors all turn out too small. Perhaps Illustrator can do this properly.

Preserve painting after resize or refresh

How can I preserve the painting I drew on a picturebox?
I draw a circle, and fill it via the ExtFloodFill API.
This works fine.
When I resize the form (or minimize it) and resize it back to its original size part of the painting is gone.
When I refresh the picturebox the painting will be gone completely
I tried to repaint it in the Paint event, but this caused it to be repainted continuously as the painting itself triggered the Paint event as well.
See below for a test project.
When you click on the picturebox the painting will be drawn.
When you doubleclick the picturebox will be refreshed.
[1 form with 1 picturebox named pictureBox1]
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace FloodFill
{
public partial class Form1 : Form
{
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateSolidBrush(int crColor);
[DllImport("gdi32.dll")]
public static extern bool ExtFloodFill(IntPtr hdc, int nXStart, int nYStart, int crColor, uint fuFillType);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
public static extern int GetPixel(IntPtr hdc, int x, int y);
public static uint FLOODFILLSURFACE = 1;
public Form1()
{
InitializeComponent();
}
private void pictureBox1_Click(object sender, EventArgs e)
{
DrawCircle();
FillGreen();
}
private void DrawCircle()
{
Graphics graBox = Graphics.FromHwnd(pictureBox1.Handle);
graBox.DrawEllipse(Pens.Red, 10, 10, 100, 100);
}
private void FillGreen()
{
Graphics graBox = Graphics.FromHwnd(pictureBox1.Handle);
IntPtr ptrHdc = graBox.GetHdc();
IntPtr ptrBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
SelectObject(ptrHdc, ptrBrush);
ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
DeleteObject(ptrBrush);
graBox.ReleaseHdc(ptrHdc);
}
private void pictureBox1_DoubleClick(object sender, EventArgs e)
{
pictureBox1.Refresh();
}
}
}
How can I preserve the painting I make when my form or the picturebox is resized or in any other way to be refreshed?
[EDIT]
I changed my Paint event to the following:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
DrawCircle();
FillGreen();
}
And now the circle is being redrawn after a resized, but the FloodFill isn't
(I also gave the picturebox a lightblue background for another test)
[EDIT2]
I changed the Paint event to use the Graphics g as follows:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
DrawCircle(g);
FillGreen(g);
}
private void DrawCircle(Graphics g)
{
g.DrawEllipse(Pens.Red, 10, 10, 100, 100);
}
private void FillGreen(Graphics g)
{
IntPtr ptrHdc = g.GetHdc();
IntPtr ptrBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
SelectObject(ptrHdc, ptrBrush);
ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
DeleteObject(ptrBrush);
g.ReleaseHdc(ptrHdc);
}
But when I resize back to the original size some lines of the FloodFill are skipped, especially when I resize slowly
Using GDI+ methods for drawing the code is simply:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Rectangle rect = new Rectangle(10, 10, 100, 100);
e.Graphics.FillEllipse(Brushes.Green, rect);
using (Pen pen = new Pen(Color.Red, 2f))
{
e.Graphics.DrawEllipse(pen , rect);
}
}
None of your DllImport or constants is needed at all.
Note that I chose to use a Pen with a width of 2f to demonstrate the use of the using clause to correctly create and dispose of the GDI+ (non-standard) object Pen
This will persist as it is drawn whenever needed. To draw it initially you must call pictureBox1.Invalidate(); once!
If you want to change the coordinates you should move the variable rect to class level, set it to new data and the call Invalidate on the PictureBox! The same goes for Colors or the Pen.Width: use class level variables and after each change trigger the Paint event by calling Invalidate()..
Once you understand that, the most important part of learning drawing in GDI+ is done..
For filling any drawings you have three options:
simple shapes draw with DrawXXX methods all have a FillXXX method.
complex shapes can be created with a GraphicsPath, which also has both a DrawXXX methods and a FillXXX method.
areas not created by shapes but by drawn pixels must be filled with a floodfill method. There is non buit-in in GDI+, but can simply write your own. maybe like the Fill4 in this answer..
Update: If you feel better using the FloodFill from gdi32.dll you can do so, but please change the code to use the Graphics object from the Paint event:
FillGreen(e.Graphics);
private void FillGreen(Graphics graBox)
{
IntPtr ptrHdc = graBox.GetHdc();
IntPtr ptrBrush = CreateSolidBrush(ColorTranslator.ToWin32(Color.Green));
SelectObject(ptrHdc, ptrBrush);
ExtFloodFill(ptrHdc, 50, 50, GetPixel(ptrHdc, 50, 50), FLOODFILLSURFACE);
DeleteObject(ptrBrush);
graBox.ReleaseHdc(ptrHdc);
}
for better flexibility you can probably make the params all dynamic, but I'm not used to that old library..
Note that the order of calls matters: FillXXX wil cover some of the pixels drawn by DrawXXX, so it must come first. FloodFill however depends on the bounding lines, in your case the circle, being drawn first, so it must come after..

How to convert System.Windows.Input.Cursor to ImageSource?

I have a .cur file path ("%SystemRoot%\cursors\aero_arrow.cur") witch I want to display in an image control. So I need to convert Cursor to ImageSource. I tried both CursorConverter and ImageSourceConverter but had no luck. I also tried creating Graphics from the cursor and then converting it to Bitmap, But that didn't work either.
This thread says:
it is complicated to convert Cursor to Icon direcetly because Cursor
doesn't expose the imagesource it use.
and
If you really want to bind an image to cursor, there is an approach
you might want to try.
Since WindowForm is capable of drawing the cursor, we can use
WindowForm to draw cursor on a bitmap. After that we could find a way
to copy that bitmap to something WPF support.
Now the funny thing is that I can't create a new instance of System.Windows.Forms.Cursor with neither the file path nor the stream since It throws the following exception:
System.Runtime.InteropServices.COMException (0x800A01E1):
Exception from HRESULT: 0x800A01E1 (CTL_E_INVALIDPICTURE)
at System.Windows.Forms.UnsafeNativeMethods.IPersistStream.Load(IStream pstm)
at System.Windows.Forms.Cursor.LoadPicture(IStream stream)
So can anybody tell me the best way to convert System.Windows.Input.Cursor to ImageSource?
And what about .ani cursors? If I remember correctly System.Windows.Input.Cursor does not support animated cursors, So how can I show them to the user? Converting them to gif then using the 3d party gif libraries?
I found the solution in this thread: How to Render a Transparent Cursor to Bitmap preserving alpha channel?
So here is the code:
[StructLayout(LayoutKind.Sequential)]
private struct ICONINFO
{
public bool fIcon;
public int xHotspot;
public int yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
[DllImport("user32")]
private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO pIconInfo);
[DllImport("user32.dll")]
private static extern IntPtr LoadCursorFromFile(string lpFileName);
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool DeleteObject(IntPtr hObject);
private Bitmap BitmapFromCursor(Cursor cur)
{
ICONINFO ii;
GetIconInfo(cur.Handle, out ii);
Bitmap bmp = Bitmap.FromHbitmap(ii.hbmColor);
DeleteObject(ii.hbmColor);
DeleteObject(ii.hbmMask);
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
Bitmap dstBitmap = new Bitmap(bmData.Width, bmData.Height, bmData.Stride, PixelFormat.Format32bppArgb, bmData.Scan0);
bmp.UnlockBits(bmData);
return new Bitmap(dstBitmap);
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
//Using LoadCursorFromFile from user32.dll, get a handle to the icon
IntPtr hCursor = LoadCursorFromFile("C:\\Windows\\Cursors\\Windows Aero\\aero_busy.ani");
//Create a Cursor object from that handle
Cursor cursor = new Cursor(hCursor);
//Convert that cursor into a bitmap
using (Bitmap cursorBitmap = BitmapFromCursor(cursor))
{
//Draw that cursor bitmap directly to the form canvas
e.Graphics.DrawImage(cursorBitmap, 50, 50);
}
}
It's written for Win Forms and draws an image. But can be used in wpf as well with referencing to System.Windows.Forms. and then you can convert that bitmap to bitmap source and show it in an image control...
The reason I'm using System.Windows.Forms.Cursor instead of System.Windows.Input.Cursor is that I can't get to create a new instance of cursor using the IntPtr handle...
Edit: The above method does NOT work with cursors having low color bits.
An alternative is to use Icon.ExtractAssociatedIcon instead:
System.Drawing.Icon i = System.Drawing.Icon.ExtractAssociatedIcon(#"C:\Windows\Cursors\arrow_rl.cur");
System.Drawing.Bitmap b = i.ToBitmap();
Hope that helps someone ...

Drawing and clearing on screen with Graphics.FromHwnd

I am trying to create a program which gets the handle of the window under your cursor, show's some data about it and draws a filled rectangle (with very low alpha) on top of the whole window. I am using C# and winforms.
I have succeeded in doing so, but the problem is my draw method is in a BackgroundWorker's loop and it keeps making more and more rectangles (-> rectangle with higher alpha) on the window or when moving mouse to another window the old one still exists.
I haven't managed to find a method to clear the drawn rectangle as it just "is" on the screen and isn't bound to the graphics object or anything.
I have tried using certain native methods such as
[DllImport("User32.dll")]
public static extern Int64 SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);
[DllImport("user32.dll")]
public static extern bool UpdateWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RedrawWindowFlags flags);
but none of the above has worked correctly. Some of them do work but as the messages get in the queue the redrawing doesn't occur immediately or is very slow and glitched (flickering etc).
So, the question is, how would I "remove" the rectangle I have drawn using Graphics.FromHwnd(handleOfWindowUnderCursor)? I actually think it doesn't matter that it is drawn on other window as I have had the very same problem earlier when trying to get rid of the drawings on my own form too (never got that fixed either!).
Alternatively, any suggestions on how I could accomplish drawing and removing the rectangle on the window under cursor without using the methods I am now?
I noticed that drawing using
Graphics g = Graphics.FromHwnd(form.Handle);
draws on the form background, under its controls. Is it whatyou want to acomplish?
// draw the rectangle
Brush b = new SolidBrush(Color.FromArgb(20, 0, 0, 255));
g.FillRectangle(b, new Rectangle(5, 5, 200, 200));
// clear the rectangle
g.Clear(this.BackColor);
If I draw on the screen directly, with this:
Graphics g = Graphics.FromHwnd(IntPtr.Zero);
the rectangle disapears immediately after Windows refreshes the screen.
There is a third option, which is not realy strightforward.
Instead of drawing a rectangle, create a form with lowered opacity, TopMost property set to true and without borders. Then make it transparent to events:
protected override void WndProc(ref Message m)
{
const int WM_NCHITTEST = 0x0084;
const int HTTRANSPARENT = (-1);
if (m.Msg == WM_NCHITTEST)
{
m.Result = (IntPtr)HTTRANSPARENT;
}
else
{
base.WndProc(ref m);
}
}
The only things you have to take care after that is this form's Visible, Location and Size properties.
bool change = false;
private void timer1_Tick(object sender, EventArgs e)
{
try
{
if (change)
{
InvalidateRect(IntPtr.Zero, IntPtr.Zero, true);
change = false;
}
else
{
PaintRectangleToScreen();
change = true;
}
}
catch (System.Exception caught)
{
MessageBox.Show(caught.Message);
}
}

Categories

Resources