I owned complex drawing code with GDI+ which draws something like a chart on a user control.
If the user clicks with control pressed, a vertical marker line in dash style should be shown.
Now I look for a way to extend the drawing code without touching the complex drawing code.
I created a marker class which attaches to the mouse-up event of the user control.
In the eventhandler a check against (ModifierKeys == Keys.Control) is done.
If the user holds the control key and click with the left mouse button the draw method of the marker class is called with the Graphics object of the usercontrol as a parameter.
The current behaviour is that for each click a new line is drawn, but the line should be deleted and a new one should be drawn.
How can i erase the drawed line?
Do I have to redraw the comlete content of the user control?
You cannot delete a drawn line because there is no way to restore the underlying graphics.
What you could do is either:
Redraw the entire graph (without the line)
or
Stack a second transparent usercontrol on top and use that to display the line. Removing and drawing it when needed.
The answer here is clearly yes. With GDI+ you just draw directly on a bitmap buffer, so if you want to undo a previous drawing operation you can do one of those things (depending on the complexity of the issue and performance):
restore the bytes that have been changed on the bitmap buffer
reload a previous state of the drawing bitmap
A simple solution would be to have 2 bitmaps (something like that is usually called double buffering). One that is shown currently (and contains the final state) and one that is used for preview only. The preview one is always a copy of the first one - just with current modifications.
So basic algorithm for this simple implementation:
start with two bitmaps (blank but identical sizes) [named A and B]
if the user draws a line always make a copy of the bitmap A in B and draw on B - show B
if the user finishes the line then make a copy of B in A and again - show B
So always show the preview bitmap, which is just a modified bitmap of the original one.
Here is an example programming code in C# (assuming all events are connected and the preview Bitmap, B, is the picture box itself (which is just named pictureBox1 here):
Bitmap bmp;
bool isDrawing;
Point previous;
void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
isDrawing = true;
previous = e.Location;
}
void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
isDrawing = false;
}
void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (isDrawing)
{
using (Graphics g = Graphics.FromImage(bmp))
{
double wf = (double)bmp.Width / (double)pictureBox1.Width;
double hf = (double)bmp.Height / (double)pictureBox1.Height;
g.ScaleTransform((float)wf, (float)hf);
g.DrawLine(Pens.Black, e.Location, previous);
}
pictureBox1.Refresh();
previous = e.Location;
}
}
This code will do everything to display drawing a straight line from a point to another by simply pressing the left mouse button.
Related
I know there are many questions on this, but I just can't get it to work. I have this event:
private void pictureBox1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
e.Graphics.DrawRectangle(new Pen(selectionBrush, 1), selectionRectangle);
}
It works as it should. Draws a colored rectangle over the image I put inside picture box. But after I draw that rectangle, I want to erase it. I basically use it as selection area for cropping the image. Is there a way that I can erase the rectangle that I have drawn? Thanks.
Here is where I want to erase the rectangle:
private void button1_Click(object sender, EventArgs e)
{
//here are some examples of what I have tried
pictureBox1.Invalidate();
Invalidate();
}
Your Paint routine has a call to DrawRectangle. Then you invalidate the rectangle. At that point, your paint routine will get called again, and, if your code to draw the rectangle is still there, it will get redrawn. You need to make it so the DrawRectangle call no longer gets called once you don't need it any more (with an if statement or something).
You also probably want to inflate the size of the invalidation rectangle by one pixel all around to make sure you don't leave any bread crumbs behind
i have a class that draws waveforms of audio. I'm drawing it in OnPaint function. Now i need to draw a line that shows where on the waveform we are at current moment. I can calculate the position but when i try to draw it i need to call Invalidate() and that forces form to redraw all that waveform chart data (a lot of points).
So is there a way to put some transparent element over this one and then call Invalidate() only on that element? i was trying with picture box but no sucess...
//main form
private void timer100ms_Tick(object sender, EventArgs e)
{
customWaveViewer1.currentPosition = (int)((stream.Position / (float)stream.Length) * customWaveViewer1.Width);
customWaveViewer1.overlayLabel.Invalidate(false);
}
//drawing function in my class
private void overlayLabelInvalidate(object sender, PaintEventArgs e)
{
Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0));
e.Graphics.DrawLine(pen, currentPosition, 0, currentPosition, this.Height);
}
//constructor
public CustomWaveViewer()
{
InitializeComponent();
this.DoubleBuffered = true;
this.PenColor = Color.DodgerBlue;
this.PenWidth = 1;
overlayLabel = new PictureBox();
overlayLabel.Size = new System.Drawing.Size(this.Width, this.Height);
overlayLabel.Location = new Point(this.Left, this.Top);
overlayLabel.Visible = true;
overlayLabel.BackColor=Color.FromArgb(0,0,0,0);
overlayLabel.Paint += new System.Windows.Forms.PaintEventHandler(this.overlayLabelInvalidate);
Controls.Add(overlayLabel);
}
Actually what you are saying is not exactly true.
In the painteventargs there is a rectangle indicating the small portion of the window that needs to be repainted.
Also when you invalidate, you don't necessarily need to invalidate the whole form.
In your case you might want to invalidate only the old and new position of the marker that indicates where you are in the waveform.
So in your algorithm of your paint method, it is really up to you to make it efficient and only paint that part of the window that really needs repainting and to skip the part that does not need repainting.
It really can make a huge difference.
To make it even more look professional, set double buffering on.
No need to hastle with bitmaps of the whole image you have yourself, that is just what double buffering is all about, and forms can do it for you.
I copied following excerpt from https://msdn.microsoft.com/en-us/library/windows/desktop/dd145137(v=vs.85).aspx
BeginPaint fills a PAINTSTRUCT structure with information such as the dimensions of the portion of the window to be updated and a flag indicating whether the window background has been drawn. The application can use this information to optimize drawing. For example, it can use the dimensions of the update region, specified by the rcPaint member, to limit drawing to only those portions of the window that need updating. If an application has very simple output, it can ignore the update region and draw in the entire window, relying on the system to discard (clip) any unneeded output. Because the system clips drawing that extends outside the clipping region, only drawing that is in the update region is visible.
In this case there is no simple output and taking this into account is adviced.
I am not saying that creating a bitmap will not work. I am saying that optimizing your drawing logic will solve it just as well.
The information above still stands as windows forms is built on top of the old win32 api.
I would like to be able to fill any given area of an image with a given color, much like you can use paint to fill a rectangle, circle or any other shape delimited by a color.
To make this simpler I already have made the picture box source image to have the same size as the picture box itself, which should make things a bit easier.
How can I do this given that I have a picture box with an image and an already defined color and the user only has to click over the picture box to fill in any area with such color.
void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
// The color to be used
Color color = Color.Red;
// Image has same dimensions as picturebox to make things easier
Image img = pictureBox1.Image;
// Where it was clicked.
Point clickCoords = new Point(e.X, e.Y);
// Coloring that area clicked much like Paint does. How?
...
// After coloring show the result in the picture box again
pictureBox1.Image = img;
}
Thanks.
EDIT: Example of my desired behavior.
To make my goal obvious, let me add this small example.
You know MS Paint right?
You selected the pencil tool and start doing anything on the canvas, doesn't matter form or shape or even if the points your are doing with the pencil are connected or not.
Now you select the bucket tool and start clicking on the canvas. What will it do? Fill in the selected area according to the color you clicked over and how far she goes without changing with the color you have selected on the color pallet.
This is the behavior I want to emulate on my picture box mouse click event.
What you are looking for is a flood-fill algorithm. This should do the trick just fine.
The algorithm recursively searched for neighboring uncolored pixels, until it hits a wall.
You can use the code from here:
// Load the image (probably from your stream)
Image image = Image.FromFile( imagePath );
using (Graphics g = Graphics.FromImage(image))
{
Color customColor = Color.FromArgb(50, Color.Gray);
SolidBrush shadowBrush = new SolidBrush(customColor);
g.FillRectangles(shadowBrush, new RectangleF[] { rectFToFill });
}
image.Save( imageNewPath );
, but there is a one thing you missed - a rectFToFill, which you should define according your click event. There are a lot of strategies you can use, I suggest you to handle a Drag event or something like that, save the event start point and end point, after that define the rectangular you must fill:
You should examine the event points and after that create the Rectangle(F) structure using some of its constructors.
The Graphics class has a dozen methods you can use to draw some shapes or lines on your Image.
I exhaustively used the search but have not been able to find a satisfying solution to my problem.
I programmed a data visualization using a chart (datavisualization.charting.chart). This chart changes steadily since it shows some kind of simulation results.
I would like to draw for example a line upon the chart (depending on the mouse position) in order to show context sensitive information.
Therefore, I tried two implementations, none of both works exactly as I would like it to:
plotLine is called on MouseMove- Event
gGraph is created from the underlying graph using: chartCreateGraphics();
protected void plotLine(object sender, System.EventArgs e)
{
if (this.chart.Series.Count > 0) //ensure that the chart shows data
{
plotChart(); // this plots the underlying chart
penGraph = new Pen(Color.Black);
Point point1 = new Point(Form1.MousePosition.X - chart.Location.X, 0);
Point point2 = new Point(Form1.MousePosition.X - chart.Location.X,
chart.Location.Y + chart.Size.Height);
gGraph.DrawLine(penGraph, point1, point2);
penGraph.Dispose();
}
}
Here, the line disappears each time immediately after being plotted, but should remain as long as the mouse is not moved.
protected void plotLine(object sender, System.EventArgs e)
{
penGraph = new Pen(Color.Black);
Point point1 = new Point(Form1.MousePosition.X - chart.Location.X, 0);
Point point2 = new Point(Form1.MousePosition.X - chart.Location.X,
chart.Location.Y + chart.Size.Height);
gGraph.DrawLine(penGraph, point1, point2);
penGraph.Dispose();
}
Here, all plotted lines remain in the graphics surface as long as the chart is not plotted by new. (only the last line should remain in order to indicate the mouse position)
Can anybody help me?
You should be drawing in the OnPaint event. You are circumventing the update model and are seeing the effects of doing so. Sure, you do some drawing in MouseMove, but when the Paint event fires it will simply be erased.
Put your code in OnPaint first. On mouse move simply record whatever data you need to (i.e., mouse position) and then call Invalidate() on the chart. When you do that the Paint event will be called and your drawing code will fire.
Rule of thumb; never draw from anywhere but the Paint event.
I am trying to create this simple application in c#: when the user double clicks on specific location in the form, a little circle will be drawn. By one click, if the current location is marked by a circle - the circle will be removed.
I am trying to do this by simply register the MouseDoubleClick and MouseClick events, and to draw the circle from a .bmp file the following way:
private void MouseDoubleClick (object sender, MouseEventArgs e)
{
Graphics g = this.CreateGraphics();
Bitmap myImage = (Bitmap)Bitmap.FromFile("Circle.bmp");
g.DrawImage(myImage, e.X, e.Y);
}
My problem is that I dont know how to make the circle unvisible when the user clicks its location: I know how to check if the selected location contains a circle (by managing a list of all the locations containig circles...), but I dont know how exactly to delete it.
Another question: should I call the method this.CreateGraphics() everytime the user double-clicks a location, as I wrote in my code snippet, or should I call it once on initialization?
My personal preference is to put my images in instances of the Picturebox class. Reason being, I can simply call each Picturebox's Hide() function (or set 'Visible` to false).
What you're doing is drawing directly onto the window's client area, which technically isn't wrong but normally should be done in the form's Paint handler. If at some point you decide you don't want your circle to be visible anymore, you can call the form's Invalidate() method which triggers the Paint event. There, you explicitly do not draw your circle, and so to the user, the circle disappears.
The nice thing about a Picturebox is that it's persistent - you put your image into it and optionally draw on that image, but you only need to draw once. If you use the Paint handler technique, your drawing code gets called each time the form needs to redraw itself.
Edit:
Here's some code that illustrates my Paint handler information:
private void Form_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(); // clear any and all circles being drawn
if (CircleIsVisible)
{
e.Graphics.DrawEllipse( ... ); // OR, DrawImage( ) as in your example
}
}
private void MouseDoubleClick (object sender, MouseEventArgs e)
{
CircleIsVisible = true;
Invalidate(); // triggers Paint event
}
If you're drawing bitmaps, I would load the bitmap once and store it as a class variable. This way you don't need to hit the hard drive each time you want to draw. Dispose of the bitmap when you dispose of your class (in this case, your window).
I thinks you should clear all of the image you draw before your next double click.
Such as Graphics.Clear().
On the other hand, you should not to create Graphics object or dispose it every time.
If you have simple background color you could use Graphics.DrawEllipse to draw Circles and then just change circle color to the background color. Also you need to have a Collection of all circles you draw so you can access any circle that you've drawn.