|
|
1.1 root 1: Microsoft Foundation Classes Microsoft Corporation
2: Technical Notes
3:
4: #14 : Custom Controls and other topics
5:
6: This note describes the custom control support in MFC, how self
7: drawing controls are supported as well as the interface and use
8: of the CBitmapButton class.
9:
10: Also dynamic subclassing is described, as well as general advice on
11: ownership of CWnd objects v.s. HWNDs.
12:
13: The MFC sample application 'CTRLTEST' illustrates many of these
14: features. Please refer to the source code to that sample
15: (in \C700\MFC\SAMPLES\CTRLTEST) as well as the general
16: README.TXT for the samples (\C700\MFC\SAMPLES\README.TXT).
17:
18: =============================================================================
19: Custom Control Interface
20: ========================
21:
22: Owner Draw Controls/Menus:
23: --------------------------
24:
25: Windows provides support for "owner draw" for controls and menus.
26: These are windows messages sent to a parent window of a control or
27: menu that allow you customize the visual appearance and behaviour
28: of the control or menu.
29:
30: MFC directly supports owner draw with the message map entries:
31: CWnd::OnDrawItem
32: CWnd::OnMeasureItem
33: CWnd::OnCompareItem
34: CWnd::OnDeleteItem
35:
36: You can override these in your CWnd derived class (usually a dialog
37: or main frame window) to implement the owner draw behaviour,
38: just as it is done in the C API.
39:
40: This approach does not lead to reusable code. If you have two
41: similar controls in two different dialogs, you must implement
42: the custom control behavior in two places.
43: The MFC supported self drawing control architecture solves this problem.
44:
45: Self Drawing Controls/Menus:
46: ----------------------------
47:
48: MFC provides a default implementation (in CWnd) for the standard
49: owner draw messages. This default implementation will decode
50: the owner draw parameters, and delegate the owner draw messages
51: to the controls or menu. This is called "self draw" since the
52: drawing (/measuring/comparing) code is in the class of the control
53: or menu, not in the owner window.
54:
55: This allows you to build reusable control classes that display
56: using "owner draw" semantics, only the code for drawing the
57: control is in the control class and not the owner. This is
58: an object-oriented approach to custom control programming.
59:
60: For self draw buttons:
61: CButton:DrawItem(LPDRAWITEMSTRUCT);
62: // draw this button
63:
64: For self draw menus:
65: CMenu:MeasureItem(LPMEASUREITEMSTRUCT);
66: // measure the size of an item in this menu
67: CMenu:DrawItem(LPDRAWITEMSTRUCT);
68: // draw an item in this menu
69:
70: For self draw listboxes:
71: CListBox:MeasureItem(LPMEASUREITEMSTRUCT);
72: // measure the size of an item in this listbox
73: CListBox:DrawItem(LPDRAWITEMSTRUCT);
74: // draw an item in this listbox
75:
76: CListBox:CompareItem(LPCOMPAREITEMSTRUCT);
77: // compare two items in this listbox if LBS_SORT
78: CListBox:DeleteItem(LPDELETEITEMSTRUCT);
79: // delete an item from this listbox
80:
81: For self draw comboboxes:
82: CComboBox:MeasureItem(LPMEASUREITEMSTRUCT);
83: // measure the size of an item in this combobox
84: CComboBox:DrawItem(LPDRAWITEMSTRUCT);
85: // draw an item in this combobox
86:
87: CComboBox:CompareItem(LPCOMPAREITEMSTRUCT);
88: // compare two items in this combobox if CBS_SORT
89: CComboBox:DeleteItem(LPDELETEITEMSTRUCT);
90: // delete an item from this combobox
91:
92:
93: For details on the owner draw structures, DRAWITEMSTRUCT, MEASUREITEMSTRUCT,
94: COMPAREITEMSTRUCT and DELETEITEMSTRUCT please refer to the MFC
95: documentation for CWnd::OnDrawItem, OnMeasureItem, OnCompareItem,
96: and OnDeleteItem respectively.
97:
98: You do not have to examine the 'CtlType' or 'CtlID' fields of these
99: structures (MFC does that for you).
100:
101: For self drawing menus you must override both MeasureItem and DrawItem
102: member functions.
103:
104: For self drawing listboxes and comboboxes you must override MeasureItem
105: and DrawItem. You must specify the OWNERDRAWVARIABLE style in the dialog
106: template (LBS_OWNERDRAWVARIABLE and CBS_OWNERDRAWVARIABLE respectively).
107: The OWNERDRAWFIXED style will not work with self drawing items since
108: the fixed item height is determined before self drawing controls
109: are attached to the listbox (the Win 3.1 member functions
110: CListBox::SetItemHeight and CComboBox::SetItemHeight can be used
111: to get around this limitation).
112:
113: For self drawing listboxes and comboboxes with the SORT style
114: (LBS_SORT and CBS_SORT respectively) you must override the
115: CompareItem member function.
116:
117:
118: Examples of Self Drawing Controls/Menus:
119: ----------------------------------------
120: The CTRLTEST sample application (\c700\mfc\samples\ctrltest) provides
121: samples of an self draw menu (showing colors) and an self draw
122: listbox (also showing colors).
123:
124: The most typical example of a self drawing button is a bitmap
125: button (a button that shows one, two or three bitmap images
126: for the different states). This is so common we have provided
127: a class in MFC that directly provides this functionality.
128: (see CBitmapButton below).
129:
130: =============================================================================
131: CBitmapButton:
132: ==============
133:
134: The CBitmapButton class is derived from CButton and provides a
135: self-draw implementation of a push button. This button uses
136: bitmaps instead of text for the face of the button.
137:
138: Creating a bitmap button:
139: Here are the steps to create a bitmap button and use it in
140: a dialog. This just shows one way of using this class.
141: There are more options available using the CBitmapButton class.
142: 1) Create the bitmaps:
143: Create one, two or three bitmaps using IMAGEDIT.EXE.
144: Normally you should create all three.
145: The first bitmap is for the "up" button state, the second
146: for the "down" button state. The third bitmap is for the
147: "focused" button state which used when the input focus is
148: on the button (usually the same as "up" but with a heavier
149: border). All bitmaps should be the same size, but there
150: is no restriction on that size.
151:
152: For each button, pick an image name for that button
153: (up to 7 characters, eg: "MYIMAGE").
154: Save the bitmaps to three separate files with file
155: names ending in "U", "D" and "F", all with the .BMP
156: extension, for example: MYIMAGEU.BMP, MYIMAGED.BMP,
157: and MYIMAGEF.BMP.
158:
159: 2) Placing a bitmap button in a dialog:
160: Using DLGEDIT to edit your dialog. Add an owner-draw
161: button wherever you want to have a bitmap button
162: (add a pushbutton and set the "Owner-Draw" style).
163: Set the text to the image name (eg: "MYIMAGE"),
164: and define a symbol for that button (eg: IDC_MYIMAGE).
165: The size of the button you draw on the dialog does
166: not matter, since the default CBitmapButton autoload
167: routine will resize it to the exact size of the bitmap.
168:
169: 3) Add the bitmaps to your .RC file:
170: Each of the bitmaps should be included in the RC file
171: with the same name as the file, for example:
172: MYIMAGEU bitmap MYIMAGEU.BMP
173: MYIMAGED bitmap MYIMAGED.BMP
174: MYIMAGEF bitmap MYIMAGEF.BMP
175:
176: Note the choice of "U", "D" and "F" is not arbitrary,
177: the AutoLoad function relies on these particular names.
178:
179: 3) Aliasing the dialog control with the C++ CBitmapButton object:
180: Create a C++ dialog class (derived from CModalDialog usually).
181: For each bitmap button in the dialog, have a CBitmapButton
182: member object (the name of the member is not important).
183: In the OnInitDialog routine for your dialog, call AutoLoad
184: for each bitmap button (passing the control ID for the button
185: and the dialog pointer).
186: The AutoLoad member function will load in the bitmaps (based
187: on the image name we set up earlier). AutoLoad will also
188: attach the dialog control to the C++ CBitmapButton object
189: using SubclassDlgItem (SubclassDlgItem is a very general
190: control/window aliasing mechanism that is described in detail below).
191:
192: class CMyDlg : ...
193: {
194: CBitmapButton m_mybtn;
195: ...
196: };
197:
198: BOOL CMyDlg::OnInitDialog()
199: {
200: VERIFY(m_mybtn.AutoLoad(IDC_MYIMAGE, this));
201: // verify we have loaded the image into the button with
202: // control ID of IDC_MYIMAGE
203: ...
204: }
205:
206:
207: Examples of CBitmapButtons:
208: ---------------------------
209: See CTRLTEST and SPEAKN for examples of bitmap buttons.
210:
211: Member functions of CBitmapButton:
212: ----------------------------------
213:
214: CBitmapButton two constructors are provided, one with no
215: parameters that does nothing, and another
216: with 3 parameters that will load the
217: bitmaps for you.
218: LoadBitmaps loads the bitmaps from the named resource
219: The first bitmap is required, the other
220: two are optional.
221: SizeToContent resizes the button to the size of the first
222: bitmap
223:
224: AutoLoad does everything:
225: * loads in the bitmaps based on the text
226: of the button + suffices "U", "D" and "F"
227: * resizes the button to content
228: * dynamically subclasses the button object
229:
230: =============================================================================
231: Dynamic Subclassing:
232: ====================
233:
234: Subclassing is the Windows term for replacing the WndProc of a
235: window with a different WndProc, and calling the old WndProc
236: for default (super class) functionality.
237:
238: This should not be confused with C++ class derivation (C++ terminology
239: uses the words "base" and "derived" while the Windows object model
240: uses "super" and "sub"). C++ derivation with MFC and Windows subclassing
241: are very similar in functionality - except for the fact C++ does
242: not support a feature similar to dynamic subclassing.
243:
244: The CWnd class provides the connection between a C++ object (derived
245: from CWnd) and a Windows window object (aka an HWND).
246:
247: There are three common ways these are related:
248: * CWnd creates the HWND. The behaviour can be modified in a derived class.
249: This is a case of "class derivation" and is done by creating
250: a class derived from CWnd and created with calls to 'Create'.
251: * CWnd gets attached to an existing HWND. The behaviour of the
252: existing window is not modified.
253: This is a case of "delegation" and is made possible by
254: calling 'Attach' to alias an existing HWND to a CWnd C++
255: object.
256: * CWnd gets attached to an existing HWND and you can modify
257: the behaviour in a derived class.
258: This is called "dynamic subclassing" since we are changing
259: the behaviour (and hence the class) of a Windows object at runtime.
260:
261: This last case is done with the member functions:
262: CWnd::SubclassWindow and SubclassDlgItem.
263:
264: Both routines attach a CWnd object to an existing Windows HWND.
265: SubclassWindow takes the HWND directly, and SubclassDlgItem is
266: a helper that takes a control ID and the parent window (usually
267: a dialog). SubclassDlgItem is designed for attaching C++ objects
268: to dialog controls created from a dialog template.
269:
270: Please refer to the CTRLTEST example for several examples of
271: when to use SubclassWindow and SubclassDlgItem.
272:
273: =============================================================================
274: Cleanup: CWnd::PostNCDestroy:
275: =============================
276:
277: The following is an important topic. If you follow the guidelines
278: set out below, you will have very few problems with cleanup problems
279: (either in forgetting to delete/free C++ memory, forgetting to
280: free up system resources like HWNDs, or freeing objects too many times).
281:
282: There are typically four kinds of CWnd derived objects:
283: * child windows / controls (derived from CWnd)
284: * main frame windows (MDI and SDI included, derived from CFrameWnd)
285: * modeless dialogs (derived from CDialog)
286: * modal dialogs (derived from CModalDialog)
287:
288:
289: Here are the recommended ownership rules:
290: * child windows / controls should be embedded as members in
291: their parent window / dialog class. They will get automatically
292: cleaned up when the parent window / dialog object gets
293: destroyed.
294: * main frame windows should be allocated with 'new'.
295: The standard application startup will do this, eg:
296: m_pMainWnd = new CMyMainWindow;
297: * related to the above: main frame windows should not be
298: embedded as members in other classes/objects.
299: * main frame windows should not be deleted with the 'delete'
300: operator, but use DeleteWindow instead.
301: * modeless dialogs usually follow the same rules as main frame
302: windows - but you must override PostNcDestroy yourself
303: to call "delete this".
304: * modal dialogs should be embedded on the frame (i.e. auto
305: variables) and get cleaned up when the routine using
306: the dialog returns.
307:
308: When destroying a Windows window, the last windows message sent to
309: the window is 'WM_NCDESTROY'. The default CWnd handler for that
310: message (CWnd::OnNcDestroy) will detach the HWND from the C++
311: object and call the virtual function 'PostNcDestroy'.
312:
313: The default PostNcDestroy implementation does nothing.
314: The CFrameWnd implementation will delete the C++ object.
315:
316: When to override PostNcDestroy?:
317: * if you have a CFrameWnd derived class embedded in another
318: class or statically allocated.
319: * if you have a modeless dialog you want to automatically
320: cleanup.
321:
322: For example, if you wish to have a frame window class that can
323: be allocated on the stack frame, as a static member, or embedded in
324: another object, you would derive your own class as
325: usual. You would, in addition, override the virtual member function
326: PostNcDestroy as follows:
327:
328: class CMyFrameWnd : public CFrameWnd
329: // Frame window class that can be allocated on the stack or
330: // in static memory.
331: {
332: // standard function overrides, constructors, message handlers
333:
334: protected:
335: virtual void PostNcDestroy();
336: };
337:
338: void CMyFrameWnd::PostNcDestroy()
339: {
340: // do nothing
341: }
342:
343: The owner of the object is responsible for calling DestroyWindow() and
344: making sure the object is properly cleaned up. This is demonstrated
345: in the CTRLTEST sample application.
346:
347: =============================================================================
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.