|
1 <?xml version="1.0" encoding="utf-8"?> |
|
2 <!-- Copyright (c) 2007-2010 Nokia Corporation and/or its subsidiary(-ies) All rights reserved. --> |
|
3 <!-- This component and the accompanying materials are made available under the terms of the License |
|
4 "Eclipse Public License v1.0" which accompanies this distribution, |
|
5 and is available at the URL "http://www.eclipse.org/legal/epl-v10.html". --> |
|
6 <!-- Initial Contributors: |
|
7 Nokia Corporation - initial contribution. |
|
8 Contributors: |
|
9 --> |
|
10 <!DOCTYPE concept |
|
11 PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd"> |
|
12 <concept id="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1" xml:lang="en"><title>How |
|
13 to write controls</title><prolog><metadata><keywords/></metadata></prolog><conbody> |
|
14 <p>Cone itself does not provide any concrete controls. Uikon and the UI variant |
|
15 libraries provide a large number of 'stock' controls for application writers. |
|
16 Application writers often need to supplement the standard set of controls |
|
17 with application specific controls of their own. These may be completely new |
|
18 controls or, more often, compound controls which contain a number of standard |
|
19 controls. </p> |
|
20 <p>This section describes how to create controls and how to integrate them |
|
21 in to the control framework. It is divided into the following sections: </p> |
|
22 <p><xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-341017B2-8CF9-5124-8D20-C75A8A51F0B7-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-13">Creating |
|
23 a control</xref> </p> |
|
24 <p><xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-3F0E8223-2218-5C95-AFBC-D66AD1DB12A7-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-14">Window |
|
25 owning or not?</xref> </p> |
|
26 <p><xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-795EBF51-AD26-513E-9A82-A99C629CE779-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-15">Creating |
|
27 a compound control</xref> </p> |
|
28 <p><xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-795EBF51-AD26-513E-9A82-A99C629CE779-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-15">Size, |
|
29 position and layout</xref> </p> |
|
30 <p><xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-351911EE-87C7-5D11-8434-BA7FD3741745-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-17">Drawing |
|
31 and refreshing</xref> </p> |
|
32 <p> <xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-FF7DB067-24AD-50C3-BF52-952F836609B0-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-18">Drawing |
|
33 backgrounds</xref> </p> |
|
34 <p> <xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-54C6A39A-CBD0-52E5-8CD0-76BE22247A54-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19">Drawing |
|
35 text</xref> </p> |
|
36 <p> <xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-D1AED2A8-94AC-54BB-9CEB-C8C3643AFBBD-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-20">Drawing |
|
37 graphics</xref> </p> |
|
38 <p><xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-AC723EE4-1482-59C5-9F13-CAE119C7800D-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-21">Handling |
|
39 events</xref> </p> |
|
40 <p><xref href="GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1.dita#GUID-B84FA223-3DFD-58C5-8CEF-C5AA73AA6290-GENID-1-8-1-6-1-1-4-1-6-1-9-1/GUID-FE221E89-1817-5A73-8FBA-212FBC030766-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-22">Implementing |
|
41 the Object Provider (MOP) interface</xref> </p> |
|
42 <section id="GUID-341017B2-8CF9-5124-8D20-C75A8A51F0B7-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-13"><title>Creating a |
|
43 control</title> <p>A control is a class which derives from <xref href="GUID-B06F99BD-F032-3B87-AB26-5DD6EBE8C160.dita"><apiname>CCoeControl</apiname></xref>. |
|
44 It should have a public constructor and, if any leaving function calls or |
|
45 memory allocations are required during construction, a <codeph>ConstructL()</codeph> function. |
|
46 The majority of re-useable and configurable controls have a <codeph>ConstructFromResourceL()</codeph> function |
|
47 which allows a specific instance of a control to be configured using an application's |
|
48 resource file. Obviously any memory allocated must be freed in the destructor. |
|
49 Before a control is drawn to the screen it must be activated. The <codeph>ActivateL() |
|
50 function</codeph> may be overriden to perform last-minute configuration (but |
|
51 must call the function in the base class). </p> <codeblock id="GUID-18D48E7E-9846-5CE0-BE1E-FAB723419B90-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-13-3" xml:space="preserve"> |
|
52 Class CMyControl : public CCoeControl |
|
53 { |
|
54 public: |
|
55 CMyControl() ; |
|
56 void ConstructL(...) ; |
|
57 // from CCoeControl |
|
58 void ConsructFromResourceL( TResourceReader& aReader ) ; |
|
59 private: |
|
60 ~CMyControl() ; |
|
61 |
|
62 // additional functions to handle events |
|
63 // additional functions to draw the control |
|
64 // additional functions to determine the size, layout and position the control |
|
65 }</codeblock> </section> |
|
66 <section id="GUID-3F0E8223-2218-5C95-AFBC-D66AD1DB12A7-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-14"><title>Window owning |
|
67 or not? </title> <p>The decision over whether to make a control window owning |
|
68 or not is usually straightforward. Each view requires a window, so the top-level |
|
69 control must be window-owning and a child of the AppUi. Below this a window |
|
70 owning control is normally only necessary where a sense of layering is required: |
|
71 for instance a pop-up window or a scrolling window. Dialogs and menus are |
|
72 window owning controls but these are normally implemented in the Uikon and |
|
73 UI variant libraries and do not require custom derivation from <xref href="GUID-B06F99BD-F032-3B87-AB26-5DD6EBE8C160.dita"><apiname>CCoeControl</apiname></xref>. |
|
74 Unnecessary window-owning controls should be avoided as they require more |
|
75 infrastructure, place greater demand on the Window Server and reduce performance. </p> <p>If |
|
76 a control must be window owning its window must either be created in the <codeph>ConstructL()</codeph> function |
|
77 or by the caller. The former is preferred. There are several overloads of |
|
78 the <codeph>CreateWindowL()</codeph> and <codeph>CreateBackedUpWindowL()</codeph> functions. |
|
79 Those which do not take a parent parameter create a top-level window which |
|
80 is a child of the root window. </p> <p>If a control is not window owning its <codeph>SetContainerWindowL()</codeph> function |
|
81 must be called when it is instantiated. </p> <p>If it can, the Framework will |
|
82 automatically set up the parent pointer when the window, or associated window |
|
83 relationship is established. If it cannot do this, because <codeph>CreateWindowL()</codeph> or <codeph>SetContainerWindowL()</codeph> did |
|
84 not provide a <xref href="GUID-B06F99BD-F032-3B87-AB26-5DD6EBE8C160.dita"><apiname>CCoeControl</apiname></xref>, the parent pointer (and MopParent) |
|
85 may be set expicitly using <codeph>SetParent()</codeph> and <codeph>SetMopParent()</codeph>. </p> </section> |
|
86 <section id="GUID-795EBF51-AD26-513E-9A82-A99C629CE779-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-15"><title>Creating a |
|
87 compound control</title> <p>Most applications UIs are built from compound |
|
88 controls. Many custom controls are built up from stock controls and are therefore |
|
89 also compound controls. When a compound control is constructed it constructs |
|
90 its components in its <codeph>ConstructL()</codeph> function. When it receives |
|
91 commands itself, such as <codeph>ActivateL()</codeph> and <codeph>DrawNow()</codeph> it |
|
92 passes them on to each of its components. In most cases the Framework does |
|
93 much of the donkey work as long as the compound control has been constructed |
|
94 correctly. </p> <p>There are now two methods of creating and managing lodger |
|
95 controls. The first method described is the one that should be used. </p> <codeblock id="GUID-F77661F7-6A68-58DB-B875-9D957A937617-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-15-4" xml:space="preserve"> |
|
96 void MyControl::ConstructL( ... ) |
|
97 { |
|
98 // initialise the component array. This must be called once (subsequent calls have no effect) |
|
99 InitComponentArrayL() ; |
|
100 |
|
101 // construct each component control and add it to the component array. |
|
102 CComponent* myComponent = new (ELeave) CComponent ; |
|
103 Components().AppendLC( myComponent ) ; // or InsertLC or InsertAfterLC(). Places item on cleanup stack. |
|
104 myComponent->ConstructL() ; |
|
105 myComponent->SetThisAndThatL() ; |
|
106 CleanupStack::Pop( myComponent ) ; |
|
107 }</codeblock> <p>The return value of the insert and append methods is |
|
108 a <xref href="GUID-2D8BFBA2-79AC-364D-875D-E863CD4A2FE1.dita#GUID-2D8BFBA2-79AC-364D-875D-E863CD4A2FE1/GUID-AD37D86C-F9FA-3514-BFD4-7139A0B8543F"><apiname>CCoeControlArray::TCursor</apiname></xref> object which works as an iterator. |
|
109 It will remain valid when other items are inserted or deleted, or even if |
|
110 the whole array is re-ordered. </p> <p>The insert and append methods leave |
|
111 the component on the Cleanup Stack using a dedicated Cleanup Item that protects |
|
112 the parent's array as well as the component itself. </p> <p>The insert and |
|
113 append methods allow each component to be given an ID. The ID must be unique |
|
114 only within the parent so typically a compound control will have an enum listing |
|
115 each of its children's IDs. <xref href="GUID-2D8BFBA2-79AC-364D-875D-E863CD4A2FE1.dita"><apiname>CCoeControlArray</apiname></xref> , accessed |
|
116 using <codeph>CCoeControl::Components()</codeph>, has a <codeph>ControlById()</codeph> method |
|
117 to retrieve components using their IDs. </p> <p>Components in the array are, |
|
118 by default, owned by the parent and will be deleted automatically when the |
|
119 parent is deleted. The default may be overridden using <codeph>CCoeControlArray::SetControlsOwnedExternally()</codeph>. |
|
120 The setting applies to all of the components. </p> <p>Controls may be removed |
|
121 from the array using one of the <codeph>Remove()</codeph> methods. These do |
|
122 not delete. </p> <codeblock id="GUID-9F9EC327-E7DE-59B8-838B-F5052E3750D3-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-15-10" xml:space="preserve"> |
|
123 class CCoeControlArray |
|
124 ... |
|
125 public: |
|
126 IMPORT_C TInt Remove(const CCoeControl* aControl); |
|
127 IMPORT_C CCoeControl* Remove(TCursor aRemoveAt); |
|
128 IMPORT_C CCoeControl* RemoveById(TInt aControlId); |
|
129 ... |
|
130 </codeblock> <p>Using the component array as described is now the approved |
|
131 method of constructing and managing compound controls. In older versions of |
|
132 Symbian OS a specific method of handling components was not provided and developers |
|
133 were obliged to design their own. Bypassing the component array is still possible. |
|
134 It is necessary to allocate and store the components (typically as member |
|
135 data) and to implement the <codeph>CountComponentControls()</codeph> and <codeph>ComponentControl()</codeph> functions |
|
136 to return the number of components and a specified component to the framework. |
|
137 The new method offers significant advantages when controls are added and removed |
|
138 dynamically or are dependant on run-time data. The new method is also integrated |
|
139 with new layout managers. </p> </section> |
|
140 <section id="GUID-7E144310-9AF4-50F1-AD8A-9F9E05D554D1-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-16"><title>Size, position |
|
141 and layout</title> <p>There are several factors which contribute to a control's |
|
142 size and position. The control itself will require a certain size in order |
|
143 to display itself (and its data) correctly. The control's container will be |
|
144 responsible for positioning the control but is also likely to be responsible |
|
145 for positioning other controls - each of which will have its own requirements. |
|
146 Additionally there are the requirements of the UI's look and feel that must |
|
147 be complied with. </p> <p>Each control is responsible for implementing its |
|
148 own <codeph>Size()</codeph> function. </p> <p>Until Symbian OS version 9.1 |
|
149 it was normal to write layout code for simple and compound controls in the <codeph>SizeChanged()</codeph> function. |
|
150 This is called by the framework, as one might expect, when a control's size |
|
151 (its 'extent') is changed. From 9.1, however, Symbian OS supports the use |
|
152 of the layout manager interface (<xref href="GUID-A622B8C7-60F4-38E8-B102-14883BCBA249.dita"><apiname>MCoeLayoutManager</apiname></xref>) and |
|
153 the <codeph>SizeChanged()</codeph> function is now implemented in the base |
|
154 class. (Note that if a control's position is changed, with no size change, |
|
155 using <codeph>CCoeControl::SetPosition()</codeph> its <codeph>PositionChanged()</codeph> function |
|
156 is called and that default implementation of <codeph>PositionChanged()</codeph> is |
|
157 empty). </p> <codeblock id="GUID-53A10820-550A-52C2-AE80-D556FD77E8FA-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-16-5" xml:space="preserve"> |
|
158 class MCoeLayoutManager |
|
159 ... |
|
160 protected: |
|
161 IMPORT_C MCoeLayoutManager(); |
|
162 |
|
163 public: |
|
164 virtual TBool CanAttach() const = 0; |
|
165 virtual void AttachL(CCoeControl& aCompoundControl) = 0; |
|
166 virtual void Detach(CCoeControl& aCompoundControl) = 0; |
|
167 virtual TSize CalcMinimumSize(const CCoeControl& aCompoundControl) const = 0; |
|
168 virtual void PerformLayout() = 0; |
|
169 virtual TInt CalcTextBaselineOffset(const CCoeControl& aCompoundControl, const TSize& aSize) const = 0; |
|
170 virtual void SetTextBaselineSpacing(TInt aBaselineSpacing) = 0; |
|
171 virtual TInt TextBaselineSpacing() const = 0; |
|
172 virtual void HandleAddedControlL(const CCoeControl& aCompoundControl, const CCoeControl& aAddedControl) = 0; |
|
173 virtual void HandleRemovedControl(const CCoeControl& aCompoundControl, const CCoeControl& aRemovedControl) = 0; |
|
174 virtual TInt HandleControlReplaced(const CCoeControl& aOldControl, const CCoeControl& aNewControl) = 0; |
|
175 ... |
|
176 </codeblock> <p>A layout manager may be attached to a compound control. </p> <codeblock id="GUID-ACCD143C-8618-5606-B749-D7EFE5C83ACD-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-16-7" xml:space="preserve"> |
|
177 class CCoeControl |
|
178 ... |
|
179 protected: |
|
180 IMPORT_C MCoeLayoutManager* LayoutManager() const; |
|
181 IMPORT_C virtual void SetLayoutManagerL(MCoeLayoutManager* aLayoutManager); |
|
182 |
|
183 public: |
|
184 IMPORT_C virtual TBool RequestRelayout(const CCoeControl* aChildCtrl); |
|
185 ...</codeblock> <p>The default implementations of <codeph>MinimumSize()</codeph> and <codeph>SizeChanged()</codeph> now |
|
186 use the layout manager. </p> <codeblock id="GUID-88340A33-A58C-5856-AD47-C2E1637DD45F-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-16-9" xml:space="preserve"> |
|
187 EXPORT_C TSize CCoeControl::MinimumSize() |
|
188 { |
|
189 const MCoeLayoutManager* layoutManager = LayoutManager(); |
|
190 if (layoutManager) |
|
191 return layoutManager->CalcMinimumSize(*this); |
|
192 else |
|
193 return iSize; |
|
194 } |
|
195 |
|
196 EXPORT_C void CCoeControl::SizeChanged() |
|
197 { |
|
198 MCoeLayoutManager* layout = LayoutManager(); |
|
199 if (layout) |
|
200 layout->PerformLayout(); |
|
201 </codeblock> <p>The layout manager is responsible for the size and position |
|
202 of the component controls. In practice it's likely that the UI variant libraries |
|
203 will provide concrete layout managers. Application developers should use these |
|
204 as the basis for control-specific layout managers. </p> </section> |
|
205 <section id="GUID-351911EE-87C7-5D11-8434-BA7FD3741745-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-17"><title> Drawing and |
|
206 refreshing</title> <p>A fundamental requirement of most controls is that they |
|
207 are able to render themselves onto the screen. For most controls the drawing |
|
208 process involves outputting text, painting backgrounds (either plain or from |
|
209 a bitmap), drawing shapes (graphics objects) and drawing component controls. </p> <p>Screen |
|
210 drawing may be initiated by the application itself, following something within |
|
211 the application changing, or by the Window Server, due to something else in |
|
212 the system updating the screen while the application is visible. In both cases |
|
213 the control's <codeph>Draw()</codeph> function will be called automatically |
|
214 by the framework. For compound controls all of the components' <codeph>Draw()</codeph> functions |
|
215 will also be called - unless the component lies completely outside the area |
|
216 that requires redrawing. </p> <p>As a control writer you will probably have |
|
217 to implement a <codeph>Draw()</codeph> function. </p> <p>Here is the signature |
|
218 for <codeph>Draw()</codeph>: </p> <codeblock id="GUID-7A83E8B5-9673-5EBC-B628-674734A07E0F-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-17-6" xml:space="preserve">private: |
|
219 void Draw( const TRect& aRect ) const ;</codeblock> <p>Note that it |
|
220 is private, takes a <codeph>const TRect&</codeph> as a parameter, must |
|
221 not leave and is <codeph>const</codeph>. </p> <p>It should only be called |
|
222 by the framework. Application initiated redraws should be through calls to <codeph>DrawNow()</codeph>, <codeph>DrawDeferred()</codeph> or |
|
223 custom functions for drawing smaller elements. </p> <p>The <codeph>aRect</codeph> parameter |
|
224 is the part of the control that requires drawing (refreshing). </p> <p>The |
|
225 function is <codeph>const</codeph> and non-leaving because it is intended |
|
226 to support the decoupling of drawing actions from application state. </p> </section> |
|
227 <section id="GUID-FF7DB067-24AD-50C3-BF52-952F836609B0-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-18"><title>Drawing backgrounds</title> <p>A |
|
228 control's background is typically determined by the current colour scheme |
|
229 or skin. It may be a plain colour or a bitmap. It's also possible that a control |
|
230 is to appear non-rectangular or transparent in which case some of the background |
|
231 will be the control underneath. Prior to Symbian OS 9.1 controls were required |
|
232 to clear and update their whole area and creating these effects was rather |
|
233 complex. From 9.1 controls are drawn 'backmost first'. </p> <p>Background |
|
234 drawing should be done by a dedicated background drawer - i.e. an object which |
|
235 implements the <xref href="GUID-88936D48-B801-3D9C-8A9D-3498807937CE.dita"><apiname>MCoeControlBackground</apiname></xref> interface. A background |
|
236 can be attached to a <xref href="GUID-B06F99BD-F032-3B87-AB26-5DD6EBE8C160.dita"><apiname>CCoeControl</apiname></xref> using <codeph>SetBackground()</codeph> and |
|
237 is used for that control and all of its children. When a control is drawn |
|
238 the framework looks for the nearest background up the run-time hierarchy and |
|
239 calls <codeph>MCoeControlBackground::Draw()</codeph>. </p> <p>UI variant libraries |
|
240 typically provide backgrounds. They are not owned by the controls to which |
|
241 they are attached. </p> </section> |
|
242 <section id="GUID-54C6A39A-CBD0-52E5-8CD0-76BE22247A54-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19"><title>Drawing text</title> <p>Text |
|
243 must be drawn with the correct color, font, size and direction. As with backgrounds, |
|
244 these are determined at runtime according to UI customizations. This is achieved |
|
245 by means of a Text Drawer. Note the use of the <xref href="GUID-2280260C-3A4F-3C1E-ADF2-3219ED7FE0DE.dita"><apiname>XCoeTextDrawer</apiname></xref> class. |
|
246 This is a smart pointer (note the use of the <codeph>-></codeph> operator |
|
247 to access <xref href="GUID-C8C7B785-B3CF-3488-AEB1-BE0A70F6C1F2.dita"><apiname>CCoeTextDrawerBase</apiname></xref> functions) which ensures that |
|
248 only one text drawer is allocated on the heap at a time. </p> <codeblock id="GUID-511A9CEE-F02C-5667-A334-3C61A2428F0F-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19-3" xml:space="preserve"> |
|
249 XCoeTextDrawer textDrawer( TextDrawer() ); |
|
250 textDrawer->SetAlignment(iAlignment); |
|
251 textDrawer->SetMargins(iMargin); |
|
252 textDrawer->SetLineGapInPixels(iGapBetweenLines); |
|
253 textDrawer.SetClipRect(aRect); // have to use . [dot] operator for SetClipRect() as not CCoeTextDrawerBase function. |
|
254 |
|
255 textDrawer.DrawText(gc, *iTextToDraw, Rect(), *Font()); |
|
256 </codeblock> <p>Text drawers are typically provided by the UI variant library |
|
257 or skin manager. Controls within the run-time hierarchy can set the text drawer |
|
258 for their children by overriding <codeph>GetTextDrawer()</codeph>. </p> <p>Note |
|
259 that the text drawer expects text to be passed as a <xref href="GUID-07D86324-ED54-3FCD-B301-53B7A6E04AE3.dita"><apiname>TBidiText</apiname></xref> rather |
|
260 than a descriptor. Controls should store all display text in <xref href="GUID-07D86324-ED54-3FCD-B301-53B7A6E04AE3.dita"><apiname>TBidiText</apiname></xref> objects. |
|
261 Application writers should consider the implications of right-to-left layouts |
|
262 for languages such as Hebrew and Arabic. </p> <p>A control's <codeph>GetTextDrawer()</codeph> function |
|
263 might look something like this. It checks on the current state of the control |
|
264 (<codeph>IsDimmed()</codeph>) and passes the call on to a skin manager. </p> <codeblock id="GUID-155AD8EC-17DD-5155-BE20-23246A33695C-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19-7" xml:space="preserve"> |
|
265 EXPORT_C void CMyButtonControl::GetTextDrawer(CCoeTextDrawerBase*& aTextDrawer, const CCoeControl* aDrawingControl, TInt /*aKey*/) const |
|
266 { |
|
267 const TInt textDrawerIndex = (IsDimmed() ? EButtonTextDimmed : EButtonText); |
|
268 |
|
269 SkinManager::GetTextDrawer(aTextDrawer, KSkinUidButton, textDrawerIndex, aDrawingControl); |
|
270 } |
|
271 </codeblock> <p>If the control is drawing text on its own graphics (and does |
|
272 not care about the text drawer of its parents) it can just create an <xref href="GUID-2280260C-3A4F-3C1E-ADF2-3219ED7FE0DE.dita"><apiname>XCoeTextDrawer</apiname></xref> object |
|
273 on the stack in its <codeph>Draw()</codeph> method and initiate it from the |
|
274 skin that it is currently using to draw its graphics, using the <codeph>CSkinPatch::TextDrawer()</codeph> method, |
|
275 like this: </p> <codeblock id="GUID-3DFDBF27-8744-5283-AC7B-EC512EEEBB7D-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19-9" xml:space="preserve"> |
|
276 const CSkinPatch& skin = SkinManager::SkinPatch(KSomeSkinUid, KSomeSkinIndex, this); |
|
277 |
|
278 skin.DrawBitmap(gc, Rect(), aRect); |
|
279 XCoeTextDrawer textDrawer( skin.TextDrawer(KSomeSkinUid, ESomeSkinTextDimmed, this) ); |
|
280 |
|
281 const CFont& font = ScreenFont(TCoeFont::NormalFont); |
|
282 textDrawer.DrawText(gc, iText, rect, font); |
|
283 </codeblock> <p>The example above also illustrates how to retrieve the correct |
|
284 font. <xref href="GUID-2A12FE3B-47F2-3016-8161-A971CA506491.dita"><apiname>CFont</apiname></xref> objects must not be stored in control member |
|
285 data as they must change when the control's zoom state changes. Instead, a <xref href="GUID-463C1928-878D-3B06-ABFD-178BE1BAD776.dita"><apiname>TCoeFont</apiname></xref> that |
|
286 represents a font's size (logical or absolute in pixels) and style (plain, |
|
287 bold, italic, subscript, or superscript) should be used. </p> <codeblock id="GUID-864A57DB-BCDA-50B6-B876-9EF62CFB27C6-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19-11" xml:space="preserve"> |
|
288 class TCoeFont |
|
289 ... |
|
290 public: |
|
291 IMPORT_C TCoeFont(TLogicalSize aSize, TInt aStyle, TInt aFlags = ENoFlags); |
|
292 IMPORT_C TCoeFont(TInt aHeightInPixels, TInt aStyle, TInt aFlags = ENoFlags); |
|
293 IMPORT_C TCoeFont(const TCoeFont& aFont); |
|
294 IMPORT_C TCoeFont(); |
|
295 ...</codeblock> <p>By creating a <xref href="GUID-463C1928-878D-3B06-ABFD-178BE1BAD776.dita"><apiname>TCoeFont</apiname></xref> object |
|
296 describing the properties of the desired font, a <xref href="GUID-2A12FE3B-47F2-3016-8161-A971CA506491.dita"><apiname>CFont</apiname></xref> object |
|
297 reference (needed to actually draw the text) can be fetched from the <xref href="GUID-372CDE49-2148-3A21-8EA3-8D4656548C23.dita"><apiname>CCoeFontProvider</apiname></xref>. |
|
298 A font provider can be attached to any <xref href="GUID-B06F99BD-F032-3B87-AB26-5DD6EBE8C160.dita"><apiname>CCoeControl</apiname></xref> and |
|
299 will keep information about the typeface used by that control and all controls |
|
300 below. A default font provider is attached to the <xref href="GUID-12A9389D-363B-3F54-857F-89EE0EDCDF40.dita"><apiname>CCoeEnv</apiname></xref>. </p> <codeblock id="GUID-C46BEB52-33DB-564B-9A64-53763C7CD226-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19-13" xml:space="preserve"> |
|
301 class CCoeControl |
|
302 ... |
|
303 public: |
|
304 IMPORT_C const CCoeFontProvider& FindFontProvider() const; |
|
305 IMPORT_C void SetFontProviderL(const CCoeFontProvider& aFontProvider); |
|
306 ... |
|
307 </codeblock> <p>To get hold of the <xref href="GUID-2A12FE3B-47F2-3016-8161-A971CA506491.dita"><apiname>CFont</apiname></xref> object |
|
308 a <codeph>Draw()</codeph> method can be implemented like this: </p> <codeblock id="GUID-45292362-1E39-59B0-AC7F-14C98592F059-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19-15" xml:space="preserve"> |
|
309 void CMyControl::Draw(const TRect& aRect) |
|
310 { |
|
311 const CCoeFontProvider& fontProvider = FindFontProvider(); |
|
312 const CFont& font = fontProvider.Font(TCoeFont::LegendFont(), AccumulatedZoom()); |
|
313 |
|
314 XCoeTextDrawer textDrawer( TextDrawer() ); |
|
315 textDrawer->SetAlignment(EHCenterVCenter); |
|
316 textDrawer.DrawText(gc, iText, rect, font); |
|
317 } |
|
318 </codeblock> <p>For convenience there’s a <codeph>CCoeControl::ScreenFont()</codeph> method |
|
319 that locates the font provider and calls it with the control’s accumulated |
|
320 zoom: </p> <codeblock id="GUID-5974F900-84B7-5262-8428-88797911F94A-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-19-17" xml:space="preserve"> |
|
321 class CCoeControl |
|
322 ... |
|
323 protected: |
|
324 IMPORT_C const CFont& ScreenFont(const TCoeFont& aFont) const; |
|
325 ... |
|
326 </codeblock> </section> |
|
327 <section id="GUID-D1AED2A8-94AC-54BB-9CEB-C8C3643AFBBD-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-20"><title>Drawing graphics</title><p>Controls |
|
328 draw graphics objects - lines, rectangles, shapes and bitmaps to a <keyword>graphics |
|
329 context</keyword>. The graphics context is provided by the Window Server and |
|
330 represents a group of settings appropriate for the physical device that is |
|
331 ultimately being drawn to. In most cases the device is a screen and a graphics |
|
332 context should be obtained using <codeph>CCoeControl::SystemGc()</codeph>. <codeph>CCoeControl::SystemGc()</codeph> gets |
|
333 the current graphics context from the run-time hierarchy. Controls in the |
|
334 hierarchy may override graphics context settings which will then be passed |
|
335 on to their children. <i>Controls should not get their graphics context directly |
|
336 from CCoeEnv as to do so would bypass the hierarchy</i>. </p> <codeblock id="GUID-DC2B7A84-BA93-5AE0-BABA-9CF6B156A54E-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-20-3" xml:space="preserve"> |
|
337 void CMyControl::Draw( const TRect& aRect ) |
|
338 { |
|
339 CWindowGc& gc = SystemGc() ; // get gc from run time hierarchy |
|
340 TRect rect = TRect( Size() ) ; |
|
341 if ( IsBlanked() ) |
|
342 { |
|
343 // blank out the entire control |
|
344 gc.SetPenStyle( CGraphicsContext::ENullPen ) ; |
|
345 gc.SetBrushStyle( CGraphicsContext::ESolidBrush ) ; |
|
346 TRgb blankColor = BlankingColor() ; |
|
347 gc.SetBrushColor( blankColor ) ; |
|
348 gc.DrawRect( rect ) ; |
|
349 } |
|
350 else |
|
351 { |
|
352 // draw masked bitmap in the centre of the control |
|
353 // The parent will draw the background |
|
354 TInt id = BitMapId() ; |
|
355 |
|
356 TInt x = Size().iWidth - iBitmap[id]->SizeInPixels().iWidth ; |
|
357 TInt y = Size().iHeight - iBitmap[id]->SizeInPixels().iHeight ; |
|
358 |
|
359 TPoint pos = Rect().iTl ; |
|
360 pos.iX = pos.iX + ( x / 2 ) ; |
|
361 pos.iY = pos.iY + ( y / 2 ) ; |
|
362 |
|
363 gc.BitBltMasked( pos, iBitmap[id], rect, iMaskBitmap, ETrue ) ; |
|
364 } |
|
365 }</codeblock> <p>Before a graphics context can be used it must be activated. |
|
366 After use it must be deactivated. Activation and deactivation are done automatically |
|
367 by the framework in <codeph>DrawNow()</codeph>, <codeph>DrawDeferred()</codeph> and <codeph>HandleRedrawEvent()</codeph> but |
|
368 must be done explicitly for any other application initiated drawing by calling <codeph>ActivateGc()</codeph> and <codeph>DeactivateGc()</codeph>. </p> <p>Controls may implement partial drawing to speed up performance. The <codeph>Draw()</codeph> function |
|
369 may be split into sub functions: <codeph>DrawThis()</codeph>, <codeph>DrawThat()</codeph>, <codeph>DrawTheOther()</codeph>. |
|
370 Each of these requires a corresponding <codeph>DrawThisNow()</codeph> and/or <codeph>DrawThisDeferred()</codeph> function. </p> <codeblock id="GUID-EDC0D6F1-61BC-552F-B56D-C32148ADECA3-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-20-6" xml:space="preserve"> |
|
371 CMyControl::Draw() |
|
372 { |
|
373 DrawThis() ; |
|
374 DrawThat() ; |
|
375 DrawTheOther() ; |
|
376 }</codeblock> <codeblock id="GUID-E026186D-7B55-5AB5-9391-8587E3510D6D-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-20-7" xml:space="preserve"> |
|
377 CMyControl::DrawThisNow() |
|
378 { |
|
379 ActivateGc() ; |
|
380 DrawThis() ; |
|
381 DeactivateGc() ; |
|
382 }</codeblock> </section> |
|
383 <section id="GUID-AC723EE4-1482-59C5-9F13-CAE119C7800D-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-21"><title>Handling events </title> <p>The |
|
384 Control Framework supports user interaction in two ways: key-press events |
|
385 and pointer events. Both types of event arrive through the Window Server though |
|
386 they each arrive in a slightly different way. Both are closely related to |
|
387 the concept of 'focus' and the location of the cursor. </p> <p><b>Handling |
|
388 key events </b> </p> <p>Key events are delivered to the AppUi. The Window |
|
389 Server channels them through the root window of its current window group which |
|
390 maps to the AppUi foreground application. The AppUi offers each key event |
|
391 to each of the controls on its control stack in priority order until one of |
|
392 the controls 'consumes' it. </p> <p>To receive key events a control must implement <codeph>CCoeControl::OfferKeyEventL()</codeph>. |
|
393 If it handles the event it must return <codeph>EKeyWasConsumed</codeph>: If |
|
394 it doesn't it must return <codeph>EKeyWasNotConsumed</codeph> so that the |
|
395 next control on the stack receives it. </p> <codeblock id="GUID-448E9DF8-9D63-5BA4-94A7-4FB4B96A6DEA-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-21-6" xml:space="preserve"> |
|
396 TKeyResponse CMyControl::OfferKeyEventL( const TKeyEvent& aKeyEvent, TEventCode aType) |
|
397 { |
|
398 TKeyResponse returnValue = EKeyWasConsumed ; |
|
399 switch( aKeyEvent.iCode ) |
|
400 { |
|
401 case EKeyEnter : |
|
402 // do stuff |
|
403 break ; |
|
404 case EKeyEscape : |
|
405 // do stuff : |
|
406 break ; |
|
407 |
|
408 ... |
|
409 |
|
410 default : |
|
411 // did not recognise key event |
|
412 returnValue = EKeyWasNotConsumed ; |
|
413 break ; |
|
414 } |
|
415 return returnValue ; |
|
416 }</codeblock> <p>The handling of key events will depend on the design |
|
417 and purpose of the control itself. Compound controls might need to keep track |
|
418 of an input <b>focus</b>, or <b>cursor</b>, and to pass key events amongst |
|
419 its lodgers. Input into one lodger might have an effect on another - pressing |
|
420 a navigation key might cause one control to lose the highlight and another |
|
421 to gain it, pressing a number key might cause a text editor to grow which |
|
422 might, in turn, require all of the components below it to shuffle downwards |
|
423 and a scroll bar to become visible (which might also require some controls |
|
424 to be laid out differently). </p> <p><b>Handling pointer events </b> </p> <p>Pointer |
|
425 events are slightly different as the position of the pointer, rather than |
|
426 of the focus, is significant. The Window Server passes a pointer event to |
|
427 the top-most visible window at the point of contact. The Framework uses the |
|
428 functions <codeph>ProcessPointerEventL()</codeph> and <codeph>HandlePointerEventL()</codeph> to |
|
429 work down the hierarchy. The Framework also uses the <xref href="GUID-A2BF9AE8-CF42-3D94-8763-66DB11EDDA46.dita"><apiname>MCoeControlObserver</apiname></xref> and |
|
430 focussing mechanisms to inform the observer of the controls that will be losing |
|
431 and gaining the focus. </p> <p><b>Using the Control Observer Interface</b></p><p>The |
|
432 Control Framework facilitates this type of relationship between a container |
|
433 and its lodgers with the <codeph>MCoeControlObserver</codeph> interface. Typically |
|
434 the container implements the interface and becomes the observer for each lodger |
|
435 that can receive user input (focus). There is only one function in <codeph>MCoeControlObserver</codeph>:</p><codeblock xml:space="preserve">virtual void HandleControlEventL( CCoeControl *aControl, TCoeEvent aEventType ) = 0 ;</codeblock><p>and it is called when an observed control calls</p><codeblock xml:space="preserve">void CCoeControl::ReportEvent( MCoeControlObserver::TCoeEvent aEvent ) ;</codeblock><p>A control can have only one observer (or none) so <codeph>ReportEvent()</codeph> does |
|
436 not need to specify an observer. An observer may observe any number of controls |
|
437 so <codeph>HandleControlEventL()</codeph> takes the observed control as a |
|
438 parameter. The other piece of information passed to the observer is a <codeph>TCoeEvent</codeph>.</p><codeblock xml:space="preserve">enum TCoeEvent |
|
439 { |
|
440 EEventRequestExit, |
|
441 EEventRequestCancel, |
|
442 EEventRequestFocus, |
|
443 EEventPrepareFocusTransition, |
|
444 EEventStateChanged, |
|
445 EEventInteractionRefused |
|
446 };</codeblock><p><codeph>CCoeControl</codeph> also provides <codeph>IsFocused()</codeph>, <codeph>SetFocused()</codeph> and <codeph>IsNonFocussing()</codeph>. Note that Framework does not attempt to ensure exclusivity of focus, nor |
|
447 does it give any visible indication of focus. It is up to the application |
|
448 developer to ensure that only one control has the focus at a time, that the |
|
449 focus is correctly transferred between controls, that only appropriate controls |
|
450 receive the focus and that the focus is visible at all times.</p><codeblock xml:space="preserve">void CContainer::HandleControlEventL(CCoeControl* aControl, TCoeEvent aEventType) |
|
451 { |
|
452 switch (aEventType) |
|
453 { |
|
454 case EEventRequestFocus: |
|
455 { |
|
456 if( !(aControl->IsFocussed()) ) |
|
457 { |
|
458 aControl->SetFocus( ETrue ) ; |
|
459 // remove focus from other controls |
|
460 for ( Tint ii = 0 ; ii < CountComponentControls() ; ii++ ) |
|
461 { |
|
462 CCoeControl* ctl = ComponentControl( ii ) ; |
|
463 if( ( ctl != aControl ) && !( ctl->IsNonFocussing() ) ) |
|
464 { |
|
465 aControl->SetFocus( EFalse ) ; |
|
466 } |
|
467 } |
|
468 } |
|
469 } |
|
470 break; |
|
471 ... |
|
472 } |
|
473 }</codeblock> <p>Control developers may implement <codeph>HandlePointerEventL()</codeph>, |
|
474 which is a virtual function, to perform pointer event functionality. The implementation |
|
475 must, however, call the base class function. </p> <p>Controls may modify their |
|
476 pointer area, possibly if they appear non-rectangular or overlap. To do so |
|
477 requires the addition of a hit test which describes a hit-test region. A hit-test |
|
478 region may cover all or part of one or more controls. A hit for a control |
|
479 is registered in the area covered by both the control and its associated hit |
|
480 test. </p> <p>The diagram below represents three controls, each of which is |
|
481 rectangular but which appears on the screen as a non-rectangular bitmap. Only |
|
482 a hit on a bitmap area should register. This could be achieved by defining |
|
483 a single hit-test region in the shape (and position) of the three blue areas |
|
484 and associating it with each of the controls. The class that implements the |
|
485 hit-test region must implement the MCoeControlHitTest interface. </p> <fig id="GUID-5C856964-8553-543A-B7E2-8D5DCA9BF52C-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-21-22"> |
|
486 <title> Hit-test region example </title> |
|
487 <image href="GUID-CF34E1C9-48E5-5B91-A48E-C68E647116A0_d0e53527_href.png" placement="inline"/> |
|
488 </fig> <codeblock id="GUID-D2AF9CEB-3072-5239-A157-D19852076CEF-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-21-23" xml:space="preserve"> |
|
489 class MCoeControlHitTest |
|
490 ... |
|
491 public: |
|
492 virtual TBool HitRegionContains( const TPoint& aPoint, const CCoeControl& aControl ) const = 0; |
|
493 </codeblock> <p>A hit test is associated with a control using <codeph>CCoeControl::SetHitText()</codeph>. |
|
494 The base class implementation of <codeph>HandlePointerEventL()</codeph> performs |
|
495 the following test: </p> <codeblock id="GUID-35C26C5B-A19A-528A-B5BE-B8177F81B05D-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-21-25" xml:space="preserve"> |
|
496 ... |
|
497 const MCoeControlHitTest* hitTest = ctrl->HitTest() ; |
|
498 if( hitTest ) |
|
499 { |
|
500 if( hitTest->HitRegionContains( aPointerEvent.iPosition, *ctrl ) && |
|
501 ctrl->Rect().Contains( aPointerEvent.iPosition ) )</codeblock> <p>Note |
|
502 that this is performed by a container when deciding which lodger to pass the |
|
503 event onto. This snippet also illustrates how a control can find where (<codeph>iPosition</codeph>) |
|
504 the pointer event actually occurred. </p> <p>Pointer support includes dragging |
|
505 & grabbing. See <xref href="GUID-1FFA0073-3D83-388E-A824-08C31F90CC54.dita"><apiname>TPointerEvent</apiname></xref>. </p> </section> |
|
506 <section id="GUID-FE221E89-1817-5A73-8FBA-212FBC030766-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-22"><title>Implementing |
|
507 the Object Provider (MOP) interface</title> <p>The <xref href="GUID-F32E2F00-B68F-59B2-AABA-181E16354C86-GENID-1-8-1-3-1-1-7-1-7-1-9-1.dita">Object |
|
508 Provider mechanism</xref> exists to allow a control to call a function on |
|
509 another control in the hierarchy for which it does not have a reference. It |
|
510 simply calls <codeph>MopGetObject()</codeph> specifying the interface containing |
|
511 the function. It may also call <codeph>MopGetObjectNoChaining()</codeph> to |
|
512 inquire of a specific object whether it supports the requested interface. </p> <p>Only |
|
513 controls which wish to supply an interface require customisation. In order |
|
514 to be identifiable an interface must have an associated UID. The following |
|
515 code samples show how <xref href="GUID-0DC77F9B-718A-31DF-B076-3C3F83378BF4.dita"><apiname>CEikAlignedControl</apiname></xref> implements and |
|
516 supplies <xref href="GUID-65610725-E873-3F8D-AD60-FC0AD726E0C5.dita"><apiname>MEikAlignedControl</apiname></xref>: </p> <codeblock id="GUID-9926598F-163B-5ACF-B320-66B43D331E1A-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-22-4" xml:space="preserve"> |
|
517 class MEikAlignedControl |
|
518 ... |
|
519 public: |
|
520 DECLARE_TYPE_ID( 0x10A3D51B ) // Symbian allocated UID identifies this interface |
|
521 ... |
|
522 </codeblock> <codeblock id="GUID-1C2FE28B-7A1F-5726-8690-50FBA8672A2C-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-22-5" xml:space="preserve"> |
|
523 class CEikAlignedControl : public CCoeControl, public MEikAlignedControl |
|
524 { |
|
525 ... |
|
526 private: //from CCoeControl |
|
527 IMPORT_C TTypeUid::Ptr MopSupplyObject( TTypeUid aId ) ; |
|
528 ... |
|
529 </codeblock> <codeblock id="GUID-03CD8613-9C1D-556C-94B7-5D9A1C23FF83-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-22-6" xml:space="preserve"> |
|
530 EXPORT_C TTypeUid::Ptr CEikAlignedControl::MopSupplyObject( TTypeUid aId ) |
|
531 { |
|
532 if( aId.iUid == MEikAlignedControl::ETypeId ) |
|
533 return aId.MakePtr( static_cast<MEikAlignedControl*>( this ) ) ; |
|
534 |
|
535 return CCoeControl::MopSupplyObject( aId ) ; // must call base class! |
|
536 } |
|
537 </codeblock> <p>To get an interface from the object provider framework the |
|
538 caller must use a pointer to the interface. </p> <codeblock id="GUID-CB641ADD-5AFE-5D72-A909-CD3F0C3BAA9A-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-22-8" xml:space="preserve"> |
|
539 ... |
|
540 MEikAlignedControl* alignedControl = NULL ; |
|
541 MyControl->MopGetObject( alignedControl ) ; |
|
542 if ( alignedControl ) |
|
543 { |
|
544 ... // etc.</codeblock> <p>To get an interface from a specific object |
|
545 the caller may use the no-chaining function call. </p> <codeblock id="GUID-AEB5EB12-6289-5DCB-BF07-71B8F7A67E33-GENID-1-8-1-6-1-1-4-1-6-1-9-1-2-22-10" xml:space="preserve"> |
|
546 ... |
|
547 MEikAlignedControl* alignedControl = NULL ; |
|
548 aControl->MopGetObjectNoChaining( alignedControl ) ; |
|
549 if ( alignedControl ) |
|
550 { |
|
551 ... // etc.</codeblock> </section> |
|
552 </conbody></concept> |