Wiley.Symbian.OS.C.plus.plus.for.Mobile.Phones.Aug.2007 (779890), страница 89
Текст из файла (страница 89)
You could say that even the button bar has a model(defined by the resource file definitions that construct it) and a controller(somewhere in the application framework).DRAWING AND REDRAWING495The MVC paradigm can become particularly blurred when givingfeedback to some kinds of user interaction – navigation, cursor selection,animation or drag-and-drop (which admittedly is rare in Symbian OS,though it does exist, for example when re-sizing grid columns in aSymbian OS Sheet).
Nonetheless, it remains extremely useful and youcan use it to think about the design of many Symbian OS controls andapplications.Terms used in MVCWe should finish this section with a note on nomenclature. A ‘control’in Symbian OS is not usually a ‘controller’ in the MVC sense. A SymbianOS control does, however, usually contain pure MVC view functionality:its Draw() function draws a model without changing it.In Symbian OS literature, the word ‘view’ is used for a control orsome drawing or interaction code, to highlight the fact that the view isentirely separate from the model. A good example is ETEXT and FORM,the Symbian OS rich text components: ETEXT is a model without viewsand FORM provides views but has no model.We use ‘application view’ in this sense, while the model is oftencontained in the document class. In the Noughts and Crosses application,‘OandX view’ is the name of the control, because the game status modeldata is kept in a separate class.
Often, in Symbian OS literature, the word‘model’ is used for application data that can be saved to a file.The Draw() ContractSymbian OS controls use the Draw() function to implement MVC viewfunctionality. CCoeControl::Draw() is defined in coecntrl.h as:IMPORT_C virtual void Draw(const TRect& aRect) const;A derived class overrides this virtual function to draw – or redraw – itsmodel.
For the rare cases in which this function is not overridden, there’sa default implementation that leaves the control blank.Because CCoeControl::Draw() is strictly an MVC view function,it should not update the model. It is therefore const and non-leaving.Your implementation of Draw() must not leave.This is another reason why most CGraphicsContext functions returnvoid: if they could fail, Draw() could also fail.496GRAPHICS FOR DISPLAYHandling redrawsSystem-initiated redraw handling starts in the window server, whichdetects when you need to redraw part of a window. In fact, it maintainsan invalid region on the window and sends an event to the applicationthat owns the window, asking it to redraw the invalid region.
CONEworks out which controls intersect the invalid region and converts theevent into a call to Draw() for all affected controls. A system-initiatedredraw must redraw the model exactly as the previous draw.Application-initiated redraw handling starts (by definition) in the application. If you update a model and need to redraw a control, you cansimply call its DrawNow() function. DrawNow() is a non-virtual functionin CCoeControl that:• tells the window server that the control is about to start redrawing• calls Draw()• tells the window server that the control has finished redrawing.In theory, then, you do not need to code any new functions in orderto do an application-initiated redraw.
You can simply call DrawNow(),so that your Draw() function is called in turn.Where to drawIt is possible that only part of your control needs to be drawn (orredrawn). To understand this, you need to distinguish between the fourregions shown in Figure 17.6.Bounding rectangle ofinvalid region within controlInvalid regionof windowControlWindowScreenFigure 17.6Window regionsDRAWING AND REDRAWING497Your control is part of a window. The window server knows about thewindow and knows which regions of the window are invalid – that is,the parts that need to be redrawn.
Your Draw() function must draw theentire invalid region, but it must not draw outside the boundary of thecontrol.The window server clips drawing to the invalid region – which isclearly bounded, in turn, by the boundary of the window itself.If your control does not occupy the entire window, you are responsiblefor ensuring that your redraw does not spill beyond the boundaries ofthe control.Often, this turns out not to be too onerous a responsibility: manycontrols, such as buttons and the Noughts and Crosses applicationscreen, draw rectangles, lines and text that are guaranteed to be insidethe control’s boundary in any case.In the few cases where this does not happen, you can issue a SetClippingRect() call to the graphics context that ensures that futuredrawing is clipped to the control’s rectangle. Here is an example that isdeveloped later in this chapter:aGc.SetClippingRect(aDeviceRect);aGc.SetPenColor(KRgbDarkGray);aGc.DrawRect(surround);This is necessary because surround could have been bigger thanaDeviceRect, which is the region of the control that this code is allowedto draw into.
You can cancel this later, if you wish, with CancelClippingRect(), but since CGraphicsContext::Reset() does thisanyway and Reset() is called prior to each control’s Draw(), you donot need to do this explicitly from a control.You can assume that the graphics context is reset before Draw() iscalled. Do not reset it yourself and do not set colors and options thatyou do not need.Avoiding wasteful redrawsDrawing outside the invalid region is technically harmless (because suchdrawing is clipped away by the window server whether it is inside yourcontrol’s boundaries or not), but it is potentially wasteful.
You may beable to save time by confining your drawing activity to the invalid region;498GRAPHICS FOR DISPLAYthe tradeoff is that you must do some testing to find out what you mustdraw and what you do not need to draw.That’s the purpose of the TRect passed to the Draw() function: it isthe bounding rectangle of the invalid region. If you wish, you can use thisto draw (or redraw) only the part of the control within the passed TRect.It is worth doing this if the cost of testing is outweighed by the savingsfrom avoiding irrelevant drawing.In practice, very few controls gain much by confining their redrawactivity entirely to the bounding rectangle: it is simpler and not muchslower to redraw the whole control. As a result, the majority of controlsare coded to ignore the bounding rectangle.
If you are writing a controlthat does use the TRect, remember that you still have to obey thecontract to cover the entire invalid region within the boundary of yourcontrol and nothing outside your control. (If, however, you are using oneof the window server features then you need to draw to the whole of therectangle, not just the invalid area within it.) You may still have to set aclipping region to ensure this – the system does not set one for you.Early in its development, Symbian OS passed the invalid region (ratherthan its bounding rectangle) to Draw(). This turned out to be moretrouble than it was worth. Regions are data structures of arbitrary size,which are much harder to pass around than TRect objects but theywere passed whether they were needed or not – and they usually werenot.
As a compromise, the bounding rectangle of the invalid regionwas passed.Breaking the const and leave rulesIn quite rare circumstances, you may need to do some processing inDraw() that is not related to drawing. This could happen, for instance,if your view is very complicated and you are doing lazy initializationof some of the associated data structures in order to minimize memoryusage.In this case, you may need to allocate memory during Draw() tohold the results of your intermediate draw-related calculations and thisallocation could cause a leave.
In addition, you’ll want to use a pointerto refer to your newly allocated memory, perhaps in the control. Thisrequires you to change the pointer value, which violates the constattribute of Draw().The solution in this case is to use casting to get rid of the constattribute (for more information on the MUTABLE macros, see the SDK)and to put your resource-allocating code into a leaving function that iscalled from a TRAP() within Draw(). You also have to decide what todraw if your resource allocation fails.DRAWING CONTROLS49917.4 Drawing ControlsChapter 15 described the drawing code for the main Noughts and Crossesapplication.
This code draws to the whole of the control each time andattempts to draw each pixel only once so there is no flicker. It is notperfect at this but the approach described there is good enough for mostcontrols. This section illustrates some techniques that allow individualparts of controls to be drawn.Drawing to Part of a ViewAlthough none of the views in the Noughts and Crosses applicationrequire this functionality, it is an important concept for which thereare good patterns that can be followed. Let’s suppose that, instead ofcontaining one O or X, the Status Window contains two and that theyare surrounded with a rectangular box. The code for drawing this mightthen be:void COandXStatusWin::Draw(const TRect& /*aRect*/) const{CWindowGc& gc = SystemGc();TRect rect = Rect();gc.Clear(rect);TRect border(iSymbolPos, TSize(2*iSymbolSize, iSymbolSize));border = border.Grow(1,1);gc.DrawRect(border);TRect symRect(iSymbolPos, TSize(iSymbolSize, iSymbolSize));DrawSymbol(gc, symRect, iLeftSymbol);symRect.Move(iSymbolSize, 0);DrawSymbol(gc, symRect, iRightSymbol);}This function uses four new members, which store the location of thetop left of the left symbol and the size at which the symbols are to bedrawn; the right symbol is the same size and next to the left one.
Nowthere is much more being drawn in this function and there is a greaterchance of flicker. The background is drawn in white and the symbolis drawn on top; however, there is a gap between the drawing of thebackground and that of the symbol. To avoid this, we can clear the areaoutside the border to start with:void COandXStatusWin::Draw(const TRect& /*aRect*/) const{CWindowGc& gc = SystemGc();TRect rect = Rect();TRect border(iSymbolPos, TSize(2*iSymbolSize, iSymbolSize));border = border.Grow(1,1);500GRAPHICS FOR DISPLAYgc.SetPenStyle(CGraphicsContext::ENullPen);gc.SetBrushStyle(CGraphicsContext::ESolidBrush);gc.SetBrushColor(KRgbWhite);DrawUtils::DrawBetweenRects(gc, rect, border);gc.SetPenStyle(CGraphicsContext::ESolidPen);gc.SetBrushStyle(CGraphicsContext::ENullBrush);gc.DrawRect(border);TRect symRect(iSymbolPos, TSize(iSymbolSize, iSymbolSize));DrawOneTile(gc, symRect, iLeftSymbol==ETileCross);symRect.Move(iSymbolSize, 0);DrawOneTile(gc, symRect, iRightSymbol==ETileCross);}The DrawOneTile() function is defined as:void COandXStatusWin::DrawOneTile(CWindowGc& aGc, const TRect& aRect,TBool aDrawCross) const{aGc.Clear(aRect);DrawSymbol(aGc, aRect, aDrawCross);}Now the background is cleared immediately before the symbol isdrawn on top of it, so there is less chance of flicker.If only one of the symbols changes, there are several options.