Slow screen drawing in .Net C# winforms application - c#

I have a very large C# .net 2.0 winforms application which has some drawing issues.
When going into different forms you can see controls being drawn and even the title bar of the form being resized and then disappearing.
The base form that all other forms inherit from has the following code in its constructor. Including setting DoubleBuffering to true.
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.UpdateStyles();
The forms also have a backgroundgradient, but removing this makes no difference to the speed.
All controls and forms inherit from base versions.
What can i add or check to help with the drawing speed?
Code within the OnPaint
if (this.b_UseBackgroundGradient)
{
Graphics graphics = e.Graphics;
Rectangle backgroundRectangle = this.ClientRectangle;
this.SuspendLayout();
if (backgroundRectangle.Width != 0 && backgroundRectangle.Height != 0)
{
using (Brush backgroundBrush = new LinearGradientBrush(backgroundRectangle, base.BackColor, this.BackGradiantColour, LinearGradientMode.ForwardDiagonal))
{
graphics.FillRectangle(backgroundBrush, backgroundRectangle);
}
}
this.ResumeLayout();
}
else
{
base.OnPaint(e);
}

I'd say that your "optimizations" is aimed att fooling the eye to believe that everthing you draw by yourself in OnPaint will appear to draw faster, but apart from that, it will really slow your application down. Especially if you have a lot of forms open, since each one of the form will have created a huge bitmap which is used for double-buffering.
So: in order go gain speed: remove all of your code above and insert it only where absolutely needed.
EDIT: After I saw your OnPaint-code: remove ALL "optimizations" AND move your OnPaint code to OnPaintBackground instead. Make sure that you do not call base.OnPaintBackground. Throw away Susped/Resume layout.
All that you really need to do is to use OnPaintBackground instead of OnPaint and the rest will take care of itself.

Google around for SuspendLayout/ResumeLayout. I would also have a look to see if it is correct to have the stylign information in the constructor or if there is a better method that is called before constructor.

Related

Paint Event e.Graphics.DrawImage doesn't seem to follow double buffering

Currently working on a rewrite of older code where I'm using Graphics functions to draw an interactive vector-based map based off location data.
Back when this was using .NET 4.7, I could simply draw my stuff into a Bitmap, do Graphics gr = Panel.CreateGraphics(), then gr.DrawImage(). I never enabled double buffering, and it always worked fine.
Now I'm in .NET 5, still with WinForms, and trying to do things a bit better based off a Paint event and invalidating the component whenever it's interacted with.
This is the core of my problem here
Bitmap Buffer; //this is initialized elsewhere, don't worry
public void MapPlotPanel_Paint(object sender, PaintEventArgs e)
{
//... Drawing code removed, it's just a novel Graphics object drawing into the Bitmap ...
e.Graphics.DrawImage(Buffer, Point.Empty);
}
While this draws the image into the panel fine, I get terrible flickering, as I can see the bitmap being drawn into the panel in real time. Even if I Panel.CreateGraphics() inside the event handler and use that instead of the PaintEventArgs object, exactly as I did previously, the same thing happens.
Within a Form load event handler, I have
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Which has no effect.
The darnedest thing, though, is if instead of applying the image the panel using Graphics, I do Panel.BackgroundImage = Buffer, it'll never flicker, but then of course, what's drawn only shows the next time the component is invalidated.
I seriously can't think what I might be missing here, any help would be appreciated.
As already mentioned in the comments - to enable Double Buffering of the panel you need to SetStyle() within the panel constructor, not in the form load event. To do so, you have to create your own panel. Bellow the sample code of custom panel class.
public partial class CustomPanel : UserControl
{
public CustomPanel()
{
InitializeComponent();
// Add Double Buffering
SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
}
}

Drawing an image onto a Panel control gives artefacts when resizing

Currently I'm trying to do what I thought would be a simple task:
Draw an image onto the full area of a Panel control in Windows Forms. (Please ignore for the moment that I could use the BackgroundImage property)
The image to draw looks like this:
I.e. a yellow box with an 1 pixel blue frame around.
To draw, I'm using the Paint event of the Panel control:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(Resources.MyImage, panel1.ClientRectangle);
}
This looks fine when initially displaying the form:
When resizing the form (and the docked panel, too), it either cuts the edges when being made smaller...
...or it draws artefacts, when being made larger:
I'm pretty sure that there is going on something rather simple and straight-forward but I really cannot understand the reason.
Since I'm ignoring the ClipRectangle and always draw everything, I thought the image would be scaled all the time.
My questions are:
What is the reason for the artefacts? (I love to understand this!)
What do I have to do in order to get rid of the artefacts? (beside calling Invalidate on each resize)
Update, SOLUTION:
Thanks to Ryan's answer, I was able to find an acceptable solution. Basically I derived a class from Panel, did an override of OnPaintBackground and did not call the base method. Last, I added the following code to the constructor of my derived panel:
base.DoubleBuffered = true;
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
UpdateStyles();
The reason for the artefacts is that the entire surface isn't redrawn when the form is resized; only the necessary parts are. The generally best solution is what you don't want to do, calling Invalidate on each resize. However, if this is in fact your situation, just use a PictureBox instead. If it's not, you might consider overriding OnPaint in your form instead, and using this.SetStyle(ControlStyles.ResizeRedraw, true) to do this automatically.

