|
|
1.1 root 1: /**************************************************************************\
2: * subclass.c -- Demonstrate subclassing of standard controls.
3: *
4: * written March 92 by Steve Firebaugh
5: *
6: * This sample allows the user to create an arbitrary number of child
7: * controls on the main window. These controls are subclassed, and the
8: * subclass procedure provides the user a way to move and size the controls.
9: * A menu item switches in and out of "Test Mode." When this is on, the
10: * subclass procedure passes all messages through to the old procedure, and
11: * the controls act just like normal.
12: *
13: * There is a single subclass window procedure for a variety of different
14: * control classes. This is accomplished by storing the old window procedure
15: * for the control in a structure pointed at by the 4 "user extra bytes."
16: * I.e. the GWL_USERDATA contains a pointer to the following structure:
17: *
18: * typedef struct tagExtraBytes{
19: * WNDPROC pfnOldProc;
20: * RECT rect;
21: * int Action;
22: * } ExtraBytes, *PExtraBytes;
23: *
24: * and the old window procedure is stored in the pfnOldProc field.
25: * The rect field is used for drawing the temporary rectangle while the
26: * user is dragging, and the Action field holds the type of action that
27: * is allowed (move, size, ...). Notice that the rect rectangle is in
28: * the coordinate space of the parent window.
29: *
30: * Warning: buttons, edit fields, and list boxes are easy. It is difficult
31: * to extend this to static controls because they do not normally receive
32: * input and thus do not get mousedown messages. It is difficult to extend
33: * this to comboboxes because of the compound structure and the fact that
34: * the comboboxes children controls (edit field & list box) get the mouse
35: * messages first.
36: *
37: \**************************************************************************/
38:
39: #include <windows.h>
40: #include "subclass.h"
41:
42:
43:
44: /**************************************************************************\
45: *
46: * function: WinMain()
47: *
48: * input parameters: c.f. generic sample
49: *
50: \**************************************************************************/
51: int APIENTRY WinMain(HANDLE hInstance, HANDLE hPrevInstance,
52: LPSTR lpCmdLine, int nCmdShow)
53: {
54: MSG msg;
55:
56: UNREFERENCED_PARAMETER( lpCmdLine );
57:
58:
59: /* Check for previous instance. If none, then register class. */
60: if (!hPrevInstance) {
61: WNDCLASS wc;
62:
63: wc.style = NULL;
64: wc.lpfnWndProc = (WNDPROC)MainWndProc;
65: wc.cbClsExtra = 0;
66: wc.cbWndExtra = 0;
67: wc.hInstance = hInstance;
68: wc.hIcon = LoadIcon(hInstance, "subclassIcon");
69: wc.hCursor = LoadCursor(NULL, IDC_ARROW);
70: wc.hbrBackground = GetStockObject(GRAY_BRUSH);
71: wc.lpszMenuName = "scMenu";
72: wc.lpszClassName = "scClass";
73:
74: if (!RegisterClass(&wc)) return (FALSE);
75: } /* class registered o.k. */
76:
77:
78: /* Create the main window. Return false if CreateWindow() fails */
79: hInst = hInstance;
80:
81: hwndMain = CreateWindow(
82: "scClass",
83: "Subclass Window Sample",
84: WS_OVERLAPPEDWINDOW,
85: CW_USEDEFAULT,
86: CW_USEDEFAULT,
87: CW_USEDEFAULT,
88: CW_USEDEFAULT,
89: NULL,
90: NULL,
91: hInstance,
92: NULL);
93:
94: if (!hwndMain) return (FALSE);
95:
96:
97: ShowWindow(hwndMain, nCmdShow);
98:
99: /* Loop getting messages and dispatching them. */
100: while (GetMessage(&msg,NULL, NULL, NULL)) {
101: TranslateMessage(&msg);
102: DispatchMessage(&msg);
103: }
104:
105: /* Return the value from PostQuitMessage */
106: return (msg.wParam);
107: }
108:
109:
110:
111:
112:
113:
114:
115:
116: /**************************************************************************\
117: *
118: * function: MainWndProc()
119: *
120: * input parameters: normal window procedure parameters.
121: * global variables:
122: * hInst - global instance handle. used in create window.
123: * fTestMode - global boolean, set by menu, if true ignore subclass actions.
124: *
125: * The main window actually does very little. Just respond to menu commands.
126: \**************************************************************************/
127: LONG APIENTRY MainWndProc(HWND hwnd, UINT message, UINT wParam, LONG lParam)
128: {
129: HWND hwndChild;
130:
131: switch (message) {
132:
133: /**********************************************************************\
134: * WM_DESTROY
135: *
136: * Post quit message.
137: \**********************************************************************/
138: case WM_DESTROY:
139: PostQuitMessage(0);
140: break;
141:
142:
143: /**********************************************************************\
144: * WM_COMMAND
145: *
146: * Switch on the different menu items. Create a different class of
147: * window for most of them. Switch on and off the fTestMode global
148: * variable on IDM_TEST.
149: \**********************************************************************/
150: case WM_COMMAND: {
151: switch (LOWORD (wParam)) {
152: case IDM_BUTTON:
153: hwndChild = CreateWindow(
154: "BUTTON", "button",
155: WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS,
156: 50,10, 60, 32,
157: hwnd, NULL, hInst, NULL);
158:
159: SubclassWindow (hwndChild, SubclassWndProc);
160: break;
161:
162: case IDM_EDIT:
163: hwndChild = CreateWindow(
164: "EDIT", "edit",
165: WS_VISIBLE | WS_CHILD | WS_BORDER | WS_CLIPSIBLINGS,
166: 50,50, 60, 20,
167: hwnd, NULL, hInst, NULL);
168:
169: SubclassWindow (hwndChild, SubclassWndProc);
170: break;
171:
172: case IDM_LIST:
173: hwndChild = CreateWindow(
174: "LISTBOX", NULL,
175: WS_VISIBLE | WS_CHILD | WS_BORDER | WS_CLIPSIBLINGS,
176: 50,90, 60, 50,
177: hwnd, NULL, hInst, NULL);
178:
179: SubclassWindow (hwndChild, SubclassWndProc);
180: break;
181:
182: case IDM_TEST:
183: fTestMode = ToggleMenu (GetMenu (hwnd), IDM_TEST);
184: break;
185:
186: } /* end switch */
187: } break; /* end WM_COMMAND */
188:
189: default:
190: return (DefWindowProc(hwnd, message, wParam, lParam));
191: }
192: return (NULL);
193: }
194:
195:
196:
197:
198: /**************************************************************************\
199: *
200: * function: SubclassWndProc
201: *
202: * input parameters: normal window procedure parameters.
203: * global variables:
204: * hwndMain - parent window of the control.
205: * fTestMode - global boolean, set by menu, if true ignore subclass actions.
206: \**************************************************************************/
207: LONG APIENTRY SubclassWndProc(HWND hwnd, UINT message, UINT wParam, LONG lParam)
208: {
209: PExtraBytes peb;
210:
211:
212: /* get the pointer to the extra bytes structure out of the user data. */
213: peb = (PExtraBytes)GetWindowLong (hwnd, GWL_USERDATA);
214:
215: /* if running in test mode, just return the old procedure. */
216: if (fTestMode)
217: return (CallWindowProc ((peb->pfnOldProc), hwnd, message, wParam, lParam));
218:
219:
220: switch (message) {
221:
222:
223: /**********************************************************************\
224: * WM_DESTROY
225: *
226: * Free up the ExtraBytes structure which was allocated at subclass time.
227: \**********************************************************************/
228: case WM_DESTROY:
229: LocalUnlock (LocalHandle ((LPSTR)peb));
230: break;
231:
232:
233: /**********************************************************************\
234: * WM_SETCURSOR
235: *
236: * Set the mouse pointer conditional on the Action which would be taken
237: * if the user presses mouse down. This is set in WM_MOUSEMOVE.
238: \**********************************************************************/
239: case WM_SETCURSOR:
240: switch (peb->Action) {
241: case ACTIONNONE :SetCursor (LoadCursor (NULL, IDC_ARROW)); break;
242: case ACTIONMOVE :SetCursor (LoadCursor (NULL, IDC_SIZEALL)); break;
243: case ACTIONSIZEX :SetCursor (LoadCursor (NULL, IDC_SIZEWE)); break;
244: case ACTIONSIZEY :SetCursor (LoadCursor (NULL, IDC_SIZENS)); break;
245: case ACTIONSIZEXY:SetCursor (LoadCursor (NULL, IDC_SIZENWSE)); break;
246: }
247: return (0);
248:
249:
250:
251: /**********************************************************************\
252: * WM_LBUTTONDOWN
253: *
254: * The user is beginning a drag operation. Fill in an initial window pos
255: * in the rect field, figure out which corner the cursor should be set
256: * to, translate that point to screen coordinates, and finally capture
257: * the mouse.
258: \**********************************************************************/
259: case WM_LBUTTONDOWN: {
260: POINT mouse;
261:
262: QueryWindowPos (hwnd, &peb->rect);
263:
264: switch (peb->Action) {
265: case ACTIONMOVE :
266: mouse.x = peb->rect.left;
267: mouse.y = peb->rect.top ;
268: break;
269:
270: case ACTIONSIZEX :
271: mouse.x = peb->rect.right;
272: mouse.y = peb->rect.top ;
273: break;
274:
275: case ACTIONSIZEY :
276: mouse.x = peb->rect.left;
277: mouse.y = peb->rect.bottom;
278: break;
279:
280: case ACTIONSIZEXY:
281: mouse.x = peb->rect.right;
282: mouse.y = peb->rect.bottom;
283: break;
284: }
285:
286: /* SetCursorPos() works based on screen position, and mouse is
287: * currently relative to hwndMain.
288: */
289: ClientToScreen (hwndMain, &mouse);
290: SetCursorPos (mouse.x, mouse.y);
291: SetCapture (hwnd);
292: } return 0;
293:
294:
295: /**********************************************************************\
296: * WM_LBUTTONUP
297: *
298: * Complement of the WM_LBUTTONDOWN message. Release the capture,
299: * and reset the actual window's position.
300: \**********************************************************************/
301: case WM_LBUTTONUP:
302: if (GetCapture() == hwnd) {
303: ReleaseCapture();
304: SetWindowPos (hwnd, NULL, peb->rect.left, peb->rect.top,
305: (peb->rect.right - peb->rect.left),
306: (peb->rect.bottom - peb->rect.top),
307: SWP_NOZORDER);
308: InvalidateRect (hwnd, NULL, TRUE);
309: }
310: return 0;
311:
312:
313:
314: /**********************************************************************\
315: * WM_MOUSEMOVE
316: *
317: * There are two cases of interest here. If the mouse is captured, then
318: * change the rect field in the extrabyte structure to reflect the new
319: * size/position that the user is selection.
320: *
321: * If the mouse is not captured, then set the Action field dependent on
322: * the quadrant so that the WM_SETCURSOR will display the correct cursor.
323: *
324: \**********************************************************************/
325: case WM_MOUSEMOVE: {
326: POINT mouse;
327: /* the LO/HI-WORD from lParam will be a signed short relative to
328: * the child control window.
329: */
330: mouse.x = (int)(short)LOWORD(lParam);
331: mouse.y = (int)(short)HIWORD(lParam);
332:
333:
334:
335: /* if the mouse is captured, then we are doing direct manipulation */
336: if (GetCapture() == hwnd) {
337:
338: /* translate mouse pos to be client relative, not control relative */
339: ClientToScreen (hwnd, &mouse);
340: ScreenToClient (hwndMain, &mouse);
341:
342: /* erase the old rectangle */
343: PaintRect (hwndMain, &peb->rect);
344:
345: switch (peb->Action) {
346: case ACTIONMOVE : {
347: int width, height;
348:
349: width = peb->rect.right - peb->rect.left;
350: height = peb->rect.bottom - peb->rect.top;
351: peb->rect.left = mouse.x;
352: peb->rect.top = mouse.y;
353: peb->rect.right = peb->rect.left + width;
354: peb->rect.bottom = peb->rect.top + height;
355: }break;
356:
357: case ACTIONSIZEX :
358: peb->rect.right = mouse.x;
359: break;
360:
361: case ACTIONSIZEY :
362: peb->rect.bottom = mouse.y;
363: break;
364:
365: case ACTIONSIZEXY:
366: peb->rect.right = mouse.x;
367: peb->rect.bottom = mouse.y;
368: break;
369: }
370:
371: /* Redraw the new rectangle */
372: PaintRect (hwndMain, &peb->rect);
373: }
374:
375:
376:
377: /* if the mouse is not captured, then set the action for the sake of
378: * the WM_SETCURSOR message. The action is dependent upon which
379: * quadrant of the window the mouse cursor is over.
380: */
381: else {
382: int width2, height2;
383: RECT rect;
384:
385: GetClientRect (hwnd, &rect);
386: width2 = (rect.right - rect.left)/2;
387: height2 = (rect.bottom - rect.top)/2;
388:
389: /* upper left hand corner */
390: if ((mouse.x <= (width2)) && (mouse.y <= (height2))) {
391: peb->Action = ACTIONMOVE;
392:
393: /* lower left hand corner */
394: } else if ((mouse.x <= (width2)) && (mouse.y > (height2))) {
395: peb->Action = ACTIONSIZEY;
396:
397: /* upper right hand corner */
398: } else if ((mouse.x > (width2)) && (mouse.y <= (height2))) {
399: peb->Action = ACTIONSIZEX;
400:
401: /* lower right hand corner */
402: } else if ((mouse.x > (width2)) && (mouse.y > (height2))) {
403: peb->Action = ACTIONSIZEXY;
404: }
405: }
406: } return 0;
407:
408:
409:
410: /**********************************************************************\
411: * for messages that are not handled explicitly here, pass them
412: * back to the old window procedure.
413: \**********************************************************************/
414: default:
415: return (CallWindowProc ((peb->pfnOldProc), hwnd, message, wParam, lParam));
416: break;
417: } /* end switch */
418:
419: return 0;
420: }
421:
422:
423:
424:
425:
426:
427: /**************************************************************************\
428: * function: SubclassWindow
429: *
430: * input parameters:
431: * hwnd - window handle to be subclassed,
432: * SubclassWndProc - the new window procedure.
433: *
434: * Set in a new window procedure for this window. Store the old window
435: * procedure in the first field of the extrabytes structure. This routine
436: * is specific to this program in the use of this particular extrabyte
437: * structure. Note that the pointer in the user bytes needs to be freed
438: * later (in WM_DESTROY).
439: \**************************************************************************/
440: VOID SubclassWindow (HWND hwnd, WNDPROC SubclassWndProc)
441: {
442: PExtraBytes peb;
443:
444: peb = (PExtraBytes)LocalAlloc (LPTR, sizeof (ExtraBytes));
445: peb->pfnOldProc = (WNDPROC) GetWindowLong (hwnd, GWL_WNDPROC);
446:
447: SetWindowLong (hwnd, GWL_USERDATA, (LONG) peb);
448: SetWindowLong (hwnd, GWL_WNDPROC, (LONG) SubclassWndProc);
449: }
450:
451:
452:
453:
454:
455: /**************************************************************************\
456: * function: QueryWindowPos
457: *
458: * input parameters:
459: * hwnd - window handle, prect - pointer to rectangle to hold the answer.
460: *
461: * global variables:
462: * hwndMain - parent window of the control.
463: *
464: * Return the bounding rectangle for the hwnd control relative to hwndMain.
465: \**************************************************************************/
466: VOID QueryWindowPos (HWND hwnd, LPRECT prect)
467: {
468: GetWindowRect (hwnd, prect);
469: ScreenToClient (hwndMain, (LPPOINT)&(prect->left));
470: ScreenToClient (hwndMain, (LPPOINT)&(prect->right));
471: }
472:
473:
474:
475:
476:
477: /**************************************************************************\
478: * function: PaintRect
479: *
480: * input parameters:
481: * hwnd - window handle, prect - pointer to rectangle to be painted.
482: *
483: * Get an HDC and draw the rectangle in R2_NOT mode which if done twice will
484: * leave the screen in its initial state.
485: \**************************************************************************/
486: VOID PaintRect (HWND hwnd, LPRECT prect)
487: {
488: HDC hdc;
489:
490: hdc = GetDC (hwnd);
491: SetROP2(hdc, R2_NOT);
492: SelectObject (hdc, GetStockObject (NULL_BRUSH));
493: Rectangle (hdc, prect->left, prect->top,
494: prect->right, prect->bottom);
495: ReleaseDC (hwnd, hdc);
496: }
497:
498:
499:
500:
501:
502: /**************************************************************************\
503: * function: ToggleMenu
504: *
505: * input parameters:
506: * hmenu - menu handle, iditem - id of the item to check.
507: *
508: * returns: the new checked state.
509: *
510: * Change a menu item to be checked if it is not, or unchecked if it is.
511: \**************************************************************************/
512: DWORD ToggleMenu (HMENU hmenu, UINT iditem)
513: {
514: DWORD state;
515:
516: /* query the checked state.
517: * unfortunately, this turns checked off.
518: */
519: state = CheckMenuItem (hmenu, iditem, MF_BYCOMMAND);
520:
521: /* switch the truth value of oldstate. */
522: state = (state) ? MF_UNCHECKED : MF_CHECKED;
523:
524: /* reset the menu item. */
525: CheckMenuItem (hmenu, iditem, MF_BYCOMMAND | state);
526:
527: return state;
528: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.