diff -r 43e37759235e -r 51a74ef9ed63 Symbian3/SDK/Source/GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Symbian3/SDK/Source/GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita Wed Mar 31 11:11:55 2010 +0100 @@ -0,0 +1,552 @@ + + + + + +How +to write controls +

Cone itself does not provide any concrete controls. Uikon and the UI variant +libraries provide a large number of 'stock' controls for application writers. +Application writers often need to supplement the standard set of controls +with application specific controls of their own. These may be completely new +controls or, more often, compound controls which contain a number of standard +controls.

+

This section describes how to create controls and how to integrate them +in to the control framework. It is divided into the following sections:

+

Creating +a control

+

Window +owning or not?

+

Creating +a compound control

+

Size, +position and layout

+

Drawing +and refreshing

+

Drawing +backgrounds

+

Drawing +text

+

Drawing +graphics

+

Handling +events

+

Implementing +the Object Provider (MOP) interface

+
Creating a +control

A control is a class which derives from CCoeControl. +It should have a public constructor and, if any leaving function calls or +memory allocations are required during construction, a ConstructL() function. +The majority of re-useable and configurable controls have a ConstructFromResourceL() function +which allows a specific instance of a control to be configured using an application's +resource file. Obviously any memory allocated must be freed in the destructor. +Before a control is drawn to the screen it must be activated. The ActivateL() +function may be overriden to perform last-minute configuration (but +must call the function in the base class).

