I have written a custom control that renders some graphics. The graphics itself is fairly expensive to render but once rendered it rarely changes. The problem I have is that if you move the mouse really fast over the graphics surface it keeps calling the control's overridden Paint method which then incurs a high CPU penalty:
private void UserControl1_Paint(object sender, PaintEventArgs e)
What techniques could be used to avoid this situation or minimise any unnecessary redrawing since the graphics/image underneath the mouse pointer is not actually changing?
EDIT: After seeing your edit, I can assure you that OnPaint is not called by default when the mouse moves over a control. Something in your code is definitely causing the re-paint, you just don't see it yet. Perhaps posting some of your code would help us find the problem.
Are you invalidating your control on MouseMove? That is likely a bad idea, and if you really needed to do it (i.e., you are making a graphics editor or something), you would have to be smart about how large a region was actually re-drawn. So, solution; don't paint your control in MouseMove.
Otherwise, I would not expect OnPaint to fire when the mouse moves over the control. You can also just generate the image once and then blt it to the Graphics object until it needs to be re-generated.
You can use a buffer image on which to draw when something is changed and in the Paint method just copy the image on the screen. Should be pretty fast. Also you can use the clip region to copy only the part that needs updating. This should reduce CPU usage.
You can also use an IsDirty flag to know when to update the buffer image (i.e. fully redraw it) if needed.
You should not call Paint directly.
Instead, call Invalidate (Control.Invalidate). This queues the need to be repainted, and Windows will take care of the call itself. This way, a lot of rapid invalidations (repaint requests) can be serviced by one call to Paint.
One of the things that can cause high CPU load in Paint method is improper (unmanaged) resource release. Make sure you do Dispose() all of the pens, brushes and so forth (probably all of the System.Drawing classes instances has some unmanaged resource tied to them).
Basically you should Dispose() of the object as soon as you have finished working with it. Do not cache or anything - GDI+ resources are system resources and should be returned to system as soon as possible. Acquiring them (creating new Brush class instance for example) should be pretty quick, but I do not have anything to back this statement up at the moment.
Also check the Clip Rectange in the PaintArgs event argument so that you only repaint the area that needs to get updated instead of re-drawing the entire image.
You can use this code to supress and resume redrawing of a control :]
Good luck.
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace pl.emag.audiopc.gui {
// ************************************************************************
//`enter code here`
// ************************************************************************
public class PaintingHelper {
// ********************************************************************
//
// ********************************************************************
public static void SuspendDrawing(Control parent) {
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
// ********************************************************************
//
// ********************************************************************
public static void ResumeDrawing(Control parent) {
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
// ********************************************************************
//
// ********************************************************************
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, Int32 wMsg,
bool wParam, Int32 lParam);
// ********************************************************************
//
// ********************************************************************
private const int WM_SETREDRAW = 11;
}
}
Related
I was just wondering if anyone could shed some light onto this for me. I've been coding c# for years but never even touched anything in the System.Drawing namespace except for the bitmap class and I've been following some tutorials and came up with some code that works. I'm developing a 2D Game Engine and the code below is for the graphics engine, which uses GDI. However, I just don't understand how this code is even working. Here it is:
private Graphics frontBuffer;
private Graphics backBuffer;
private Bitmap backBufferBitmap;
public void Initialize()
{
backBufferBitmap = new Bitmap(game.Form.Width, game.Form.Height);
frontBuffer = game.Form.CreateGraphics();
backBuffer = Graphics.FromImage(backBufferBitmap);
}
public void Update()
{
try
{
frontBuffer.DrawImageUnscaled(backBufferBitmap,0,0);
backBuffer.Clear(Color.Black);
}
catch(Exception e)
{
throw e;
}
}
So, the main part that's confusing to me is this;
How is the back buffer bitmap getting updated? and why is the back buffer being cleared and not the front buffer?
Also, the initialize method is called once and the update method is called once per frame in a while loop.
After you initialize the backBuffer graphics objects from the bitmap, every time you say, for example backBuffer.DrawLine(...) GDI+ will do the pixel manipulations directly on this Bitmap. They are linked in a way. Think of backBufferBitmap as the canvas, and of backBuffer as the brush.
The frontBuffer is initialized from the form instead. So the form is it's canvas and whatever you do with frontBuffer is drawn to the form - in this case here it draws the backBufferBitmap.
It's basically a double-buffering scheme, that has a lot of advantages over directly drawing your lines and circles to the form, e.g. less flickering. Whenever you draw something to a form, remember that it is removed very often (e.g. when you move the form outside of the screen area). You would need to refresh it using the form's Paint event.
After Update() is called, you would need to redraw your scene to backBuffer, before you call Update again, because the bitmap is blacked out by your Clear() after it is drawn to the screen.
frontBuffer is getting updated because each time you are calling frontBuffer.DrawImageUnscaled(backBufferBitmap,0,0); in update()
backBuffer is gettting cleared because you are calling backBuffer.Clear(Color.Black);
Also, initialize() is supposed to be called only once. At the time of object creation. And I believe it is part of a larger program where parent is calling update() of child.
I have the following code to drag the form and make it transparent when its getting dragged. The problem is that it flickers and isn't dragging smooth. I have a picture on the form, not sure if that's what's causing this. How can I make it not flicker. If I remove the opacity then it's getting dragged fast/smooth.
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam,
int lParam);
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
public void Drag(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
this.Opacity = 0.9;
ReleaseCapture();
SendMessage(Handle, 0xA1, 0x2, 0);
this.Opacity = 1;
}
}
private void Body_MouseDown(object sender, MouseEventArgs e)
{
Drag(e);
}
There are a number of properties of the Form and Control class that are "heavy", requiring a great deal of change in the underlying native Windows window. They are the properties that are associated with the style flags that are passed to the native CreateWindowEx() call. The Opacity property, along with the TransparencyKey property are like that, when you change them from the default then the window needs another style flag, WS_EX_LAYERED.
That's a problem, given that this style flag is specified when you create the window. Windows has some support for changing them after the window is created with SetWindowsLongPtr() but that's always been spotty. Particularly so for WS_EX_LAYERED, a lot of stuff happens under the hood when that's turned on. It is implemented by taking advantage of a hardware feature in the video adapter called "layers". A layer is a separate chunk of video memory whose pixels are combined with the main memory. The mixer that supports that provides the opacity effect (multiply) and the transparency key effect (omit).
So changing the Opacity property on the fly, after the window is created is quite difficult. So much so that WPF completely forbids it. But Winforms doesn't, it needed to deal with the limitations of Windows 98. Which also made it difficult to change properties like ShowInTaskbar, RightToLeft, FormBorderStyle. It uses a trick to permit changing these properties, it completely destroys the native window and recreates it, now using the new style flags.
Problem solved, but this does have noticeable side effects. Inevitably, the window you look at disappears and the new window gets created and painted in the same spot. That causes the flicker you complained about. Also, destroying the window causes a lot of internal state to be lost. Winforms does its best to restore that state as well as it can for the new window, but the "I'm currently being moved" state cannot be restored. The modal move loop already terminated.
The workaround for this problem is crude but simple. Set the Opacity property in the Properties window to 99%. And change your code to restore it to 99 instead of 100. Now the style bit never has to be changed so you won't get these artifacts anymore.
I've implemented a simple multithreaded Server\Client game as an assignment for my college.
on the client side in addition to the main thread there are:
1-thread which is responsible of drawing the play ground and the players on the form.
2-thread to communicate with the server to send the directions and receive the location and other information.
first I used the invoke technique but it didn't work because I was using the Graphics after it disposed. see Draw on a form by a separate thread
so In order to avoid that and regularize the drawing thread, I just raising the flag 'Invalidate' every specific time on the drawing thread and leave the actual handling of it to the main thread:
public Form1()
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
InitializeComponent();
}
private void Draw()
{
while (true)
{
Thread.Sleep(200);
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (map.hasGraph)
{
map.Draw(e.Graphics);
if (this.Index != null)
e.Graphics.FillRectangle(Brush, Rectangle);
if (OpponentIndex != null)
e.Graphics.FillRectangle(OpponentBrush, OpponentRectangle);
}
}
the problem here that the form is blinking in arbitrary fashion even though I'm using double buffering, and the blinking is reduced when I increase the sleeping time for the drawing thread, but I think 200ms is already too much.
any advice?
[Edit]
I realized that I'm setting the double buffering flag from the code and from the property editor which made the problem (this may be a fool idea) but I spent half an hour testing my code with one of the flags and with both of them, the problem raised when I set the double buffering flag from two places, my problem is solved but please now I need to know if this could be what solved it.
It must get worse and worse the longer it runs right?
Everytime your program paints it launches draw, which has an infinite loop, which calls paint, which calls draw in another infinite loop. IT seems you have a circular reference here. If I can assume Map.Draw is private void Draw()
There is a far easier solution to this, draw everything to a bitmap then draw the bitpmap in the onPaint event.
Bitmap buffer=new Bitmap(this.Width, this.Height); //make sure to resize the bitmap during the Form.Onresize event
Form1()
{
InitializeComponent();
Timer timer=new Timer();
timer.Interval= 100;
timer.Tick+=......
timer.Start()
}
//the Timer tick event
private void timer_tick(....
{
if (map.hasGraph)
{
using (Graphics g = Graphics.FromImage(buffer))
{
//You might need to clear the Bitmap first, and apply a backfround color or image
//change color to whatever you want, or don't use this line at all, I don't know
g.Clear(Color.AliceBlue);
if (this.Index != null)
g.FillRectangle(Brush, Rectangle);
if (OpponentIndex != null)
g.FillRectangle(OpponentBrush, OpponentRectangle);
}
panel1.BackgroundImage=buffer;
}
}
Note I did not test this for syntax accuracy.
The memory of the system might be quite low for the operation executed.
read more about it.
http://msdn.microsoft.com/en-us/library/system.drawing.graphics.aspx
Create an image which will be used to store last rendered scene.
Create new thread whith will draw to the image
Create a timer whitch will refresh image
Copy image to form on timer tick
I have an application where the user draws some shapes.
When I click over a shape and I drag it, the CPU goes 100% because of Invalidate() inside MouseMove.
If I a use a timer and call Invalidate() from tick event the moving is not so smooth.
Is there any other approach to minimize CPU and have smooth moving?
` Point startDragMousePoint;
Point startShapeLocation;
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if(isMouseDown)
{
Point deltaPoint = Point.Subtract(e.Location, new Size(startDragMousePoint));
shape.Location = Point.Add(startShapeLocation, new Size(deltaPoint));
Invalidate();
}
}
private void Canvas_Paint(object sender, PaintEventArgs e)
{
shape.Render(e.Graphics);
}`
There are three general solutions.
1) Don't draw while your moving, this was the solution in windows for a long time, when you dragged a window, it just disapeard and you saw the outline of a window.
2) Create a bitmap object and only move that. Note you will have to redraw the area under it.
3) Don't invalidate the hole window, just the area you are changing. Drawing to a buffer (a bitmap) can help you reuse areas.
Also, if GDI isn't the fastest drawing functions in the world. If your shape is very complex, you might want to consider using OpenGL, DirectX or SDL.
Instead of invalidating the entire area you could invalidate the portion of the control that has changed by using:
Rectangle changedArea = new Rectangle(cX, cY, cW, cH);
this.Invalidate(changedArea);
Also make sure your control is set to use DoubleBuffering
this.DoubleBuffered = true;
From the limited code that you have put up, I think the invalidate will not cause any problem. Most probably the problem may be inside the real rendering code of yours shape.Render(). In the past I have written similar application where I have called Invalidate in the mouse move and the applications has worked fine. Only there were some flickering which is gone on enabling double buffering.
In C# WinForms - I am drawing a line chart in real-time that is based on data received via serial port every 500 ms.
The e.Graphics.DrawLine logic is within the form's OnPaint handler.
Once I receive the data from the serial port, I need to call something that causes the form to redraw so that the OnPaint handler is invoked. I have tried this.Refresh and this.Invalidate, and what happens is that I lose whatever had been drawn previously on the form.
Is there another way to achieve this without losing what has been drawn on your form?
The point is that you should think about storing your drawing data somewhere. As already said, a buffer bitmap is a solution. However, if you have not too much to draw, sometimes it is easier and better to store your drawing data in a variable or an array and redraw everything in the OnPaint event.
Suppose you receive some point data that should be added to the chart. Firs of all you create a point List:
List<Point> points = new List<Point>();
Then each time you get a new point you add it to the list and refresh the form:
points.Add(newPoint);
this.Refresh();
In the OnPaint event put the following code:
private void Form_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLines(Pens.Red, points);
}
This works quite fast up to somehow 100 000 points and uses much less memory than the buffer bitmap solution. But you should decide which way to use according to the drawing complexity.
As rerun said, you need to buffer your form (since it appears that you are discarding the data after you draw it).
This is basically how I would do it:
private Bitmap buffer;
// When drawing the data:
if (this.buffer == null)
{
this.buffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
}
// then draw on buffer
// then refresh the form
this.Refresh();
protected override void OnPaint(PaintEventArgs e)
{
if (this.buffer != null)
{
e.Graphics.DrawImage(this.buffer);
}
}
That said, you probably want to cache your data so you can change the size of the buffer when the form size changes and then redraw the old data on it.
The solution may be this.Invalidate()
the default way to handle this is to create a memory bitmap and draw on that then set the image property of the picture box to the memory bitmap.
You will need to store historical data somewhere and just repaint it.
That will be much easier than say caching and clipping bitmaps.