equivalent CreateGraphics in wpf

So, I've used winForms .CreateGraphics to draw a variety of different things, from lines to boxes to images. It was very snappy and responsive.
I am trying to learn WPF in C#
I found that WPF allows me to "add" rectangle objects to a canvas which will display them properly. HOWEVER, I am drawing hundreds of thousands of rectangles at times, and the draw rate can become exceedingly slow, and the UI becomes less snappy when I move even 1 of the rectangles.
Painting directly onto an element in winForms was not very fast, but it was consistent regardless of how much I painted.
Is there a similar solution to doing this in WPF?
I tried adding a linq to System.Drawing, which gave me a Graphics object, but none of the wpf elements i tried have the .CreateGraphics() method.
WPF uses a different model for graphics manipulation than WinForms.
With WinForms, you are able to directly edit the pixels on the screen. The concept of your rectangle is lost after the pixels are drawn. Drawing pixels is a very fast operation.
With WPF, you are not controlling the pixels on the screen. DirectDraw is. DirectDraw is a vector-based compositing engine. You do not draw pixels. You define vector shapes (or visuals). The concept of a shape, or a rectangle, is RETAINED, even after the image is rendered to the screen. When you add a new rectangle which overlaps the others, ALL OTHER RECTANGLES NEED TO BE REDRAWN. This is likely where your performance is slowing down. This does not happen when using WinForms.
You can improve the performance of WPF a bit by overriding OnRender. You can cut out the overhead of the Rectangle object and directly provide the visuals. However, you are still not drawing pixels to the screen. You are defining shapes that DirectDraw uses to render the image. In this regard, the OnRender name may be a bit misleading.
I am sure you can find plenty of tricks to improve performance of your application in WPF. There are ways to still paint pixels - but that is kinda defeating the point of WPF.
What are you doing that requires thousands of rectangles?
You would need to create a control that overrides OnRender and do your drawing in there. There isn't a way for you to draw onto another control, but a control can draw itself.
Also, keep in mind that WPF uses retained graphics, so if you change something you need to invalidate the visual as needed.
EDIT:
Something like:
public class MyControl : Control {
public MyControl() {
this.Rects = new ObservableCollection<Rect>();
// TODO: attach to CollectionChanged to know when to invalidate visual
}
public ObservableCollection<Rect> Rects { get; private set; }
protected override void OnRender(DrawingContext dc) {
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Colors.LimeGreen;
Pen myPen = new Pen(Brushes.Blue, 10);
foreach (Rect rect in this.Rects)
dc.DrawRectangle(mySolidColorBrush, myPen, rect);
}
}
As was said, WPF uses a retained graphics methodology so your actually creating 100,000 Rectangle objects in memory and then drawing all of them. The slowdowns are probably due to garbage collection and general memory issues.
Aside from override the OnRender method, here's a couple of things you could look into though.
Drawing the rectangles to an image in a background thread using the GDI methods your familiar and then write the result to a WPF WriteableBitmap
Use the D3DImage and take advantage of hardware acceleration. This requires you to know the DirectX (or Direct2D) libraries. If your interested in this approach, I'd suggest looking into SlimDx.
The problem is most likeley not that WPF can't render 1000s of graphic objects, but that your creating and adding items too far up the WPF object hierachy. It does after all use the GPU for all the graphical grunt work.
You should add objects as close to the "Visual" class as possible, as soon as you start adding objects based on the latter "UIElement" you are asking WPF to track user clicks, hovers and so on for each object, not just draw it.

Gradient Drawing Bug

