Drawing Scrollable Windows


The earlier DrawShapes example worked very well, because everything you needed to draw fit into the initial window size. This section looks at what you need to do if that's not the case.

For this example, you expand the DrawShapes sample to demonstrate scrolling. To make things a bit more realistic, you start by creating an example, BigShapes, in which you make the rectangle and ellipse a bit bigger. Also, while you're at it, you'll see how to use the Point, Size, and Rectangle structs by using them to assist in defining the drawing areas. With these changes, the relevant part of the Form1 class looks like this:

 // member fields private Point rectangleTopLeft = new Point(0, 0); private Size rectangleSize = new Size(200,200); private Point ellipseTopLeft = new Point(50, 200); private Size ellipseSize = new Size(200, 150); private Pen bluePen = new Pen(Color.Blue, 3); private Pen redPen = new Pen(Color.Red, 2); protected override void OnPaint( PaintEventArgs e ) {    base.OnPaint(e);    Graphics dc = e.Graphics; if (e.ClipRectangle.Top < 350 || e.ClipRectangle.Left < 250) { Rectangle rectangleArea = new Rectangle (rectangleTopLeft, rectangleSize); Rectangle ellipseArea = new Rectangle (ellipseTopLeft, ellipseSize); dc.DrawRectangle(bluePen, rectangleArea); dc.DrawEllipse(redPen, ellipseArea); } }

Note that you've also turned the Pen, Size, and Point objects into member fields — this is more efficient than creating a new Pen every time you need to draw anything, as you have been doing up to now.

The result of running this example looks like Figure 25-6.

image from book
Figure 25-6

You can see a problem instantly. The shapes don't fit in your 300300 pixel drawing area.

Normally, if a document is too large to display, an application will add scroll bars to let you scroll the window and look at a chosen part of it. This is another area in which if you were building Windows

Forms using standard controls, then you'd just let the .NET runtime and the base classes handle everything for you. If your form has various controls attached to it, the Form instance will normally know where these controls are and it will therefore know if its window becomes so small that scroll bars are necessary. The Form instance also automatically adds the scroll bars for you, and it is also able to draw correctly whichever portion of the screen you've scrolled to. In that case there is nothing you need to do in your code. In this chapter, however, you're taking responsibility for drawing to the screen; therefore, you're going to have to help the Form instance out when it comes to scrolling.

Getting the scroll bars added is actually very easy. The Form can still handle all that for you, because it doesn't know how big an area you will want to draw in. (The reason it hasn't in the earlier BigShapes example is that Windows doesn't know they are needed.) What you need to figure out is the size of a rectangle that stretches from the top-left corner of the document (or equivalently, the top-left corner of the client area before you've done any scrolling), and which is just big enough to contain the entire document. In this chapter, this area is referred to as the document area. As shown in Figure 25-7, for this example the document area is (250, 350) pixels.

image from book
Figure 25-7

It is easy to tell the form how big the document is. You use the relevant property, Form.AutoScrollMinSize. Therefore you can add this code to either the InitializeComponent() method or the Form1 constructor:

private void InitializeComponent() {    this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);    this.ClientSize = new System.Drawing.Size(292, 266);    this.Name = "Form1";    this.Text = "BigShapes";    this.BackColor = Color.White; this.AutoScrollMinSize = new Size(250, 350); } 

Alternatively, the AutoScrollMinSize property can be set using the Visual Studio 2005 Properties window.