+Class CMyControl : public CCoeControl + { + public: + CMyControl() ; + void ConstructL(...) ; + // from CCoeControl + void ConsructFromResourceL( TResourceReader& aReader ) ; + private: + ~CMyControl() ; + + // additional functions to handle events + // additional functions to draw the control + // additional functions to determine the size, layout and position the control + }
+
Window owning +or not?

The decision over whether to make a control window owning +or not is usually straightforward. Each view requires a window, so the top-level +control must be window-owning and a child of the AppUi. Below this a window +owning control is normally only necessary where a sense of layering is required: +for instance a pop-up window or a scrolling window. Dialogs and menus are +window owning controls but these are normally implemented in the Uikon and +UI variant libraries and do not require custom derivation from CCoeControl. +Unnecessary window-owning controls should be avoided as they require more +infrastructure, place greater demand on the Window Server and reduce performance.

If +a control must be window owning its window must either be created in the ConstructL() function +or by the caller. The former is preferred. There are several overloads of +the CreateWindowL() and CreateBackedUpWindowL() functions. +Those which do not take a parent parameter create a top-level window which +is a child of the root window.

If a control is not window owning its SetContainerWindowL() function +must be called when it is instantiated.

If it can, the Framework will +automatically set up the parent pointer when the window, or associated window +relationship is established. If it cannot do this, because CreateWindowL() or SetContainerWindowL() did +not provide a CCoeControl, the parent pointer (and MopParent) +may be set expicitly using SetParent() and SetMopParent().

+
Creating a +compound control

Most applications UIs are built from compound +controls. Many custom controls are built up from stock controls and are therefore +also compound controls. When a compound control is constructed it constructs +its components in its ConstructL() function. When it receives +commands itself, such as ActivateL() and DrawNow() it +passes them on to each of its components. In most cases the Framework does +much of the donkey work as long as the compound control has been constructed +correctly.

There are now two methods of creating and managing lodger +controls. The first method described is the one that should be used.

+void MyControl::ConstructL( ... ) + { + // initialise the component array. This must be called once (subsequent calls have no effect) + InitComponentArrayL() ; + + // construct each component control and add it to the component array. + CComponent* myComponent = new (ELeave) CComponent ; + Components().AppendLC( myComponent ) ; // or InsertLC or InsertAfterLC(). Places item on cleanup stack. + myComponent->ConstructL() ; + myComponent->SetThisAndThatL() ; + CleanupStack::Pop( myComponent ) ; + }

The return value of the insert and append methods is +a CCoeControlArray::TCursor object which works as an iterator. +It will remain valid when other items are inserted or deleted, or even if +the whole array is re-ordered.

The insert and append methods leave +the component on the Cleanup Stack using a dedicated Cleanup Item that protects +the parent's array as well as the component itself.

The insert and +append methods allow each component to be given an ID. The ID must be unique +only within the parent so typically a compound control will have an enum listing +each of its children's IDs. CCoeControlArray , accessed +using CCoeControl::Components(), has a ControlById() method +to retrieve components using their IDs.

Components in the array are, +by default, owned by the parent and will be deleted automatically when the +parent is deleted. The default may be overridden using CCoeControlArray::SetControlsOwnedExternally(). +The setting applies to all of the components.

Controls may be removed +from the array using one of the Remove() methods. These do +not delete.

+class CCoeControlArray + ... + public: + IMPORT_C TInt Remove(const CCoeControl* aControl); + IMPORT_C CCoeControl* Remove(TCursor aRemoveAt); + IMPORT_C CCoeControl* RemoveById(TInt aControlId); + ... +

Using the component array as described is now the approved +method of constructing and managing compound controls. In older versions of +Symbian OS a specific method of handling components was not provided and developers +were obliged to design their own. Bypassing the component array is still possible. +It is necessary to allocate and store the components (typically as member +data) and to implement the CountComponentControls() and ComponentControl() functions +to return the number of components and a specified component to the framework. +The new method offers significant advantages when controls are added and removed +dynamically or are dependant on run-time data. The new method is also integrated +with new layout managers.

+
Size, position +and layout

There are several factors which contribute to a control's +size and position. The control itself will require a certain size in order +to display itself (and its data) correctly. The control's container will be +responsible for positioning the control but is also likely to be responsible +for positioning other controls - each of which will have its own requirements. +Additionally there are the requirements of the UI's look and feel that must +be complied with.

Each control is responsible for implementing its +own Size() function.

Until Symbian OS version 9.1 +it was normal to write layout code for simple and compound controls in the SizeChanged() function. +This is called by the framework, as one might expect, when a control's size +(its 'extent') is changed. From 9.1, however, Symbian OS supports the use +of the layout manager interface (MCoeLayoutManager) and +the SizeChanged() function is now implemented in the base +class. (Note that if a control's position is changed, with no size change, +using CCoeControl::SetPosition() its PositionChanged() function +is called and that default implementation of PositionChanged() is +empty).

+class MCoeLayoutManager + ... + protected: + IMPORT_C MCoeLayoutManager(); + + public: + virtual TBool CanAttach() const = 0; + virtual void AttachL(CCoeControl& aCompoundControl) = 0; + virtual void Detach(CCoeControl& aCompoundControl) = 0; + virtual TSize CalcMinimumSize(const CCoeControl& aCompoundControl) const = 0; + virtual void PerformLayout() = 0; + virtual TInt CalcTextBaselineOffset(const CCoeControl& aCompoundControl, const TSize& aSize) const = 0; + virtual void SetTextBaselineSpacing(TInt aBaselineSpacing) = 0; + virtual TInt TextBaselineSpacing() const = 0; + virtual void HandleAddedControlL(const CCoeControl& aCompoundControl, const CCoeControl& aAddedControl) = 0; + virtual void HandleRemovedControl(const CCoeControl& aCompoundControl, const CCoeControl& aRemovedControl) = 0; + virtual TInt HandleControlReplaced(const CCoeControl& aOldControl, const CCoeControl& aNewControl) = 0; + ... +

A layout manager may be attached to a compound control.

+class CCoeControl + ... + protected: + IMPORT_C MCoeLayoutManager* LayoutManager() const; + IMPORT_C virtual void SetLayoutManagerL(MCoeLayoutManager* aLayoutManager); + + public: + IMPORT_C virtual TBool RequestRelayout(const CCoeControl* aChildCtrl); + ...

The default implementations of MinimumSize() and SizeChanged() now +use the layout manager.

+EXPORT_C TSize CCoeControl::MinimumSize() + { + const MCoeLayoutManager* layoutManager = LayoutManager(); + if (layoutManager) + return layoutManager->CalcMinimumSize(*this); + else + return iSize; + } + +EXPORT_C void CCoeControl::SizeChanged() + { + MCoeLayoutManager* layout = LayoutManager(); + if (layout) + layout->PerformLayout(); +

The layout manager is responsible for the size and position +of the component controls. In practice it's likely that the UI variant libraries +will provide concrete layout managers. Application developers should use these +as the basis for control-specific layout managers.

+
Drawing and +refreshing

A fundamental requirement of most controls is that they +are able to render themselves onto the screen. For most controls the drawing +process involves outputting text, painting backgrounds (either plain or from +a bitmap), drawing shapes (graphics objects) and drawing component controls.

Screen +drawing may be initiated by the application itself, following something within +the application changing, or by the Window Server, due to something else in +the system updating the screen while the application is visible. In both cases +the control's Draw() function will be called automatically +by the framework. For compound controls all of the components' Draw() functions +will also be called - unless the component lies completely outside the area +that requires redrawing.

As a control writer you will probably have +to implement a Draw() function.

Here is the signature +for Draw():

private: + void Draw( const TRect& aRect ) const ;

Note that it +is private, takes a const TRect& as a parameter, must +not leave and is const.

It should only be called +by the framework. Application initiated redraws should be through calls to DrawNow(), DrawDeferred() or +custom functions for drawing smaller elements.

The aRect parameter +is the part of the control that requires drawing (refreshing).

The +function is const and non-leaving because it is intended +to support the decoupling of drawing actions from application state.

+
Drawing backgrounds

A +control's background is typically determined by the current colour scheme +or skin. It may be a plain colour or a bitmap. It's also possible that a control +is to appear non-rectangular or transparent in which case some of the background +will be the control underneath. Prior to Symbian OS 9.1 controls were required +to clear and update their whole area and creating these effects was rather +complex. From 9.1 controls are drawn 'backmost first'.

Background +drawing should be done by a dedicated background drawer - i.e. an object which +implements the MCoeControlBackground interface. A background +can be attached to a CCoeControl using SetBackground() and +is used for that control and all of its children. When a control is drawn +the framework looks for the nearest background up the run-time hierarchy and +calls MCoeControlBackground::Draw().

UI variant libraries +typically provide backgrounds. They are not owned by the controls to which +they are attached.

+
Drawing text

Text +must be drawn with the correct color, font, size and direction. As with backgrounds, +these are determined at runtime according to UI customizations. This is achieved +by means of a Text Drawer. Note the use of the XCoeTextDrawer class. +This is a smart pointer (note the use of the -> operator +to access CCoeTextDrawerBase functions) which ensures that +only one text drawer is allocated on the heap at a time.

+XCoeTextDrawer textDrawer( TextDrawer() ); +textDrawer->SetAlignment(iAlignment); +textDrawer->SetMargins(iMargin); +textDrawer->SetLineGapInPixels(iGapBetweenLines); +textDrawer.SetClipRect(aRect); // have to use . [dot] operator for SetClipRect() as not CCoeTextDrawerBase function. + +textDrawer.DrawText(gc, *iTextToDraw, Rect(), *Font()); +

Text drawers are typically provided by the UI variant library +or skin manager. Controls within the run-time hierarchy can set the text drawer +for their children by overriding GetTextDrawer().

Note +that the text drawer expects text to be passed as a TBidiText rather +than a descriptor. Controls should store all display text in TBidiText objects. +Application writers should consider the implications of right-to-left layouts +for languages such as Hebrew and Arabic.

A control's GetTextDrawer() function +might look something like this. It checks on the current state of the control +(IsDimmed()) and passes the call on to a skin manager.

+EXPORT_C void CMyButtonControl::GetTextDrawer(CCoeTextDrawerBase*& aTextDrawer, const CCoeControl* aDrawingControl, TInt /*aKey*/) const + { + const TInt textDrawerIndex = (IsDimmed() ? EButtonTextDimmed : EButtonText); + + SkinManager::GetTextDrawer(aTextDrawer, KSkinUidButton, textDrawerIndex, aDrawingControl); + } +

If the control is drawing text on its own graphics (and does +not care about the text drawer of its parents) it can just create an XCoeTextDrawer object +on the stack in its Draw() method and initiate it from the +skin that it is currently using to draw its graphics, using the CSkinPatch::TextDrawer() method, +like this:

+const CSkinPatch& skin = SkinManager::SkinPatch(KSomeSkinUid, KSomeSkinIndex, this); + +skin.DrawBitmap(gc, Rect(), aRect); +XCoeTextDrawer textDrawer( skin.TextDrawer(KSomeSkinUid, ESomeSkinTextDimmed, this) ); + +const CFont& font = ScreenFont(TCoeFont::NormalFont); +textDrawer.DrawText(gc, iText, rect, font); +

The example above also illustrates how to retrieve the correct +font. CFont objects must not be stored in control member +data as they must change when the control's zoom state changes. Instead, a TCoeFont that +represents a font's size (logical or absolute in pixels) and style (plain, +bold, italic, subscript, or superscript) should be used.

+class TCoeFont + ... + public: + IMPORT_C TCoeFont(TLogicalSize aSize, TInt aStyle, TInt aFlags = ENoFlags); + IMPORT_C TCoeFont(TInt aHeightInPixels, TInt aStyle, TInt aFlags = ENoFlags); + IMPORT_C TCoeFont(const TCoeFont& aFont); + IMPORT_C TCoeFont(); + ...

By creating a TCoeFont object +describing the properties of the desired font, a CFont object +reference (needed to actually draw the text) can be fetched from the CCoeFontProvider. +A font provider can be attached to any CCoeControl and +will keep information about the typeface used by that control and all controls +below. A default font provider is attached to the CCoeEnv.

+class CCoeControl + ... + public: + IMPORT_C const CCoeFontProvider& FindFontProvider() const; + IMPORT_C void SetFontProviderL(const CCoeFontProvider& aFontProvider); + ... +

To get hold of the CFont object +a Draw() method can be implemented like this:

+void CMyControl::Draw(const TRect& aRect) + { + const CCoeFontProvider& fontProvider = FindFontProvider(); + const CFont& font = fontProvider.Font(TCoeFont::LegendFont(), AccumulatedZoom()); + + XCoeTextDrawer textDrawer( TextDrawer() ); + textDrawer->SetAlignment(EHCenterVCenter); + textDrawer.DrawText(gc, iText, rect, font); + } +

For convenience there’s a CCoeControl::ScreenFont() method +that locates the font provider and calls it with the control’s accumulated +zoom:

+class CCoeControl + ... + protected: + IMPORT_C const CFont& ScreenFont(const TCoeFont& aFont) const; + ... +
+
Drawing graphics

Controls +draw graphics objects - lines, rectangles, shapes and bitmaps to a graphics +context. The graphics context is provided by the Window Server and +represents a group of settings appropriate for the physical device that is +ultimately being drawn to. In most cases the device is a screen and a graphics +context should be obtained using CCoeControl::SystemGc(). CCoeControl::SystemGc() gets +the current graphics context from the run-time hierarchy. Controls in the +hierarchy may override graphics context settings which will then be passed +on to their children. Controls should not get their graphics context directly +from CCoeEnv as to do so would bypass the hierarchy.

+void CMyControl::Draw( const TRect& aRect ) + { + CWindowGc& gc = SystemGc() ; // get gc from run time hierarchy + TRect rect = TRect( Size() ) ; + if ( IsBlanked() ) + { + // blank out the entire control + gc.SetPenStyle( CGraphicsContext::ENullPen ) ; + gc.SetBrushStyle( CGraphicsContext::ESolidBrush ) ; + TRgb blankColor = BlankingColor() ; + gc.SetBrushColor( blankColor ) ; + gc.DrawRect( rect ) ; + } + else + { + // draw masked bitmap in the centre of the control + // The parent will draw the background + TInt id = BitMapId() ; + + TInt x = Size().iWidth - iBitmap[id]->SizeInPixels().iWidth ; + TInt y = Size().iHeight - iBitmap[id]->SizeInPixels().iHeight ; + + TPoint pos = Rect().iTl ; + pos.iX = pos.iX + ( x / 2 ) ; + pos.iY = pos.iY + ( y / 2 ) ; + + gc.BitBltMasked( pos, iBitmap[id], rect, iMaskBitmap, ETrue ) ; + } + }

Before a graphics context can be used it must be activated. +After use it must be deactivated. Activation and deactivation are done automatically +by the framework in DrawNow(), DrawDeferred() and HandleRedrawEvent() but +must be done explicitly for any other application initiated drawing by calling ActivateGc() and DeactivateGc().

Controls may implement partial drawing to speed up performance. The Draw() function +may be split into sub functions: DrawThis(), DrawThat(), DrawTheOther(). +Each of these requires a corresponding DrawThisNow() and/or DrawThisDeferred() function.

+CMyControl::Draw() + { + DrawThis() ; + DrawThat() ; + DrawTheOther() ; + } +CMyControl::DrawThisNow() + { + ActivateGc() ; + DrawThis() ; + DeactivateGc() ; + }
+
Handling events

The +Control Framework supports user interaction in two ways: key-press events +and pointer events. Both types of event arrive through the Window Server though +they each arrive in a slightly different way. Both are closely related to +the concept of 'focus' and the location of the cursor.

Handling +key events

Key events are delivered to the AppUi. The Window +Server channels them through the root window of its current window group which +maps to the AppUi foreground application. The AppUi offers each key event +to each of the controls on its control stack in priority order until one of +the controls 'consumes' it.

To receive key events a control must implement CCoeControl::OfferKeyEventL(). +If it handles the event it must return EKeyWasConsumed: If +it doesn't it must return EKeyWasNotConsumed so that the +next control on the stack receives it.

+TKeyResponse CMyControl::OfferKeyEventL( const TKeyEvent& aKeyEvent, TEventCode aType) + { + TKeyResponse returnValue = EKeyWasConsumed ; + switch( aKeyEvent.iCode ) + { + case EKeyEnter : + // do stuff + break ; + case EKeyEscape : + // do stuff : + break ; + + ... + + default : + // did not recognise key event + returnValue = EKeyWasNotConsumed ; + break ; + } + return returnValue ; + }

The handling of key events will depend on the design +and purpose of the control itself. Compound controls might need to keep track +of an input focus, or cursor, and to pass key events amongst +its lodgers. Input into one lodger might have an effect on another - pressing +a navigation key might cause one control to lose the highlight and another +to gain it, pressing a number key might cause a text editor to grow which +might, in turn, require all of the components below it to shuffle downwards +and a scroll bar to become visible (which might also require some controls +to be laid out differently).

Handling pointer events

Pointer +events are slightly different as the position of the pointer, rather than +of the focus, is significant. The Window Server passes a pointer event to +the top-most visible window at the point of contact. The Framework uses the +functions ProcessPointerEventL() and HandlePointerEventL() to +work down the hierarchy. The Framework also uses the MCoeControlObserver and +focussing mechanisms to inform the observer of the controls that will be losing +and gaining the focus.

Using the Control Observer Interface

The +Control Framework facilitates this type of relationship between a container +and its lodgers with the MCoeControlObserver interface. Typically +the container implements the interface and becomes the observer for each lodger +that can receive user input (focus). There is only one function in MCoeControlObserver:

virtual void HandleControlEventL( CCoeControl *aControl, TCoeEvent aEventType ) = 0 ;

and it is called when an observed control calls

void CCoeControl::ReportEvent( MCoeControlObserver::TCoeEvent aEvent ) ;

A control can have only one observer (or none) so ReportEvent() does +not need to specify an observer. An observer may observe any number of controls +so HandleControlEventL() takes the observed control as a +parameter. The other piece of information passed to the observer is a TCoeEvent.

enum TCoeEvent + { + EEventRequestExit, + EEventRequestCancel, + EEventRequestFocus, + EEventPrepareFocusTransition, + EEventStateChanged, + EEventInteractionRefused + };

CCoeControl also provides IsFocused(), SetFocused() and IsNonFocussing(). Note that Framework does not attempt to ensure exclusivity of focus, nor +does it give any visible indication of focus. It is up to the application +developer to ensure that only one control has the focus at a time, that the +focus is correctly transferred between controls, that only appropriate controls +receive the focus and that the focus is visible at all times.

void CContainer::HandleControlEventL(CCoeControl* aControl, TCoeEvent aEventType) + { + switch (aEventType) + { + case EEventRequestFocus: + { + if( !(aControl->IsFocussed()) ) + { + aControl->SetFocus( ETrue ) ; + // remove focus from other controls + for ( Tint ii = 0 ; ii < CountComponentControls() ; ii++ ) + { + CCoeControl* ctl = ComponentControl( ii ) ; + if( ( ctl != aControl ) && !( ctl->IsNonFocussing() ) ) + { + aControl->SetFocus( EFalse ) ; + } + } + } + } + break; + ... + } + }

Control developers may implement HandlePointerEventL(), +which is a virtual function, to perform pointer event functionality. The implementation +must, however, call the base class function.

Controls may modify their +pointer area, possibly if they appear non-rectangular or overlap. To do so +requires the addition of a hit test which describes a hit-test region. A hit-test +region may cover all or part of one or more controls. A hit for a control +is registered in the area covered by both the control and its associated hit +test.

The diagram below represents three controls, each of which is +rectangular but which appears on the screen as a non-rectangular bitmap. Only +a hit on a bitmap area should register. This could be achieved by defining +a single hit-test region in the shape (and position) of the three blue areas +and associating it with each of the controls. The class that implements the +hit-test region must implement the MCoeControlHitTest interface.

+ Hit-test region example + + +class MCoeControlHitTest + ... + public: + virtual TBool HitRegionContains( const TPoint& aPoint, const CCoeControl& aControl ) const = 0; +

A hit test is associated with a control using CCoeControl::SetHitText(). +The base class implementation of HandlePointerEventL() performs +the following test:

+ ... + const MCoeControlHitTest* hitTest = ctrl->HitTest() ; + if( hitTest ) + { + if( hitTest->HitRegionContains( aPointerEvent.iPosition, *ctrl ) && + ctrl->Rect().Contains( aPointerEvent.iPosition ) )

Note +that this is performed by a container when deciding which lodger to pass the +event onto. This snippet also illustrates how a control can find where (iPosition) +the pointer event actually occurred.

Pointer support includes dragging +& grabbing. See TPointerEvent.

+
Implementing +the Object Provider (MOP) interface

The Object +Provider mechanism exists to allow a control to call a function on +another control in the hierarchy for which it does not have a reference. It +simply calls MopGetObject() specifying the interface containing +the function. It may also call MopGetObjectNoChaining() to +inquire of a specific object whether it supports the requested interface.

Only +controls which wish to supply an interface require customisation. In order +to be identifiable an interface must have an associated UID. The following +code samples show how CEikAlignedControl implements and +supplies MEikAlignedControl:

+class MEikAlignedControl + ... + public: + DECLARE_TYPE_ID( 0x10A3D51B ) // Symbian allocated UID identifies this interface + ... + +class CEikAlignedControl : public CCoeControl, public MEikAlignedControl + { + ... + private: //from CCoeControl + IMPORT_C TTypeUid::Ptr MopSupplyObject( TTypeUid aId ) ; + ... + +EXPORT_C TTypeUid::Ptr CEikAlignedControl::MopSupplyObject( TTypeUid aId ) + { + if( aId.iUid == MEikAlignedControl::ETypeId ) + return aId.MakePtr( static_cast<MEikAlignedControl*>( this ) ) ; + + return CCoeControl::MopSupplyObject( aId ) ; // must call base class! + } +

To get an interface from the object provider framework the +caller must use a pointer to the interface.

+ ... + MEikAlignedControl* alignedControl = NULL ; + MyControl->MopGetObject( alignedControl ) ; + if ( alignedControl ) + { + ... // etc.

To get an interface from a specific object +the caller may use the no-chaining function call.

+ ... + MEikAlignedControl* alignedControl = NULL ; + aControl->MopGetObjectNoChaining( alignedControl ) ; + if ( alignedControl ) + { + ... // etc.
+
\ No newline at end of file