NET User Interfaces in Csharp Windows Forms and Custom Controls [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

NET User Interfaces in Csharp Windows Forms and Custom Controls [Electronic resources] - نسخه متنی

Matthew MacDonald

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید



























Optimizing GDI+ Painting


Painting is a performance-sensitive area for any application. Slow rendering may not stop your application from performing its work, but screen flicker and slow painting can make it seem unprofessional. This section considers some techniques that optimize drawing with GDI+ surfaces.


Painting and Resizing


One often overlooked fact about automatic repainting is that it only affects the portion of the window that is obscured. This is particularly important with window resizing. For example, consider the slightly modified Paint code that follows, which paints an ellipse that is the same size as the containing window. The result is pictured in Figure 12-3.


private void form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Pen drawingPen = new Pen(Color.Red, 15);
e.Graphics.DrawEllipse(DrawingPen, New Rectangle(new Point(0, 0),
this.ClientSize));
}


Figure 12-3: Filling a form with an ellipse

When you resize this window, you'll discover that the painting code isn't working correctly. The newly exposed portions of the window are filled with the resized ellipse, but the rest of the window is not updated, leading to the mess shown in Figure 12-4.


Figure 12-4: Flawed resizing

The problem is that Windows assumes that it only needs to repaint the portion of the window that has been hidden or restored. In this case, the entire content of the window depends on its dimensions, so the assumption is incorrect.

Fortunately, you can solve this problem by manually invalidating the code whenever the form is resized (by handling the resize event, as shown below, or overriding the OnResize() method).


private void form1_Resize(object sender, System.EventArgs e)
{
this.Invalidate();
}

With the addition of this code, the entire form is repainted and the ellipse grows or shrinks to fit the window bounds perfectly. Another option would be to set the Form.ResizeRedraw property to true.



Painting Portions of a Window


In some cases, it just doesn't make sense to repaint the entire window when you only need to update a portion of the display. One example is a drawing program.

Consider a simple example program that allows the user to draw squares. When the user clicks with the mouse, a square is created, but not directly drawn. Instead, a rectangle object is added to a special ArrayList collection so it can be tracked, and the form is invalidated.


// Store the squares that are painted on the form.
ArrayList squares = new ArrayList();
// This code reacts to the Form.MouseDown event.
private void DrawSquare_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{
Rectangle square = new Rectangle(e.X, e.Y, 20, 20);
squares.Add(square);
this.Invalidate();
}

The painting logic then takes over, iterating through the collection, and drawing each rectangle. The number of squares that are currently being displayed is also written to a status bar.


private void DrawSquare_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Pen drawingPen = new Pen(Color.Red, 10);
foreach (Rectangle square in squares)
{
e.Graphics.DrawRectangle(drawingPen, square);
}
pnlSquares.Text = " " + squares.Count.ToString() + " squares";
}

The result of a paint operation is shown in Figure 12-5.


Figure 12-5: A square painting program

The problem with this code is that every time a rectangle is created, the entire form is redrawn. This causes noticeable screen flicker as the number of squares advances beyond 100. You can try this out yourself using the GDI+ Basics project included with the code for this chapter.

There are two ways that you can remedy this problem. The fastest solution is to draw the square in two places: in the Paint logic and the MouseDown event handling code. With this approach, the MouseDown event handler does not need to invalidate the form. It draws the square directly, and stores enough information about the new rectangle for it to be successfully repainted if the window is minimized and restored. The potential drawback is that the code becomes significantly more tangled. If you are drawing a more complex object, you might be able to separate the drawing logic into a separate subroutine that accepts a Graphics object and the item to draw, as shown in the following code snippet.


// Paint a square in response to a mouse click.
private void DrawSquare_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{
Rectangle square = new Rectangle(e.X, e.Y, 20, 20);
squares.Add(square);
Graphics g = this.CreateGraphics();
DrawRectangle(square, g);
g.Dispose();
}
// Paint all the squares when the form needs to be refreshed
// in response to the Paint event.
private void DrawSquare_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
foreach (Rectangle square in squares)
{
DrawRectangle(square, e.Graphics);
}
}
// This procedure performs the actual drawing, and is called by
// DrawSquare_MouseDown and DrawSquare_Paint.
private void DrawRectangle(Rectangle rect, Graphics g)
{
Pen drawingPen = new Pen(Color.Red, 10);
g.DrawRectangle(drawingPen, rect);
}

A simpler approach is to use one of the overloaded versions on the Invalidate() method. This instructs Windows to repaint only a small portion of the window. The full painting code still runs (which could slow your application if the painting is complex), but only the specified region is repainted, thereby improving performance and drastically reducing screen flicker.


private void DrawSquare_MouseDown(object sender,
System.Windows.Forms.MouseEventArgs e)
{
Rectangle square = new Rectangle(e.X, e.Y, 20, 20);
squares.Add(square);
this.Invalidate(square);
}

Another way to paint just a portion of a window, and achieve better performance, is to develop owner-drawn controls that override their own OnPaint() methods.


Tip

The framework just discussed could become the basis of a simple GDI+ drawing application.You would probably add controls that allow the user to draw more than one type of object.You would need to add a special class (perhaps called DrawnShape) that encapsulates all the details about the drawn object, such as size, color, pen width, and so on.Your Paint event handler would then iterate through a collection of DrawnShape objects and render all of them to the form.




Rendering Mode and Antialiasing


One factor that's hampered the ability of drawing tools in other programming frameworks (like Visual Basic) is the lack of control over rendering quality. With GDI+, however, you can enhance the quality of your drawing with automatic antialiasing.