Setting the minimum size at application startup and leaving it thereafter is fine in this particular example, because you know that is how big the screen area will always be. Your document never changes size while this particular application is running. Keep in mind, however, that if your application does things like display contents of files or something else for which the area of the screen might change, you will need to set this property at other times (and in that case you'll have to sort out the code manually — the Visual Studio 2005 Properties window can only help you with the initial value that a property has when the form is constructed).

Setting AutoScrollMinSize is a start, but it's not yet quite enough. Figure 25-8 shows what the sample application looks like now — initially you get the screen that correctly displays the shapes.

image from book
Figure 25-8

Notice that not only has the form correctly set the scroll bars, but it has also correctly sized them to indicate what proportion of the document is currently displayed. You can try resizing the window while the sample is running — you'll find the scroll bars respond properly, and even disappear if you make the window big enough so that they are no longer needed.

However, look at what happens when you actually use one of the scroll bars and scroll down a bit (see Figure 25-9). Clearly, something has gone wrong!

image from book
Figure 25-9

What's wrong is that you haven't taken into account the position of the scroll bars in the code in your OnPaint() override. You can see this very clearly if you force the window to repaint itself completely by minimizing and restoring it (see Figure 25-10).

image from book
Figure 25-10

The shapes have been painted, just as before, with the top-left corner of the rectangle nestled into the top-left corner of the client area — just as if you hadn't moved the scroll bars at all.

Before you see how to correct this problem, take a closer look at precisely what is happening in these screenshots.

Start with the BigShapes sample, shown in Figure 25-8. In this example, the entire window has just been repainted. Reviewing your code you learn that it instructs the graphics instance to draw a rectangle with top-left coordinates (0,0) — relative to the top-left corner of the client area of the window — which is what has been drawn. The problem is that the graphics instance by default interprets coordinates as relative to the client window and is unaware of the scroll bars. Your code as yet does not attempt to adjust the coordinates for the scroll bar positions. The same goes for the ellipse.

Now, you can tackle the screenshot in Figure 25-9. After you scroll down, you notice that the top half of the window looks fine. That's because it was drawn when the application first started up. When you scroll windows, Windows doesn't ask the application to redraw what was already on the screen. Windows is smart enough to figure out for itself which bits of what's currently being displayed on the screen can be smoothly moved around to match where the scroll bars now are located. That's a much more efficient process, because it may be able to use some hardware acceleration to do that too. The bit in this screenshot that's wrong is the bottom third of the window. This part of the window didn't get drawn when the application first appeared, because before you started scrolling it was outside the client area. This means that Windows asks your BigShapes application to draw this area. It'll raise a Paint event passing in just this area as the clipping rectangle. And that's exactly what your OnPaint() override has done.

One way of looking at the problem is that you are at the moment expressing your coordinates relative to the top-left corner of the start of the document — you need to convert them to express them relative to the top-left corner of the client area instead (see Figure 25-11).

image from book
Figure 25-11

To make the diagram clearer, the document is actually extended further downward and to the right, beyond the boundaries of the screen, but this doesn't change our reasoning. It also assumes a small horizontal scroll as well as a vertical one.

In Figure 25-11 the thin rectangles mark the borders of the screen area and of the entire document. The thick lines mark the rectangle and ellipse that you are trying to draw. P marks some arbitrary point that you are drawing and which is being used as an example. When calling the drawing methods the graphics instance was supplied with the vector from point B to (say) point P, expressed as a Point instance. You actually need to give it the vector from point A to point P.

The problem is that you don't know what the vector from A to P is. You know what B to P is — that's just the coordinates of P relative to the top-left corner of the document — the position where you want to draw point P in the document. You also know the vector from B to A is just the amount you've scrolled by; this is stored in a property of the Form class called AutoScrollPosition. However, you don't know the vector from A to P.

Now, if you remember your high school math, you will know how to solve this problem — you subtract the one vector from the other. Say, for example, to get from B to P you move 150 pixels across and 200 pixels down, whereas to get from B to A you have to move 10 pixels across and 57 pixels down. That means to get from A to P you have to move 140 (=150 minus 10) pixels across and 143 (=200 minus 57) pixels down. To make it even simpler, the Graphics class actually implements a method that will do these calculations for you. It's called TranslateTransform(). You pass it the horizontal and vertical coordinates that say where the top left of the client area is relative to the top-left corner of the document (your AutoScrollPosition property, that is, the vector from B to A in the diagram). The Graphics device will now work out all its coordinates, taking into account where the client area is relative to the document.

Translating this long explanation into code, all you typically need to do is add this line to your drawing code:

 dc.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y); 

However in this example, it's a little more complicated because you are also separately testing whether you need to do any drawing by looking at the clipping region. You need to adjust this test to take the scroll position into account too. When you've done that, the full drawing code for the sample looks like this:

 protected override void OnPaint( PaintEventArgs e ) { base.OnPaint(e); Graphics dc = e.Graphics; Size scrollOffset = new Size(this.AutoScrollPosition); if (e.ClipRectangle.Top+scrollOffset.Width < 350 ||  e.ClipRectangle.Left+scrollOffset.Height < 250) { Rectangle rectangleArea = new Rectangle (rectangleTopLeft+scrollOffset, rectangleSize); Rectangle ellipseArea = new Rectangle (ellipseTopLeft+scrollOffset, ellipseSize); dc.DrawRectangle(bluePen, rectangleArea); dc.DrawEllipse(redPen, ellipseArea); } } 

Now you have your scroll code working perfectly; you can at last obtain a correctly scrolled screenshot (see Figure 25-12).

image from book
Figure 25-12




Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net