Access makes it easy to remove the control box (often called the system menu) and the minimize and maximize buttons when you design forms, but there doesn't seem to be a way to do this at runtime. You have an application for which you'd like to be able to remove these buttons to control how users interact with the application. Is there a way to remove these items and then replace them later?
Removing or replacing these window controls requires changing the style bits for the particular window. Every window maintains a 32-bit value that describes its physical characteristics: for example, its border type and the existence of scrollbars, a system menu, and the minimize and maximize buttons. The values are stored as bit flags, in which the state of a single bit in the 32-bit value indicates the value of some characteristic of the window. In general, you can't change the state of many of these flags without recreating the window; by setting or clearing the bits in the window's style value, however, you can force the system menu and the minimize/maximize buttons to appear or disappear.
Load and run frmSystemItems from 11-01.MDB. This form, shown in Figure 11-1, allows you to add or remove the control menu, the minimize button, and the maximize button from the current form. Select items on the form to make the corresponding items visible, or deselect to remove them. Once you've made your choices, click on the Execute button, and the code will remove or replace the items you've chosen.
To include this functionality in your own applications, follow these steps:
Import the module basControl from 11-01.MDB.
To remove or replace a form's system items, call the acbFormSystemItems subroutine, passing to it the four parameters shown in Table 11-1.
For example, the following statement, called from a button's Click event in a form's module, will show the system menu but will hide the minimize and maximize buttons:
acbFormSystemItems Me, True, False, False
Though Access does provide the ControlBox, MaxButton, and MinButton properties for forms, they're read-only once the form is in use; if you need to alter these properties at runtime, you'll need to use acbFormSystemItems instead of changing the properties directly.
Reference to the current form
True = Show system menu; False = Hide
True = Show maximize button; False = Hide
True = Show minimize button; False = Hide
Old Versus New Use of Window Buttons
The behavior of the control box and minimize and maximize buttons has changed. If you're running Windows 95 or later, using acbFormSystemItems to remove one of the minimize or maximize buttons leaves them both visible but disables the one you've requested to hide. Removing them both with acbFormSystemItems makes them both invisible. Under Windows NT and earlier, these buttons are independent, and using the subroutine to remove one makes it invisible. Under Windows 95 or later, removing the control box also removes the minimize and maximize buttons. Under Windows NT or earlier, these items are independent.
The bulk of the work in controlling these system items takes place in the private HandleStyles function in the basControl module. This function accepts a window handle (the hWnd property of a form) and three True/False values indicating which options you want to see and which you want removed. Like every window, the window you want to alter maintains a 32-bit value, its style value. Within that long integer, each of the 32 positions represents one of the possible styles for the window. If the bit is 1, the style is set on; if it's 0, the style is set off. HandleStyles builds up two long integers, each containing a series of 32 bits. The first, lngStylesOn, contains all 0s, except for the bits representing the styles you want turned on, which contain 1s. The other, lngStylesOff, contains all 1s, except for the bits representing the styles you want turned off, which contain 0s.
Using the AND operator to combine the current window style with lngStylesOff sets each style whose bit contains 0 in lngStylesOff to be 0. Using the OR operator to combine the current window style with lngStylesOn sets each style whose bit contains 1 in lngStylesOn to be 1. For example, suppose the current window style value is this:
10001000 10001010 10101011 01101101
The value in lngStylesOff contains 1s in all positions except the ones you want turned off, which contain 0s. If the value of lngStylesOff is this:
11111111 11111111 11111111 11111011
the result of using the AND operator with the original style and lngStylesOff will be this:
10001000 10001010 10101011 01101001
The value in lngStylesOn contains 0s in all positions except the ones you want turned on, which contain 1s. If the value of lngStylesOn is this:
00000000 00000000 00010000 10000000
the result of using the OR operator with lngStylesOn and the result of ANDing the original style with lngStylesOff will be this:
10001000 10001010 10111011 11101001
This final result will have three changed values: one bit that was 1 is now 0 due to the settings in lngStylesOff, and two bits that were are now 1 due to the settings in lnStylesOn.
To retrieve and replace the window's style information, the code uses the GetWindowLong and SetWindowLong API functions. Given a window handle and a flag (GWL_STYLE) indicating which 32-bit value to retrieve or set, these functions allow you to get the current value, do your work with it, and then set it back. This is the line of code that does all the work:
HandleStyles = SetWindowLong(hWnd, GWL_STYLE, _ (GetWindowLong(hWnd, GWL_STYLE) And lngStylesOff) _ Or lngStylesOn)
It sets the window style to be the value GetWindowLong retrieved, combined with the two style flags the code previously built up based on your choices.
The entire HandleStyles procedure looks like this:
Private Function HandleStyles(ByVal hWnd As Long, blnShowSystemMenu As Boolean, _ blnShowMaxButton As Boolean, blnShowMinButton As Boolean) As Long Dim lngStylesOn As Long Dim lngStylesOff As Long On Error GoTo HandleStylesExit ' Set all bits off. lngStylesOn = 0 ' Set all bits on. lngStylesOff = &HFFFFFFFF ' Turn ON bits to set attribute; turn OFF bits to turn attribute off. If blnShowSystemMenu Then lngStylesOn = lngStylesOn Or WS_SYSMENU Else lngStylesOff = lngStylesOff And Not WS_SYSMENU End If If blnShowMinButton Then lngStylesOn = lngStylesOn Or WS_MINIMIZEBOX Else lngStylesOff = lngStylesOff And Not WS_MINIMIZEBOX End If If blnShowMaxButton Then lngStylesOn = lngStylesOn Or WS_MAXIMIZEBOX Else lngStylesOff = lngStylesOff And Not WS_MAXIMIZEBOX End If ' Set the attributes as necessary. HandleStyles = SetWindowLong(hWnd, GWL_STYLE, _ (acb_apiGetWindowLong(hWnd, GWL_STYLE) And lngStylesOff) _ Or lngStylesOn) ' The 1 in the third parameter tells the window ' to repaint its entire border. Call SendMessage(hwnd, WM_NCPAINT, 1, 0) HandleStylesExit: Exit Function End Function
After the style bits are set, there's still one issue left: you must coerce the window into repainting itself so the changes become visible. Simply changing the styles isn't enough, because they don't become visible until the next time the window repaints its border.
If you resize the form, Access repaints the border, but there's no reasonable programmatic way to do this. To solve the problem, the procedure adds one more line. It calls the SendMessage API, which sends a specific message to any window (this time, it sends a message to the form itself ). The message it sends, a constant named WM_NCPAINT, tells the form to repaint its non-client area (that is, its border):
' The 1 as the third parameter tells the window ' to repaint its entire border. Call acb_SendMessage(hwnd, WM_NCPAINT, 1, 0)