I have a growing rectangle drawn on top of a TableLayoutPanel but when it grows it causes a horrible flicker even with Double Buffer.
I am using e.Graphics.FillRectangle and a global variable that increases the size of the rectangle. I set up a timer to make it grow every 1/10th of a second by 1 pixel. Why does it flicker so much and how can I stop the flicker?
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width-grow)/2, (tableLayoutPanel1.Height-grow)/2, grow,grow);
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Refresh();
}
In order to rule out all other possibilities I removed everything from my program and started from scratch with just a growing rectangle and it still creates this horrible flicker.
Ok, here is the code. You first need to make background buffer Bitmap with the size of your control. After that, you will need to draw everything on the Bitmap, and than draw that bitmap onto the control.
Bitmap backBuffer = null;
int grow = 100;
private void tableLayoutPanel1_Paint(object sender, PaintEventArgs e)
{
if (backBuffer == null)
backBuffer = new Bitmap(tableLayoutPanel1.Width, tableLayoutPanel1.Height);
Graphics g = Graphics.FromImage(backBuffer);
g.Clear(tableLayoutPanel1.BackColor);
g.FillRectangle(Brushes.Red, (tableLayoutPanel1.Width - grow) / 2, (tableLayoutPanel1.Height - grow) / 2, grow, grow);
e.Graphics.DrawImage(backBuffer, 0, 0, backBuffer.Width, backBuffer.Height);
g.Dispose();
}
private void tableLayoutPanel1_Resize(object sender, EventArgs e)
{
backBuffer = null;
}
private void timer1_Tick(object sender, EventArgs e)
{
grow += 10;
tableLayoutPanel1.Invalidate();
}
Note that you will need to create new Bitmap each time you resize the TableLayoutPanel. In addition I suggest using Invalidate() instead of Refresh().
But this will still include some potential flickering. In order to completely avoid flicker, in addition to the previous code, you will need to subclass the TableLayoutPanel and override the OnPaintBackground() method in such a way that base.OnPaintBackground is never called. This way way won't have any flickering at all. The reason why you have the flickering is because the background is redrawn before your Rectangle, any time you draw it. Switch the original TableLayoutPanel class with this BackgroundlessTableLayoutPanel
public class BackgroundlessTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
}
}
Most controls have a Paint event where you can implement any custom drawing you need to do. It is also possible to implement your own control where you override the OnPaint method. See the article here.
Both these should give ok results.
Related
I have some code which renders an image to a form and using a trackbar to rotate that image. The code was taken from a you tube tutorial and works. However, when the image rotates it flickers. The tutorial poster says that using threads will remove the flickering, but I have no clue as to how to go about doing that. Any help would be very much appreciated. The code is set out below:
public partial class Form1 : Form
{
Image img;
int angle;
public Form1()
{
InitializeComponent();
}
Thread th;
Graphics graphicToDraw;
Graphics graphicToScreen;
Bitmap bmp;
private void Form1_Load(object sender, EventArgs e)
{
img = Image.FromFile(#"c:\images\clock face with second hand 2.png");
}
private void Form1_Paint(object sender, PaintEventArgs paint)
{
bmp = new Bitmap(img.Width / 2, img.Height / 2);
graphicToDraw = Graphics.FromImage(bmp);
graphicToDraw.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
graphicToDraw.RotateTransform(angle);
graphicToDraw.TranslateTransform(-bmp.Width / 2, -bmp.Height / 2);
graphicToDraw.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphicToDraw.DrawImage(img, 0, 0);
paint.Graphics.TranslateTransform(this.Width / 2, this.Height / 2);
paint.Graphics.DrawImage(bmp, -bmp.Width / 2, -bmp.Height / 2);
Debug.WriteLine("image width = " + bmp.Width);
Debug.WriteLine("image height = " + bmp.Height);
}
private void Form1_Resize(object sender, EventArgs e)
{
Invalidate();
}
private void trackBar1_ValueChanged(object sender, EventArgs e)
{
angle = trackBar1.Value;
Invalidate();
}
}
Using threads is probably the wrong approach here. See How to: Reduce Graphics Flicker with Double Buffering for Forms and Controls. I.e. set DoubleBuffered to true for your form or control.
DoubleBuffered = true;
You can not use any background thread to draw to the UI. Only the UI thread is allowed to access any UI classes, and the paint event will always be called on the UI thread. You could use a background thread to do the intermediate drawing to the buffer. But as far as I can see this is completely unnecessary, you should just set whatever transform you need on the graphics object and draw directly to it, there should be no need for the intermediate buffer. And if you do use a intermediate buffer, please ensure everything is correctly disposed.
The main case where such a background buffer could be useful is if you have lots of complicated drawing to do, and you only need to update the buffer infrequently. Then it may make sense to do the drawing to this buffer on a background thread.
I'm trying to understand the graphics, and in the Graphics.FromImage documentation, it has this as an example:
private void FromImageImage(PaintEventArgs e)
{
// Create image.
Image imageFile = Image.FromFile("SampImag.jpg");
// Create graphics object for alteration.
Graphics newGraphics = Graphics.FromImage(imageFile);
// Alter image.
newGraphics.FillRectangle(new SolidBrush(Color.Black), 100, 50, 100, 100);
// Draw image to screen.
e.Graphics.DrawImage(imageFile, new PointF(0.0F, 0.0F));
// Dispose of graphics object.
newGraphics.Dispose();
}
I'm new to C# and Windows Forms and am struggling to understand how this all fits together. How is this code run, say when the form first loads or when a button is pressed?
Maybe this will help. I have an example of both drawing on Paint events but also drawing on top of an existing Image. I created a form with two picture boxes. One for each case. pictureBox1 has an event handler for the .Paint event, while pictureBox2 is only drawn when a button is pressed.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
pictureBox1.BackColor=Color.Black;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
// The code below will draw on the surface of pictureBox1
// It gets triggered automatically by Windows, or by
// calling .Invalidate() or .Refresh() on the picture box.
DrawGraphics(e.Graphics, pictureBox1.ClientRectangle);
}
private void toolStripButton1_Click(object sender, EventArgs e)
{
// The code below will draw on an existing image shown in pictureBox2
var img = new Bitmap(pictureBox2.Image);
var g = Graphics.FromImage(img);
DrawGraphics(g, pictureBox2.ClientRectangle);
pictureBox2.Image=img;
}
void DrawGraphics(Graphics g, Rectangle target)
{
// draw a red rectangle around the moon
g.DrawRectangle(Pens.Red, 130, 69, 8, 8);
}
}
So when you launch the application a red rectangle appears on the left only, because the button hasn't been pressed yet.
and when the button is pressed, the red rectangle appears on top of the image displayed in pictureBox2.
Nothing dramatic, but it does the job. So depending on the mode of operation you need (user graphics, or image annotations) use the example code to understand how to do it.
Help please elegantly solve a couple of problems in my small project. I'm developing a small program in c # (WinForms), which uses pictureboxes instead of buttons. I added a small feature like an 'animation' to them: when you mouse over them they decrease in size, when the mouse goes away they return to their original state. Actually, I have two problems here:
If the mouse is brought to the very edge, the picturebox will be decreased, but if the mouse is out of the current size of the picturebox, it's size will be returned back. And so on. Such a kind of 'blinking' turns out.
And more importantly, I have tooltips attached to these pictureboxes. And if you move the mouse over the picturebox, it will decrease in size and the tooltip will not be called. It will take to move the cursor a little without going beyond the picturebox so the tooltip will appear. And this behavior is counterintuitive.
Here's the code:
private void IconReduce(PictureBox picturebox)
{
originalLocation = picturebox.Location;
originalSize = picturebox.Size;
picturebox.Location = new Point(picturebox.Location.X + 2, picturebox.Location.Y + 2);
picturebox.Size = new Size(picturebox.Size.Width - 4, picturebox.Size.Height - 4);
}
private void IconNormal(PictureBox picturebox)
{
picturebox.Location = new Point(originalLocation.X, originalLocation.Y);
picturebox.Size = new Size(originalSize.Width, originalSize.Height);
}
private void pb_MouseEnter(object sender, EventArgs e)
{
IconReduce(sender as PictureBox);
}
private void pb_MouseLeave(object sender, EventArgs e)
{
IconNormal(sender as PictureBox);
}
I'm writing a PlotBox control, which inherits from a PictureBox. Every time the control changes its size, I want to redraw the plot. If I do something like that:
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Replot();
}
private void Replot()
{
//logic goes here, for example:
int width = Size.Width;
int height = Size.Height;
}
Will the width and height variables hold the new control size? Are there any additional problems I should have in mind?
The size will already have been updated by the time your Replot() method is called.
I am trying to make a simple graph for my application which shows real-time data for every 100 ms. So I thought I could draw the graph line using the DrawCurve method and started with the following code:
class BufferedPanel : Panel
{
public BufferedPanel()
{
this.DoubleBuffered = true; //to avoid flickering of the panel
}
}
class Form2: Form
{
BufferedPanel panel1 = new BufferedPanel();
List<Point> graphPoints = new List<System.Drawing.Point>();
private void Form2_Load(object sender, EventArgs e)
{
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Point[] points = graphPoints.ToArray();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
if (points.Length > 1)
g.DrawCurve(graphPen, points);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
graphPoints.Add(new System.Drawing.Point(counter * steps, (int)(float)e.UserState)); //x - regular interval decided by a constant; y - simulated by background worker
panel1.Refresh();
counter++;
}
}
As of now, I am simulating the values of graphPoints from a background worker thread. My problem is that, I could not get the graph lines visible when I doublebuffer my panel. It works well when I set doublebuffering to false. I am new to drawing using Graphics. So I am not very sure of how it works. Please help me in this.
Also, I would like to achieve AutoScrolling when the graphlines reach to end of the panel. Could you suggest an idea on how to do it?
This is an image of my working graph:
using (Graphics g = e.Graphics)
That's bad. That destroys the Graphics object that was passed to your Paint event handler. Nothing can be done with that object when your event handler returns, it is a dead parrot. So don't expect anything to work afterwards, including what needs to happen when you turn on double-buffering, the buffer needs to be drawn to the screen to be visible.
There's a simple rule to using the using statement or the Dispose() method correctly. If you create an object then you own it and it is yours to destroy it. Hands off it you didn't create it.
Some evidence that you are also getting it wrong with the "graphPen" variable. Pens are definitely an object that you create and destroy in a Paint event handler. Don't store one, that just needlessly occupies space in the GDI heap, that isn't worth the few microseconds needed to create one. You'd definitely use the using statement for the pen.
So the quick fix is:
var g = e.Graphics;