I'm having trouble with a gradient drawing call. I have a Form that looks like this.
Screenshot http://img413.imageshack.us/img413/3570/30364682.png
The problem is every now and again the above gradient drawing bug will start happening. It should go right across of course. Sometimes it only takes some build-rebuild-mashing to fix and it'll simply just "start" after a build every now and again.
That control (the top white part) is a TableLayoutPanel. The BackColor is set to white and on the panel's Paint event I do this:
/// <summary>
/// Draws the background gradient.
/// </summary>
private void titleBarLayoutPanel_Paint(object sender, PaintEventArgs e)
{
Brush brush = new LinearGradientBrush(titleBarLayoutPanel.Bounds, TaskHeaderLeftColor, TaskHeaderRightColor, LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(brush, titleBarLayoutPanel.Bounds);
}
Should I be doing something else? The problem is that it works, and then without so much as a rebuild or build this will start happening!
EDIT I have since rebuilt the class library it is contained in (it's a generic Form) then rebuilt the app it's used in and the gradient is now filling across completely. This is bizarre.
Building and re-building your application, with no changes, normally doesn't solve this (or most any other bug for that matter) save the ones in which you run your application without doing a clean/rebuild first and then notice that the code you just wrote doesn't run (not sure that's possible these days with the IDEs). I see this a lot with newer devs when they keep rebuilding hoping that somehow the compiler will make the code "correct" or that maybe the compiler is simply not generating the correct code to begin with. (Please note that I do not mean the aforementioned statements to be taken disparagingly.)
To solve the issue at hand, you might try deriving your own TableLayoutPanel class in which you override the OnBackgroundPaint event, painting your own background, or simply returning if you don't want to paint your own background. (You seem to be painting the background in the Paint event). What you are doing in the code above is simply painting over the background already painted by the control, hence the "bug" you see, (double paint). It appears that the form is not resizable. Try making it resizable. Then resize it and watch it paint, or simply move other windows over it.
class CustomTableLayoutPanel : TableLayoutPanel
{
protected override void OnPaintBackground(PaintEventArgs e)
{
Brush brush = new LinearGradientBrush(this.ClientRectangle, TaskHeaderLeftColor, TaskHeaderRightColor, LinearGradientMode.Horizontal);
e.Graphics.FillRectangle(brush, this.ClientRectangle);
//base.OnPaintBackground(e);
}
}
By the way, you should replace Bounds with ClientRectangle.
Bounds is the control's rectangle relative to its parent; ClientRectangle is relative to the control itself.
In this particular case, it won't make a difference, since the control is at 0, 0.

Resizing window causes black strips

I have a form, which sets these styles in constructor:
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
And I draw some rectangles in Paint event. There are no controls on the form. Hovewer, when I resize the form, there are black strips at right and bottom of the form. Is there any way to get rid of them? I've tried everything, listening for WM_ERASEBKGND in WndProc, manually drawing the form on WM_PAINT, implementing custom double buffer, etc. Is there anything else I could try?
I've found this:
https://connect.microsoft.com/VisualStudio/feedback/details/522441/custom-resizing-of-system-windows-window-flickers
and it looks like it is a bug in DWM, but I just hope I can do some workaround.
Please note that I must use double buffering, since I want to draw pretty intense graphic presentation in the Paint event. I develop in C# .NET 2.0, Win7.
Status Update 1
I've managed to get rid of most of the black stripes by implementing the resize functionality by myself. Hovewer there are still some minor glitches. Is there any way to do resize and paint operation at once? Here is a pseudo-code of what I need to do:
IntPtr hDC;
var size = new Size(250, 200);
IntPtr handle = API.PaintAndResizeBegin(this.Handle /* Form.Handle */,
size.Width, size.Height, out hDC);
using (var g = Graphics.FromHdc(hDC)) {
this.backBuffer.Render(g, size);
}
API.PaintAndResizeCommit(handle);
Is there any way to implement the above code?
The second solution could be to back-buffer whole form, including non-client area. But how to do that? I don't want to paint the non-client area by myself, as I want to keep the nice aero effect on Vista/7. Any help will be deeply appreciated.
Status Update 2
It looks like this problem is unsolvable, since it is omnipresent on Windows, in every application. We can just hope that MS will take some inspiration in Mac OS X and will provide appropriate APIs in new Windows.
I've found the function which can paint and resize window at the same time - UpdateLayeredWindow.
So now it should be possible to create resizable windows, which do not have any strips while being resized. However, you need to paint the window content yourself, so it is a little inconvenient. But I think that using WPF and UpdateLayeredWindow, there shouldn't be any problem.
Update
Found problems. :-) When using UpdateLayeredWindow, you must paint the window's border yourself. So, if you want standard window painted using UpdateLayeredWindow with nice glass effect in win7, you are screwed.
On Microsft Connect is even a thread about this problem, where Microsoft says it is a bug by design, and if it ever gets fixed, then probably in Win8 or some newer system. So there isn't much we could do about this.
I found that it is best not to do any custom rendering directly on the Form surface. Instead, put a docked PictureBox on the form, create Bitmap object that will be displayed in the PictureBox, draw everything onto that using the System.Drawing.Graphics.FromImage(Image) method.
I used that method with a game loop to make a simple shooter game (Crimsonland-style) and got pretty good performance (with anti-aliased lines), above 100 FPS.

Categories