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.
Related
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.
What I'm doing
I am working on a C#/.NET 4.7.2/WinForms app that draws a significant number of filled rectangles on a form using Graphics.FillRectangle.
Currently, the rectangles are drawn in the form's Paint event. After all the rectangles are drawn, a crosshair is drawn based on mouse position.
Whenever the mouse moves, Invalidate is called on the form to force a repaint so that the crosshair appears in its new position.
The problem
This is inefficient because the rectangles don't change, only the crosshair position, yet the rectangles are being redrawn every time. The CPU usage during mouse move is significant.
What next
I believe that the solution to this problem is to draw the rectangles to a buffer first (outside of the Paint event). Then, the Paint event only needs to render the buffer plus draw a crosshair on top.
Since I am new to GDI+ and manual buffering, I am wary of going down the wrong track. Google searches reveal plenty of articles on manual buffering, but each article seems to take a different approach which adds to my confusion.
I would be grateful for suggested approaches that favour simplicity and efficiency. If there is an idiomatic .NET way of doing this — the way it's meant to be done — I'd love to know.
Here's a quick and easy solution that doesn't require any buffering. To replicate this, start with a fresh Windows Forms project. I only draw two rectangles, but you can have as many as you want.
If you create a new WinForms project with these two member variables and these two handlers, you will get a working sample.
First, a couple of member variables for your form:
private bool _started = false;
private Point _lastPoint;
The started flag will turn to true after the first mouse move. The _lastPoint field will track the point at which the last cross-hairs was drawn (that's mostly why _started exists).
The Paint handler will draw the cross hairs every time it's called (you'll see why this is ok with the MouseMove handler):
private void Form1_Paint(object sender, PaintEventArgs e)
{
var graphics = e.Graphics;
var clientRectangle = this.ClientRectangle;
//draw a couple of rectangles
var firstRectangle = clientRectangle;
firstRectangle.Inflate(-20, -40);
graphics.FillRectangle(Brushes.Aqua, firstRectangle);
var secondRectangle = clientRectangle;
secondRectangle.Inflate(-100, -4);
graphics.FillRectangle(Brushes.Red, secondRectangle);
//draw Cross-Hairs
if (_started)
{
//horizontal
graphics.DrawLine(Pens.LightGray, new Point(clientRectangle.X, _lastPoint.Y),
new Point(ClientRectangle.Width + clientRectangle.X, _lastPoint.Y));
//vertical
graphics.DrawLine(Pens.LightGray, new Point(_lastPoint.X, clientRectangle.Y),
new Point(_lastPoint.X, ClientRectangle.Height + clientRectangle.Y));
}
}
Now comes the MouseMove handler. It's where the magic happens.
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
var clientRectangle = this.ClientRectangle;
var position = e.Location;
if (clientRectangle.Contains(position))
{
Rectangle horizontalInvalidationRect;
Rectangle verticalInvalidationRect;
if (_started)
{
horizontalInvalidationRect = new Rectangle(clientRectangle.X,
Math.Max(_lastPoint.Y - 1, clientRectangle.Y), clientRectangle.Width, 3);
verticalInvalidationRect = new Rectangle(Math.Max(_lastPoint.X - 1, clientRectangle.X),
clientRectangle.Y, 3, clientRectangle.Height);
Invalidate(horizontalInvalidationRect);
Invalidate(verticalInvalidationRect);
}
_started = true;
_lastPoint = position;
horizontalInvalidationRect = new Rectangle(clientRectangle.X,
Math.Max(_lastPoint.Y - 1, clientRectangle.Y), clientRectangle.Width, 3);
verticalInvalidationRect = new Rectangle(Math.Max(_lastPoint.X, clientRectangle.X - 1),
clientRectangle.Y, 3, clientRectangle.Height);
Invalidate(horizontalInvalidationRect);
Invalidate(verticalInvalidationRect);
}
}
If the cursor is within the form, I do a bunch of work. First I declare two rectangles that I will be using for invalidate. The horizontal one will be a rectangle that fills the width of the client rectangle, but is only 3 pixels high, centered on the Y coordinate of the area that I want to invalidate. The vertical one is as high as the client rectangle, but only 3 pixels wide. It's centered on the X coordinate of the area that I want to invalidate.
When the Paint handler runs, it virtually paints the entire client area, but only the pixels in the total invalidated area actually get drawn on the screen. Anything outside the invalidate area is left alone.
So, when the mouse moves, I create two rectangles (one vertical, one horizontal) that surround where the last set of cross-hairs were (so that when the pixels in those rectangles are drawn (including the background), the old cross-hairs are effectively erased) and then I create two new rectangles surrounding where the current cross-hairs should go (causing the background and the new cross-hairs to be drawn).
You are going to want to learn about invalidation rectangles if you have a complicated drawing app. For example, when the form is resized, what you want to do is invalidate only the newly unveiled rectangle(s), so that the whole drawing doesn't need to be rendered.
This works, but picking a color (or a brush) for the cross-hairs so that they always show can be difficult. Using my other suggestion (that you draw the lines twice (one to erase, one to draw) using an INVERT (i.e. XOR) brush is faster, and it always shows.
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.
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 am writing a program in Visual C# 2010 that has several icons on the form. When the mouse is placed over the icon (which is just an image) I want the icon to b highlighted via a border around the icon. In visual basic i can make a transparent rectangle with a colored border and position it over the icon. In C#, i can do the same however until i call invalidate multiple borders appear. The problem with calling invalidate is that my program is doing something in the background every second so the border keeps flashing (re-drawing).
Anyone got any ideas how i can implement this?
You didn’t say how you draw the border but from your description you are creating a graphics context for this. Don’t do this, it’s wrong. Instead, draw inside the Paint element of either the control or the its parent container.
The Paint event handler could look as follows:
private void yourControl_Paint(object sender, PaintEventArgs e)
{
if (! HasFocus(yourControl))
return;
Graphics g = e.Graphics;
using (Pen p = new Pen(Color.FromArgb(128, 0, 0, 128)))
g.DrawRectangle(p, 0, 0, yourControl.Width -1, yourControl.Height - 1);
}
This uses a hypothetical HasFocus method to determine whether this control should have a focus rectangle.
By the way, this is identical in VB and C#.