Mosaic: Customizing
We've already created some menu items to set different color schemes and game levels. Now we will make them work.
10.1 - Menu items theory
When you click on a menu item, a WM_COMMAND message is sent to the window that owns them. Because this message is also sent by buttons and other controls, a destinction is needed between menus and other controls. wNotifyCode is 0 if the message is from a menu (wNotifyCode is the high word of wParam). The low word of wParam is the menu item ID, these are defined in the resource (and in your include file).
10.2 - A few new definitions
In mosaic.inc:
MEDIUM equ 80
HARD equ 200
In your .data?:
The EASY, MEDIUM and HARD constants are used to identify a game level. Their values are actually the number of shuffles when a new game is started. Difficulty is a variable that holds the current difficulty (one of the constants).
10.3 - WM_COMMAND handler
(note: the ProcessMenuItems is not optimized at all, code is repeated many times and all this could be done more efficient, but this way it's easier to understand)
In WndProc:
mov eax, wParam
shr ax, 16
.IF ax==0 ; menu notification
invoke ProcessMenuItems, hWnd, wParam
.ENDIF
In .code:
ProcessMenuItems proc hWnd:DWORD, wParam:DWORD
mov eax, wParam
;-------------------------------------------------------------------------------
; Difficuly
;-------------------------------------------------------------------------------
.IF ax==MI_MEDIUM
invoke CheckMenuItem, hMenu, MI_EASY, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_HARD, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_MEDIUM, MF_CHECKED
mov Difficulty, MEDIUM
.ELSEIF ax==MI_EASY
invoke CheckMenuItem, hMenu, MI_EASY, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_HARD, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_MEDIUM, MF_UNCHECKED
mov Difficulty, EASY
.ELSEIF ax==MI_HARD
invoke CheckMenuItem, hMenu, MI_EASY, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_HARD, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_MEDIUM, MF_UNCHECKED
mov Difficulty, HARD
;-------------------------------------------------------------------------------
; Image type
;-------------------------------------------------------------------------------
.ELSEIF ax==MI_USEFILE
invoke CheckMenuItem, hMenu, MI_USEFILE, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_USENUMBERS, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_USESTANDARD, MF_UNCHECKED
invoke SendMessage, hToolbar, TB_CHECKBUTTON, MI_USEFILE, TRUE
;--- yet to do ---
.ELSEIF ax==MI_USENUMBERS
invoke CheckMenuItem, hMenu, MI_USEFILE, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_USENUMBERS, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_USESTANDARD, MF_UNCHECKED
invoke SendMessage, hToolbar, TB_CHECKBUTTON, MI_USENUMBERS, TRUE
invoke SetBitmap, hWnd, IMAGETYPE_NUMBERS
invoke InvalidateRect, hWnd, NULL, FALSE
.ELSEIF ax==MI_USESTANDARD
invoke CheckMenuItem, hMenu, MI_USEFILE, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_USENUMBERS, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_USESTANDARD, MF_CHECKED
invoke SendMessage, hToolbar, TB_CHECKBUTTON, MI_USESTANDARD, TRUE
invoke SetBitmap, hWnd, IMAGETYPE_STANDARD
invoke InvalidateRect, hWnd, NULL, FALSE
;-------------------------------------------------------------------------------
; Color Scheme
;-------------------------------------------------------------------------------
.ELSEIF ax==MI_COLORBLUE
invoke SetColors, eax
invoke CheckMenuItem, hMenu, MI_COLORRED, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORBLUE, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_COLORGREEN, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORUGLY, MF_UNCHECKED
invoke SetBitmap, hWnd, CurImageType
invoke InvalidateRect, hWnd, NULL, FALSE
.ELSEIF ax==MI_COLORRED
invoke SetColors, eax
invoke CheckMenuItem, hMenu, MI_COLORRED, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_COLORBLUE, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORGREEN, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORUGLY, MF_UNCHECKED
invoke SetBitmap, hWnd, CurImageType
invoke InvalidateRect, hWnd, NULL, FALSE
.ELSEIF ax==MI_COLORGREEN
invoke SetColors, eax
invoke CheckMenuItem, hMenu, MI_COLORRED, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORBLUE, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORGREEN, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_COLORUGLY, MF_UNCHECKED
invoke SetBitmap, hWnd, CurImageType
invoke InvalidateRect, hWnd, NULL, FALSE
.ELSEIF ax==MI_COLORUGLY
invoke SetColors, eax
invoke CheckMenuItem, hMenu, MI_COLORRED, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORBLUE, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORGREEN, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_COLORUGLY, MF_CHECKED
invoke SetBitmap, hWnd, CurImageType
invoke InvalidateRect, hWnd, NULL, FALSE
;-------------------------------------------------------------------------------
; New game
;-------------------------------------------------------------------------------
.ELSEIF ax==MI_NEWGAME
invoke NewGame, hWnd
;-------------------------------------------------------------------------------
; Open Bitmap
;-------------------------------------------------------------------------------
.ELSEIF ax==MI_OPENBITMAP
; --- yet to do ---
.ENDIF
ret
ProcessMenuItems endp
The handler in WndProc checks if the message is a menu item notification, and if it is, it's ID and the main window handle is passed to ProcessMenuItems. Because we gave the toolbarbuttons the same IDs as the menu items, a click on a toolbarbutton will be handled the same way as a menu item. ProcessMenuItem compares the ID to all the menu ID constants and handles them seperately.
The code above is quite big but quite simple. We will take the difficulty menu items as example:
; Difficuly
;-------------------------------------------------------------------------------
.IF ax==MI_MEDIUM
invoke CheckMenuItem, hMenu, MI_EASY, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_HARD, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_MEDIUM, MF_CHECKED
mov Difficulty, MEDIUM
.ELSEIF ax==MI_EASY
invoke CheckMenuItem, hMenu, MI_EASY, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_HARD, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_MEDIUM, MF_UNCHECKED
mov Difficulty, EASY
.ELSEIF ax==MI_HARD
invoke CheckMenuItem, hMenu, MI_EASY, MF_UNCHECKED
invoke CheckMenuItem, hMenu, MI_HARD, MF_CHECKED
invoke CheckMenuItem, hMenu, MI_MEDIUM, MF_UNCHECKED
mov Difficulty, HARD
If we choose medium difficulty (MI_MEDIUM), we first check that menu item (because we can choose between 3 menu items and the selected item is checked) with CheckMenuItem. MF_CHECKED as 3rd parameter will check a menu item, MF_UNCHECKED will uncheck a menu item. CheckMenuItem also needs the handle to the menu (hMenu) and the ID of the menu item (MI_EASY, MI_HARD, MI_MEDIUM). As you can see, the medium item is checked, the others unchecked. It works the same for ther easy and hard menu items. The most important thing, actually setting the difficulty is done by the last line in each handler, one of the constants EASY, MEDIUM and HARD is put in the Difficulty variable.
The handler for the different image types works the same way, but an extra line is included to check the right button on the toolbar (one of the three buttons in the button group that select the image type). SetBitmap is called to set the new image type (though only the numbers image type is implemented right now). InvalidateRect is used to refresh the window so it will draw the image with the new colors.
The color scheme handlers use a new function, SetColors. This function is declared below. SetColors takes the menu ID of one of the color scheme menu items as a parameter, and then uses this ID to set the color variables. SetBitmap is called to refresh the image bitmap (in ImageDC) to make it use the new colors. InvalidateRect is used to refresh the window so it will draw the image with the new colors.
MI_NEWGAME will cause a call to NewGame to start a new game. MI_OPENBITMAP will be used to open a bitmap for a user defined image type, but this is not implemented yet.
10.4 - SetColors
SetColors proc uses ebx cType:DWORD
invoke DeleteObject, hBackgroundColor
invoke DeleteObject, hTileColor
; ebx = background
; edx = text
; eax = tile
mov eax, cType
.IF ax==MI_COLORRED
mov eax, 00000A0h
mov edx, 00000FFh
mov ebx, 05050E0h
.ELSEIF ax==MI_COLORBLUE
mov eax, 0FF8080h
mov edx, 0800000h
mov ebx, 0FF8000h
.ELSEIF ax==MI_COLORGREEN
mov eax, 000A000h
mov edx, 000FF00h
mov ebx, 050E050h
.ELSEIF ax==MI_COLORUGLY
mov eax, 0FF00FFh
mov edx, 000FF00h
mov ebx, 00000FFh
.ENDIF
mov TextColor, edx
invoke CreateSolidBrush, eax
mov hTileColor, eax
invoke CreateSolidBrush, ebx
mov hBackgroundColor, eax
ret
SetColors endp
The SetColors function sets the color variables (TextColor, hTileColor and hBackgroundColor) according to the menu item we clicked on. First the two old brushes are deleted with DeleteObject (TextColor does not have to be deleted because it isn't a brush but just a colorvalue). Then according to the menu item ID, eax, edx and ebx are set with color values. Then edx, eax and ebx are used to create the brushes. If we wouldn't use the registers this way, the code would be less inefficient (more code would be needed for all the CreateSolidBrush calls etc.).
10.5 - Initial settings
To set the initial settings, we call ProcessMenuItems manually so it will think a menu item is pressed:
In the WM_CREATE handler of WndProc:
invoke ProcessMenuItems, hWnd, MI_USENUMBERS
invoke ProcessMenuItems, hWnd, MI_COLORBLUE
10.6 - Done
If you run the program, you can test the menu items and you will see that the toolbar and menu items work correctly with each other . The color schemes work fine, but the program can react a little strange sometimes because not all functions are implemented yet.
Current source code is here: mosaic7.zip.
Example of the different color schemes: