I'm working on a project where control updates and new image is drawn on a panel after every 10 seconds. Following code clears that panel first. Then draws a border to it.
private void DrawRectangle(Color color)
{
using (var graphics = CreateGraphics())
using (var pen = new Pen(color))
{
graphics.Clear(Color.Black); //External exception is thrown here.
graphics.DrawRectangle(pen, 0, 0, Size.Width - 1, Size.Height - 1);
}
}
Normally everything works fine but if I lock windows(press Win + L) then after 10 seconds when graphics.Clear(Color.Black) statement is executed, application crashes.
According to MSDN page: The Clear method clears the state of the graphics object and should not be called when the graphics object cannot be updated. For example, if the Clear method is called on a secure desktop in a terminal server session, an ExternalException may occur, leaving the Graphics object in an inconsistent state.
What should I do to prevent this crash? Should I check if windows is locked or not? and will that be the only case where this crash will occur?
Update: Same problem occurs when Screen saver is activated.
If you call CreateGraphics() when windows is locked or screensaver is shown, the graphics object it returns is in Inconsistent state and can not be used to paint. If we use such graphics object, it throws ExternalException.
Best way to get rid of this problem is suggested by user Hans Passant. Instead of using CreateGraphics() method to create graphics object, use panel's paint event. Paint event handler contains eventargs which contains graphics object. This graphics object should be used to do painting.
The reason this works because, panel's paint event does not get called when windows is locked or screensaver is shown. So no inconsistent graphics object is used for painting.
Related
I need to draw a circle with the size of the entire form. I have made a Form1 with the following Paint event handler:
private void Form1_Paint(object sender, PaintEventArgs e)
{
SolidBrush myBrush = new SolidBrush(Color.Black);
e.Graphics.FillEllipse(myBrush, 0, 0, this.ClientSize.Width, this.ClientSize.Height);
}
The problem is that when you change the size of the form or maximize it the drawing becomes corrupted. Please help me how to avoid this problem. Sorry I haven't found an appropriate topic on StackOverflow.
Painting for container controls like Form was optimized, they only redraw the part of the window that was revealed by the resize. In other words, if the user drags the right or bottom edge then there's no repaint at all when the window is made smaller. A small repaint if it is made bigger, only the part of the window to became visible.
This is normally very appropriate since they don't have much reason to completely repaint themselves, they only draw their background. This matters in particular when the user resizes the window by dragging a corner, that can generate a storm of repaint requests. And if repainting is slow then that gets to be very visible, moving the mouse makes the motion "stutter", flicker can be noticeable as well.
But you care, you need to completely redraw the ellipse since you use the ClientSize property in your paint event handler.
Whether or not this optimization is turned on is determined by a style flag for the control, as Sriram pointed out. Turning this off is so common that Winforms exposed the style flag through a simple property to make it easier to change it:
public Form1() {
InitializeComponent();
this.ResizeRedraw = true;
this.DoubleBuffered = true;
}
Note the DoubleBuffered property, another example of a convenience property that actually changed ControlStyles. You want this turned on to suppress the flicker your window exhibits. Caused by first drawing the background, which erases your ellipse, then repainting the ellipse.
There's another pretty serious problem in your code, SolidBrush is a disposable class. Disposing objects is optional, but skipping this for System.Drawing objects is quite unwise. If you resize your window frequently enough, 10000 times, then your program will crash. That sounds like a lot, but it is not when you set ResizeRedraw to true since that generates a lot of repaints. Only the garbage collector can keep you out of trouble, it won't in a simple program like this. Fix:
protected override void OnPaint(PaintEventArgs e) {
using (var brush = new SolidBrush(this.ForeColor)) {
e.Graphics.FillEllipse(brush, 0, 0, this.ClientSize.Width, this.ClientSize.Height);
}
base.OnPaint(e);
}
Btw, do not optimize this by keeping the brush around as recommended in another post. Creating a brush is very cheap, takes about a microsecond. But is far too expensive to keep around, it is allocated on a heap that's shared by all programs that run on the desktop. A limited resource, that heap is small and programs start failing badly when the that heap is out of storage.
You'll have to enable ResizeRedraw. Add the following to your constructor.
this.SetStyle(ControlStyles.ResizeRedraw, true);
On Forms Resize event paste this ;
this.Refresh();
this will redraw the form and fire the Form1.Paint event.
That occurs because only part of the form's area gets invalidated when the size changes. What you need to do is handle the SizeChanged event and call the form's Refresh method.
Call Graphics.Clear(); before drawing to clear the old graphics and avoid creating redundant brushes.Put the code for the Brush creation outside the block
SolidBrush myBrush = new SolidBrush(Color.Black);
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear();
e.Graphics.FillEllipse(myBrush, 0, 0, this.ClientSize.Width, this.ClientSize.Height);
}
When the form is loading shouldn't the CreateGraphics() return a graphics object?
I mean, on the Form1_Load event can I write for example the following?
Graphics x;
private void Form1_Load(object sender, EventArgs e)
{
x = this.CreateGraphics();
}
If not, then WHY?
I thought that when you create a new form, the constructor initiates the all object of the form. So why not also the graphics object?
I'm asking that, because when I'm trying to draw - on form_load, it doesn't show me what I draw.
The main reason is: I want to create a game, which have a board - so when the user click on the new game - first - I'm initiating the board and drawing it.
And in the onPaint event I just want to draw the current state of the board. So I thought the the initial state of the board should be draw on the formLoad event.
You should not use the Graphics object in this manner; you should enclose each usage of it in a using block, or otherwise make sure you dispose of it after each set of drawing operations. Your code here will leave an unnecessary Graphics object hanging around.
Brief example:
private void MyonPaintOverload()
{
using(Graphics x = this.CreateGraphics())
{
// draw here...
}
}
Also, drawing on Form_Load() won't work, because the window is not actually visible at that point; there's nothing to draw on, basically.
Yes, you generally need to redraw the whole thing each cycle - because something as simple as another window being dragged across your window will 'wipe out' your drawing, and when it's invalidated by the other window being moved away, you need to redraw everything that you manually drew.
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.
I have a User Control with completely custom drawn graphics of many objects which draw themselves (called from OnPaint), with the background being a large bitmap. I have zoom and pan functionality built in, and all the coordinates for the objects which are drawn on the canvas are in bitmap coordinates.
Therefore if my user control is 1000 pixels wide, the bitmap is 1500 pixels wide, and I am zoomed at 200% zoom, then at any given time I would only be looking at 1/3 of the bitmap's width. And an object which has a rectangle starting at point 100,100 on the bitmap, would appear at point 200,200 on the screen provided you were scrolled to the far left.
Basically what I need to do is create an efficient way of redrawing only what needs to be redrawn. For example, if I move an object, I can add the old clip rectangle of that object to a region, and union the new clip rectangle of that object to that same region, then call Invalidate(region) to redraw those two areas.
However doing it this way means I have to constantly convert the objects bitmap coordinates into screen coordinates before supplying them to Invalidate. I have to always assume that the ClipRectangle in PaintEventArgs is in screen coordinates for when other windows invalidate mine.
Is there a way that I can make use of the Region.Transform and Region.Translate capabilities so that I do not need to convert from bitmap to screen coordinates? In a way that it won't interfere with receiving PaintEventArgs in screen coordinates? Should I be using multiple regions or is there a better way to do all this?
Sample code for what I'm doing now:
invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));
SelectedItem.UpdateEndPoint(endPoint);
invalidateRegion.Union(BitmapToScreenRect(SelectedItem.ClipRectangle));
this.Invalidate(invalidateRegion);
And in the OnPaint()...
protected override void OnPaint(PaintEventArgs e)
{
invalidateRegion.Union(e.ClipRectangle);
e.Graphics.SetClip(invalidateRegion, CombineMode.Union);
e.Graphics.Clear(SystemColors.AppWorkspace);
e.Graphics.TranslateTransform(AutoScrollPosition.X + CanvasBounds.X, AutoScrollPosition.Y + CanvasBounds.Y);
DrawCanvas(e.Graphics, _ratio);
e.Graphics.ResetTransform();
e.Graphics.ResetClip();
invalidateRegion.MakeEmpty();
}
Since a lot of people are viewing this question I will go ahead and answer it to the best of my current knowledge.
The Graphics class supplied with PaintEventArgs is always hard-clipped by the invalidation request. This is usually done by the operating system, but it can be done by your code.
You can't reset this clip or escape from these clip bounds, but you shouldn't need to. When painting, you generally shouldn't care about how it's being clipped unless you desperately need to maximize performance.
The graphics class uses a stack of containers to apply clipping and transformations. You can extend this stack yourself by using Graphics.BeginContainer and Graphics.EndContainer. Each time you begin a container, any changes you make to the Transform or the Clip are temporary and they are applied after any previous Transform or Clip which was configured before the BeginContainer. So essentially, when you get an OnPaint event it has already been clipped and you are in a new container so you can't see the clip (your Clip region or ClipRect will show as being infinite) and you can't break out of those clip bounds.
When the state of your visual objects change (for example, on mouse or keyboard events or reacting to data changes), it's normally fine to simply call Invalidate() which will repaint the entire control. Windows will call OnPaint during moments of low CPU usage. Each call to Invalidate() usually will not always correspond to an OnPaint event. Invalidate could be called multiple times before the next paint. So if 10 properties in your data model change all at once, you can safely call Invalidate 10 times on each property change and you'll likely only trigger a single OnPaint event.
I've noticed you should be careful with using Update() and Refresh(). These force a synchronous OnPaint immediately. They're useful for drawing during a single threaded operation (updating a progress bar perhaps), but using them at the wrong times could lead to excessive and unnecessary painting.
If you want to use clip rectangles to improve performance while repainting a scene, you need not keep track of an aggregated clip area yourself. Windows will do this for you. Just invalidate a rectangle or a region that requires invalidation and paint as normal. For example, if an object that you are painting is moved, each time you want to invalidate it's old bounds and it's new bounds, so that you repaint the background where it originally was in addition to painting it in its new location. You must also take into account pen stroke sizes, etc.
And as Hans Passant mentioned, always use 32bppPArgb as the bitmap format for high resolution images. Here's a code snippet on how to load an image as "high performance":
public static Bitmap GetHighPerformanceBitmap(Image original)
{
Bitmap bitmap;
bitmap = new Bitmap(original.Width, original.Height, PixelFormat.Format32bppPArgb);
bitmap.SetResolution(original.HorizontalResolution, original.VerticalResolution);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(original, new Rectangle(new Point(0, 0), bitmap.Size), new Rectangle(new Point(0, 0), bitmap.Size), GraphicsUnit.Pixel);
}
return bitmap;
}
I know how to work with object of type Graphics (at least I am able to render images) but I always do that by passing graphics object retrieved from OnPaint method.
I would like to display an image when the app is opened (ie in Form_Load method) but have no clue how to obtain the instance of Graphics object I could use?
Thanks
Using the e.Graphics object that OnPaint() supplies to you is the correct way of doing it. It will run right after the OnLoad() method. The form isn't visible yet in OnLoad.
Getting a Graphics object from Control.CreateGraphics() is supported. However, whatever you draw with this will be wiped out as soon as the form repaints itself. Which happens when the user moves another window across yours (pre-Aero) or when she minimizes and restores or otherwise resizes the window. Use CreateGraphics only ever when animating at a high rate.
If you're attempting to create a graphics object from the surface of your form, you can use this.CreateGraphics
If you are attempting to create a new Image, you can always initialize an Image and then call Graphics.CreateGraphics.FromImage(YourImage) e.g.
Bitmap b = new Bitmap(100,100);
var g = Graphics.CreateGraphics.FromImage(b);
At this point, any drawing performed to your Graphics object will be drawn onto your image.
None of the preceding answers worked for me. I found Rajnikant Rajwadi solution effective (see https://social.msdn.microsoft.com/Forums/vstudio/en-US/ce90eb80-3faf-4266-b6e3-0082191793f7/creation-of-graphics-object-in-wpf-user-control?forum=wpf)
Here is a horribly condensed call to Graphics.MeasureString(). (please code more responsibly)
SizeF sf = System.Drawing.Graphics.FromHwnd(new System.Windows.Interop.WindowInteropHelper(this).Handle).MeasureString("w", new Font(TheControl.FontFamily.ToString(), (float)TheControl.FontSize));
And how do you plan to use the Graphics object you got in the Load event?
If you want to paint something on the screen, you have to be in the Paint event, or it will be cleared on the next paint.
What you can do: load another (simple) form, with just a picture, and hide it when your main form is loaded.
Since your Load event will probably run on the UI thread. Call DoEvents to make the other form appear.
form.CreateGraphics();
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.creategraphics.aspx
http://msdn.microsoft.com/en-us/library/5y289054.aspx