Antialiasing is a technique used to smooth out jagged edges in shapes and text. It works by adding shading at the border of an edge. For example, grey shading might be added to the edge of a black curve to make a corner look smoother. Technically, antialiasing blends a curve with its background. Figure 12-6 shows a close-up of an antialiased ellipse.


Figure 12-6: Antialiasing with an ellipse

To use smoothing in your applications, you set the SmoothingQuality property of the Graphics object. You can choose between None, HighSpeed (the default), AntiAlias, and HighQuality (which is similar to AntiAlias but uses other, slower optimizations with LCD screens). The Graphics.SmoothingQuality property is one of the few stateful Graphics class members. That means that you set it before you begin drawing, and it applies to any text or shapes you draw in the rest of the paint session (until the Graphics object is disposed of).


e.Graphics.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias;

Figure 12-7 shows a form with several picture boxes. Each picture box handles its own paint event, sets a different smoothing mode, and then draws an ellipse.


Figure 12-7: Smoothing modes for shapes

Antialiasing can also be used with fonts to soften jagged edges on text. The latest versions of the Windows operating system use antialiasing automatically with on-screen fonts. However, you can set the Graphics.TextRenderingHint property to ensure optimized text. You can choose between SingleBitPerPixelGridFit (fastest performance and lowest quality), AntiAliasGridFit (better quality but slower performance), and ClearTypeGridFit (the best quality on an LCD display). Or, you can use the SystemDefault value to use whatever font smoothing settings the user has configured. Figure 12-8 compares different font smoothing modes.


Figure 12-8: Smoothing modes for fonts



Double Buffering


You may notice that when you repaint a window frequently it flickers madly. The flicker is caused by the fact that with each paint event, the image is first erased and then redrawn object by object. The flash you see is the blank background that precedes the redrawn content.

You can reduce flickering by preventing a control or form from drawing its background. If you do, your code must begin by painting a background using one of the fill methods from the Graphics class. Otherwise, the original content remains underneath the new content.

To disable background painting, all you need to do is override the OnPaintBackground() method for the form or control and do nothing. In other words, you won't call the base OnPaintBackground() method.


protected override void OnPaintBackground(
System.Windows.Forms.PaintEventArgs pevent)
{
// Do nothing.
}

If you are filling a form or control with a custom background color, you should always follow this step, as it can improve performance dramatically. Otherwise, your window will flicker noticeably between the default background color and the color you paint every time you redraw the form.

Disabling the automatic background painting reduces flicker, but the flicker remains. To remove it completely, you can use a technique known as double buffering. With double buffering, an image is built in memory instead of on the surface of a form or control. When the image is completed, it's drawn directly to the form. The process of drawing takes just as long, but the refresh is faster because it is delayed until the image is completely rendered. Hence, there is very little flicker.

To use double buffering, you need to create an Image object. You then draw on the in-memory Image object using the Graphics methods. Finally, you copy the fully rendered image to the form. One good way to test double buffering is to create a form that is frequently refreshed. The next example presents a form with an ellipse that grows and shrinks automatically (see Figure 12-9). The form is redrawn in response to the tick of a Timer control.


Figure 12-9: Using double buffering

Here's the timer code:


private bool isShrinking = false;
private int extraSize = 0;
// This code is triggered in response to the timer tick.
private void tmrRefresh_Tick(object sender,
System.EventArgs e)
{
// Change the circle dimensions.
if (isShrinking)
{
extraSize--;
}
else
{
extraSize++;
}
// Change the sizing direction if needed.
if (extraSize > (this.Width - 150))
{
isShrinking = true;
}
else if (extraSize < 1)
{
isShrinking = false;
}
// Repaint the form.
this.Invalidate();
}

The paint code examines the state of a check box and decides whether or not it will implement double buffering.


private void DoubleBuffering_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
Graphics g;
Bitmap drawing = null;
// Check if double buffering is needed, and assign the GDI+ context.
if (chkDoubleBuffer.Checked)
{
drawing = new Bitmap(this.Width, this.Height, e.Graphics);
g = Graphics.FromImage(drawing);
}
else
{
g = e.Graphics;
}
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
// Draw a rectangle.
Pen drawingPen = new Pen(Color.Black, 10);
g.FillRectangle(Brushes.White, new Rectangle(new Point(0, 0),
this.ClientSize));
g.DrawEllipse(drawingPen, 50, 50, 50 + extraSize, 50 + extraSize);
// If using double buffering, render the final image and dispose of it.
if (chkDoubleBuffer.Checked)
{
e.Graphics.DrawImageUnscaled(drawing, 0, 0);
g.Dispose();
}
}

When you test this application, you'll see that there is absolutely no flicker in double-buffered mode. There is significant flicker without it.


Tip

The .NET Framework implements its own drawing optimizations.You'll find that if you don't override the OnPaintBackground() method the double buffered method is actually slower than direct drawing, and produces noticeable flicker. However, if you disable background painting and implement double buffering, drawing operations are performed without any detectable flicker.




Painting and Debugging


Debugging drawing code can sometimes be frustrating. For example, consider what happens if you set a breakpoint in the painting code. When the breakpoint is reached, the code enters break mode, the IDE appears, and the window is hidden. When you run the next line of code, the program is redisplayed, and a new Paint event is triggered.

To escape this endless sequence of repainting, you can use a couple of tricks:



If you have a large monitor, you may be able to run your application alongside the program you are testing. Then, when your program enters break mode, the IDE window does not appear on top of your program window, and a repaint is not triggered.



Alternatively, you can set the TopMost property of your form to true, which keeps it superimposed on your IDE window at all times. This should also avoid a repaint.





/ 142