Drawing rectangles on picture box - c#

So I'm trying to automate drawing a bunch of rectangles over an image. I've mostly figured out how to draw them, but the problem I'm running into is that I only seem to be able to get them to actually be drawn when I interact with the picture box.
namespace myGUI
{
public partial class formPicture : Form
{
public formPicture()
{
InitializeComponent();
pbImage.Image = Image.FromFile(#"../../Images/myImage.bmp");
// Doesn't Work
var g = pbImage.CreateGraphics();
g.DrawRectangle(new Pen(Color.Red, 5), 500, 200, 20, 20);
}
private void formPicture_Shown(object sender, EventArgs e)
{
// Doesn't Work
var g = pbImage.CreateGraphics();
g.DrawRectangle(new Pen(Color.Red, 5), 500, 200, 20, 20);
}
private void pbImage_Click(object sender, EventArgs e)
{
// Works
var g = pbImage.CreateGraphics();
g.DrawRectangle(new Pen(Color.Red, 5), 500, 200, 20, 20);
}
}
}
The pbImage_Click() call successfully draws the rectangle on my picture box. The other two instances of trying to draw the rectangle don't seem to result in anything.
I have no idea why it would work in one case, but not the others? I've tried invalidating the graphics, that didn't work. I've tried adding it to the Paint event, that didn't work. I've tried adding it to the validated event, the visible event, the loaded event, pretty much any other event that I can think of such that once the GUI was active it might actually result in the rectangle being drawn.
The only time it seems to work is Click or MouseClick (I haven't tried other interactive events, such as MouseUp or MouseDown, but I presume those would work as well. Besides, the whole point is that I want it to show up automatically.)

NEVER call CreateGraphics. If you want to draw on a control, handle the Paint event of that control and use the e.Graphics property provided. If the drawing needs to change, store the data that describes the drawing in one or more fields, then read those fields in the Paint event handler. If you need to force the drawing to change, modify those fields and then call Invalidate on the control. Ideally, pass an argument to that Invalidate method that describes the smallest area that has or might have changed. Here's one I and a friend prepared earlier.
Note that WinForms controls are painted and repainted quite often, so any drawing done outside the Paint event handler is likely to be wiped. That's exactly what's happening in the cases that you say are not working. The drawing is being done but is then wiped. By doing your drawing in the Paint event handler, you ensure that it is reinstated every time it is wiped, so it appears permanent.

Related

Invalidate without OnPaint

I'm trying to make an animation in Win Forms, and I have tried a different approach than calling Invalidate() in the timer_elapsed handler and then doing things in the OnPaintHandler.
I just directly draw an image on a pictureBox, then when the timer_ticks, I call Invalidate, and then I manually redraw the image on updated position.
static void tmrMoving_Elapsed(object sender, EventArgs e, Train t)
{
MainForm.playBox.Invalidate();
g.DrawImage(t.components[i], new Rectangle(
new Point(t.nextVagoonPositionX * 20, t.nextVagoonPositionY * 20),
new Size(20, 20)));
t.nextVagoonPositionX += 1;
t.nextPositionX = -1;
}
I have a problem, that when the timer ticks, the desired image sometimes get drawn, and sometimes not, like 30% chance to not draw(I have 1sec timer intervals so I can see it), even if the object is moving or just being in the same location.
Things I have tried:
1.Setting a region in Invalidate() args -> the image got drawn many times next to the older images (So it wasn't repaint at the desired location as the old images remained in their locations)
2.Calling Update() or Refresh() or both after the invalidate-> No image was ever shown
3.Combining Update() or Refresh() or both with Invalidate(rectangulous region) -> no image ever shown
4.Setting DoubleBuffering true -> no effect
5.Fiddling with this.SetStyle(ControlStyles.UserPaint, true); -> no effect (not even sure what it does btw.)
You're not supposed to directly paint UI controls outside of the OnPaint handler. In "lower level" WinAPI, you'd only painting to the screen on WM_PAINT, which is what raises the OnPaint event.
Move your drawing code (g.DrawImage(...)) out of the timer handler and into the OnPaint handler.

Winforms: How to draw line over control without invalidating it

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.

how to draw rectangle by selecting four coordinate points by user in C# windows form and display the coordinate points in text box

In my c# windows form application I am trying to draw a rectangle by getting the coordinates from the user through 4 mouse click events in the windows form, one for each point.
Here is what I've tried so far.
private void Form1_Click(object sender, EventArgs e)
{
using (Graphics g = this.CreateGraphics())
{
Pen pen = new Pen(Color.Black, 2);
Brush brush = new SolidBrush(this.BackColor);
g.FillRectangle(brush, this.Bounds); // redraws background
g.DrawRectangle(pen,textBox1.Text,textBox2.Text,textBox3.Text,textBox4.Text);
pen.Dispose();
brush.Dispose();
}
}
Your first mistake is drawing in a Click handler. Don't use CreateGraphics. Anything you draw with that is volatile and unlikely to play well.
What you should do is collect the points you want to draw when the Click event fires. Add a handler for the form's Paint event and do your drawing there. The event args will provide a Graphics object for you to use.
A separate method for calculating the rectangle might also be useful to keep that work out of the Paint handler.

Redrawing rectangle on a Panel

In my code I am drawing a rectangle as a "frame" of a Panel. I am getting the required color from XML file as a string (like "red", "blue" etc.). While creating the panel, I am painting it using this code:
Strip.Paint += (sender, e) =>
{
//MessageBox.Show(clr.ToString());
Pen p = new Pen(Color.FromName(color), 2); // color is the string with name of the color
Rectangle r = new Rectangle(1, 1, 286, 36);
e.Graphics.DrawRectangle(p, r);
p.Dispose();
e.Dispose();
};
In the method that is supposed to refresh the rectangle, I add this line
Strip.Refresh();
This works fine. But, using Timer every 30 seconds I check if the color has changed, and
if it did, redraw the rectangle with requested color. The first rectangle draws correctly. But when the Timer reaches 30, it just... Well I am not sure even how to describe it, here is the picture what it does after "refreshing":
The "Red Cross" is what happens when an exception is thrown inside a OnPaint method. It means that you have a bug in your code inside the Paint lambda.
Once an exception is thrown, an internal flag is set and the control will no longer attempt to repaint itself. This is reset only when the application is run again, or when this trick is performed.
I suspect know the problem in your case is that you are not supposed to Dispose() the PaintEventArgs object in a Paint event.
In general, you should not have to dispose of objects you did not create yourself.

Controlling the visibility of a Bitmap in .NET

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.

Categories

Resources