[CODE]
'************************************************************************
'* odrawmenu *
'* by bernard ertl *
'* july 11, 2002 *
'* revamped july 12 - changed from drawstate to drawtext *
'* - changed sizing handlers for min dialog width *
'* and maximizing *
'* - added static array to track menu item data *
'* - streamlined tracking of focus rects *
'* modified july 13 - changed wm_initdialog to eliminate reliance on*
'* getmenuiteminfo & setmenuiteminfo *
'* - changed wm_measure handler to calculate tabbed*
'* submenu items correctly *
'* - adjusted height for separators *
'* modified july 14 - added wm_nclbuttondown handler *
'* - eliminated need for static focusrect & wm_move*
'* repainting *
'* - corrected spacing of top level menu items *
'* modified july 15 - added support for nested sub-menus *
'* - overhauled painting & drawing to better *
'* conform to standard menu behavior *
'* - ensured proper key-navigation for separators *
'* modified july 16 - included option to maintain focus highlighting*
'* when dialog is inactive *
'* - corrected code for grayed/disabled items *
'* modified july 17 - grayed top menu bar when dialog is inactive *
'* - added support for %mf_checked *
'* - added support for auto-adjusting to the *
'* color display mode *
'* modified july 18 - added default checkmark if user does not *
'* specify an icon *
'* - shows icons for disabled/grayed items as *
'* monochrome now *
'* - corrected display for top level menu items *
'* that are disabled/grayed *
'* modified july 21 - added os versioning support. menu conforms to*
'* standard behavior for the current os. *
'* ** win95 tested ok *
'* ** win98 tested ok (default) *
'* ** winxp added, not fully tested *
'* *
'* credits: *
'* much of this code has been adapted from borje hagsten's & semen *
'* matusovski's code found here: *
'* http://www.powerbasic.com/support/pb...ead.php?t=4361 *
'* *
'* also, code has been adapted from borje hagsten's excellent drawmenu *
'* code. *
'* *
'* original skeleton code designed with pbforms 1.0. pb forms *
'* metastatements have been removed since i had to do some editing *
'* to the pb forms blocks and it can not be reloaded correctly. *
'* *
'* thanks to aleksander hjalmar for testing and suggesting improvements.*
'* *
'* thanks to scott turchin for the windows os version function. i *
'* modified it to suit my tastes... *
'* *
'* description: *
'* this code shows how to completely customize a menu including the top *
'* level menu bar. you can set colors and icons for all menu items. *
'* *
'* restrictions: *
'* - code requires that top level menubar items have ids in a range *
'* greater than all sublevel menu items *
'* - code assumes that all top level menubar items have sub-menus (no *
'* separators). separators are ok in sub-menus. *
'* - code assumes that all top level menubar items are unchecked *
'* - code restricts minimum sizing of dialog width to the size of the *
'* menu bar because the background coloring of the menu bar does not *
'* work correctly at all times when the toolbar is multiple lines in *
'* height. *
'************************************************************************
#compile exe
#dim all
'--------------------------------------------------------------------------------
' ** includes **
'--------------------------------------------------------------------------------
#if not %def(%winapi)
#include "win32api.inc"
#endif
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** constants **
'--------------------------------------------------------------------------------
%idd_dialog1 = 101
%idr_menu1 = 102
%idm_file_open = 1001
%idr_accelerator1 = 103
%idm_file_save = 1002
%idm_file_exit = 1003
%idm_edit_cut = 1004
%idm_edit_copy = 1005
%idm_edit_paste = 1006
%idm_help_helptopics = 1007
%idm_help_about = 1008
%idm_edit_sub_cool = 1009
%idm_edit_sub_dare = 1010
'these constants help define the menu structure
%odm_base_menubar_item_id = 9000 'this is the id threshold for top level menubar items
%odm_base_submenu_item_id = 2000 'this is the id threshold for any (sub-)menu items that contain sub-menus
%odm_max_string = 40 'number of bytes for asciiz menu strings
%odm_accel_key_spacing = 10 'seems to work good
%odm_icon_in_menu_width = 16 '16 for icon
%odm_margin_spacing = 4
%odm_text_offset_from_icon = 2 'add spacing between sub-menu item's text and icon
'these constants enumerate the different possible windows os platforms
%winunknown = 0
%win95 = 1
%win98 = 2
%winme = 3
%winnt = 4
%win2k = 5
%winxp = 6
%windotnetserver = 7
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** types **
'--------------------------------------------------------------------------------
'note: hico is the default icon image to display. if you want to display checked/unchecked states,
' hico = unchecked state icon (could be null for no image)
' hcheckedico = checked state icon (can be the same as hico too)
'note: wid **must** be the first item in the type.
type menuitemtype
wid as dword 'menu item's control id
ztxt as asciiz * %odm_max_string
hico as dword 'default icon image
hcheckedico as dword 'checked icon image
accelwidth as long 'holds textextent of any accelerator key text
end type
type colorset
bkface as long 'color for raised button face
bkfacesunken as long 'color for depressed button face
bkhighlight as long 'color for sub-menu highlighted items
bkhighlightsunken as long 'color for depressed face on sub-menu higlighted items
bkfocushighlight as long 'color for focus highlighting top level menu bar
bkfocusselect as long 'color for selected top level menu bar item
txdefault as long 'default color for text
txgrayed as long 'color for grayed text
txhighlighted as long 'color for highlighted text
end type
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** globals **
'--------------------------------------------------------------------------------
global getcolorfor() as colorset
global currentdisplaymode as long
global osversion as long
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** declarations **
'--------------------------------------------------------------------------------
declare function attachmenu1(byval hdlg as dword) as dword
declare function assignaccel(taccel as accelapi, byval wkey as word, byval wcmd _
as word, byval byfvirt as byte) as long
declare function attachaccelerator1(byval hdlg as dword) as dword
declare callback function showdialog1proc()
declare function showdialog1(byval hparent as dword) as long
declare function getwindowsversion() as long
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** pbmain **
'--------------------------------------------------------------------------------
function pbmain()
local dm as devmode
local osvo as osversioninfo
dim getcolorfor( 1)
'get current os version
'note: you can force a behavior style by explicitly assigning an equate to osversion
osversion = getwindowsversion() '%winxp
'set colors for 256 color mode
getcolorfor(0).bkface = rgb( 128, 128, 0)
getcolorfor(0).bkfacesunken = rgb( 128, 128, 0) '%yellow does not make good 3d effect
getcolorfor(0).bkhighlight = rgb( 128, 0, 0)
getcolorfor(0).bkhighlightsunken = rgb( 128, 0, 0) '%red does not make good 3d effect
select case as long osversion
case %winxp
getcolorfor(0).bkfocushighlight = getcolorfor(0).bkhighlight
getcolorfor(0).bkfocusselect = getcolorfor(0).bkfacesunken
case else
getcolorfor(0).bkfocushighlight = getcolorfor(0).bkhighlight
getcolorfor(0).bkfocusselect = getcolorfor(0).bkhighlight
end select
getcolorfor(0).txdefault = getsyscolor( %color_menutext)
getcolorfor(0).txgrayed = &h00505050 'dark gray
getcolorfor(0).txhighlighted = getsyscolor( %color_highlighttext) 'to match the sub-menu arrow graphic
'set colors for 16 bit/high color or better
getcolorfor(1).bkface = rgb( 200, 200, 152)
getcolorfor(1).bkfacesunken = rgb( 220, 220, 188)
getcolorfor(1).bkhighlight = rgb( 173, 173, 101)
getcolorfor(1).bkhighlightsunken = rgb( 187, 187, 128)
select case as long osversion
case %winxp
getcolorfor(1).bkfocushighlight = rgb(159, 220, 133)
getcolorfor(1).bkfocusselect = getcolorfor(1).bkfacesunken
case else
getcolorfor(1).bkfocushighlight = getcolorfor(1).bkhighlight
getcolorfor(1).bkfocusselect = getcolorfor(1).bkhighlight
end select
getcolorfor(1).txdefault = getsyscolor( %color_menutext)
getcolorfor(1).txgrayed = getsyscolor( %color_graytext)
getcolorfor(1).txhighlighted = getsyscolor( %color_highlighttext) 'to match the sub-menu arrow graphic
'get current display mode
dm.dmsize = len( devmode)
enumdisplaysettings byval 0, %enum_current_settings, dm
currentdisplaymode = iif&( dm.dmbitsperpel > 8, 1, 0)
showdialog1 %hwnd_desktop
end function
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** macros **
'--------------------------------------------------------------------------------
'the following macros were added for coding clarity in the showdialog1proc function
macro mhighlightmenuitem
getwindowrect cbhndl, trect
trect.nbottom = focusrect.nbottom - trect.ntop
trect.ntop = focusrect.ntop - trect.ntop
trect.nright = focusrect.nright - trect.nleft
trect.nleft = focusrect.nleft - trect.nleft
hdc = getwindowdc( cbhndl)
'draw
select case as long osversion
case %winxp
'calculate a darker shade of the focus color for the frame
i = getcolorfor( currentdisplaymode).bkfocushighlight
!xor edx, edx 'clear edx
!mov eax, i
!shr eax, 18 'divide blue value by 4
!mov dl, al
!shl edx, 16
!mov eax, i
!shr ah, 2 'divide green value by 4
!shr al, 2 'divide blue value by 4
!mov dx, ax
!mov i, edx
hf = selectobject( hdc, createpen( %ps_solid, 0, i))
'draw frame using custom colored pen
movetoex hdc, trect.nleft, trect.ntop, byval 0
lineto hdc, trect.nright-1, trect.ntop
lineto hdc, trect.nright-1, trect.nbottom-1
lineto hdc, trect.nleft, trect.nbottom-1
lineto hdc, trect.nleft, trect.ntop
deleteobject selectobject( hdc, createpen( %ps_solid, 0, getcolorfor( currentdisplaymode).bkfocushighlight))
for i = 1 to ((trect.nbottom - trect.ntop) * (trect.nright - trect.nleft)) \ 2
do
pt.x = rnd( trect.nleft+1, trect.nright-1)
loop until pt.x mod 2
do
pt.y = rnd( trect.ntop+1, trect.nbottom-1)
loop until pt.y mod 2
movetoex hdc, pt.x, pt.y, byval 0
lineto hdc, pt.x+1, pt.y
next
deleteobject selectobject( hdc, hf)
settextcolor hdc, getcolorfor( currentdisplaymode).txdefault
setbkmode hdc, %transparent
strmenu = menuitemdata( offset-1).ztxt
if len( strmenu) <> 0 then
ncm.cbsize = len( nonclientmetrics)
systemparametersinfo %spi_getnonclientmetrics, sizeof( ncm), byval varptr(ncm), 0
if len(ncm.lfmenufont) then
hf = createfontindirect(ncm.lfmenufont)
if hf then hf = selectobject(hdc, hf)
end if
t2rect = trect
'adjust for offset
decr t2rect.ntop
decr t2rect.nbottom
if menuitemdata( offset-1).hico then
t2rect.nleft = t2rect.nleft + %odm_icon_in_menu_width + %odm_margin_spacing\2 - 2 'leave room for icons
else
'when no icon in top level, adjust rect
t2rect.nleft = t2rect.nleft - 2
end if
drawtext hdc, byval strptr(strmenu), len(strmenu), t2rect, %dt_left or %dt_noclip or %dt_singleline or %dt_vcenter or %dt_center
if hf then deleteobject selectobject(hdc, hf)
end if
case %win98
drawedge hdc, trect, %bdr_raisedouter, %bf_rect
case else 'winme, winnt, win2k --- i'm not sure what the standard focus highlighting is for these oses
drawedge hdc, trect, %bdr_raisedouter, %bf_rect
end select
if hicon then drawiconex hdc, trect.nleft + %odm_margin_spacing - 2, trect.ntop + 1, hicon, 16, 16, 0, byval 0, %di_normal
releasedc cbhndl, hdc
end macro
macro mrestoremenuitem
getwindowrect cbhndl, trect
trect.nbottom = focusrect.nbottom - trect.ntop
trect.ntop = focusrect.ntop - trect.ntop
trect.nright = focusrect.nright - trect.nleft
trect.nleft = focusrect.nleft - trect.nleft
hdc = getwindowdc( cbhndl)
'erase
select case as long osversion
case %winxp
fillrect hdc, trect, hcustombrush
settextcolor hdc, getcolorfor( currentdisplaymode).txdefault
setbkmode hdc, %transparent
strmenu = menuitemdata( offset-1).ztxt
if len( strmenu) <> 0 then
ncm.cbsize = len( nonclientmetrics)
systemparametersinfo %spi_getnonclientmetrics, sizeof( ncm), byval varptr(ncm), 0
if len(ncm.lfmenufont) then
hf = createfontindirect(ncm.lfmenufont)
if hf then hf = selectobject(hdc, hf)
end if
t2rect = trect
'adjust for offset
decr t2rect.ntop
decr t2rect.nbottom
if menuitemdata( offset-1).hico then
t2rect.nleft = t2rect.nleft + %odm_icon_in_menu_width + %odm_margin_spacing\2 - 2 'leave room for icons
else
'when no icon in top level, adjust rect
t2rect.nleft = t2rect.nleft - 2
end if
drawtext hdc, byval strptr(strmenu), len(strmenu), t2rect, %dt_left or %dt_noclip or %dt_singleline or %dt_vcenter or %dt_center
if hf then deleteobject selectobject(hdc, hf)
end if
case %win98
framerect hdc, trect, hcustombrush
case else 'winme, winnt, win2k --- i'm not sure what the standard focus highlighting is for these oses
framerect hdc, trect, hcustombrush
end select
'redraw icon in case it overlapped the button frame
if hicon then drawiconex hdc, trect.nleft + %odm_margin_spacing - 2, trect.ntop + 1, hicon, 16, 16, 0, byval 0, %di_normal
releasedc cbhndl, hdc
end macro
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** menus **
'--------------------------------------------------------------------------------
function attachmenu1(byval hdlg as dword) as dword
local hmenu as long
local hpopup1 as long
local hpopup2 as long
local hpopup3 as long
menu new bar to hmenu
menu new popup to hpopup1
menu add popup, hmenu, "super fabulous &file menu", hpopup1, %mf_enabled
menu add string, hpopup1, "&open" + $tab + "ctrl+o", %idm_file_open, _
%mf_enabled
menu add string, hpopup1, "&save" + $tab + "ctrl+s", %idm_file_save, _
%mf_enabled
menu add string, hpopup1, "-", 0, 0
menu add string, hpopup1, "&exit" + $tab + "ctrl+e", %idm_file_exit, _
%mf_enabled
menu new popup to hpopup1
menu add popup, hmenu, "&edit", hpopup1, %mf_enabled
menu add string, hpopup1, "c&ut" + $tab + "ctrl+x", %idm_edit_cut, _
%mf_enabled
menu add string, hpopup1, "©" + $tab + "ctrl+c", %idm_edit_copy, _
%mf_enabled
menu add string, hpopup1, "&paste" + $tab + "ctrl+v", %idm_edit_paste, _
%mf_grayed
menu add string, hpopup1, "-", 0, 0
menu new popup to hpopup2
menu add popup, hpopup1, "&sub-menu test", hpopup2, %mf_enabled
menu add string, hpopup2, "&cool", %idm_edit_sub_cool, _
%mf_enabled
menu add string, hpopup2, "-", 0, 0
menu new popup to hpopup3
menu add popup, hpopup2, "double &nested test", hpopup3, %mf_enabled
menu add string, hpopup3, "&dare to dream" + $tab + "ctrl+d", %idm_edit_sub_dare, _
%mf_enabled
menu new popup to hpopup1
menu add popup, hmenu, "&help", hpopup1, %mf_enabled
menu add string, hpopup1, "&help topics" + $tab + "f1", _
%idm_help_helptopics, %mf_enabled
menu add string, hpopup1, "&about" + $tab + "ctrl+a", %idm_help_about, _
%mf_enabled
menu attach hmenu, hdlg
function = hmenu
end function
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** accelerators **
'
' no code was modified for accelerators. this is straight out of pbforms
'--------------------------------------------------------------------------------
function assignaccel(taccel as accelapi, byval wkey as word, byval wcmd as word, _
byval byfvirt as byte) as long
taccel.fvirt = byfvirt
taccel.key = wkey
taccel.cmd = wcmd
end function
function attachaccelerator1(byval hdlg as dword) as dword
local haccel as dword
local taccel() as accelapi
dim taccel(1 to 9)
assignaccel taccel(1), asc("o"), %idm_file_open, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(2), asc("s"), %idm_file_save, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(3), asc("e"), %idm_file_exit, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(4), asc("x"), %idm_edit_cut, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(5), asc("c"), %idm_edit_copy, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(6), asc("v"), %idm_edit_paste, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(7), %vk_f1, %idm_help_helptopics, %fvirtkey or %fnoinvert
assignaccel taccel(8), asc("a"), %idm_help_about, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(9), asc("d"), %idm_edit_sub_dare, %fvirtkey or %fcontrol or _
%fnoinvert
accel attach hdlg, taccel() to haccel
function = haccel
end function
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** support procs **
'--------------------------------------------------------------------------------
function getwindowsversion() as long
'this function courtesy of scott turchin. i've modified it for this code.
'original posting can be found here:
'http://www.powerbasic.com/support/pbforums/showthread.php?t=2295t].wid = submenuid
incr submenuid
@ptrmid[ offset].ztxt = ztxt
select case as const$ ztxt
case "&sub-menu test"
@ptrmid[ offset].hico = loadicon(0, byval %idi_question)
case "double &nested test"
@ptrmid[ offset].hico = loadicon(0, byval %idi_hand)
case else
'no icons to display
end select
'change to %mft_ownerdraw
modifymenu hsubmenu, i, (lobyt( lowrd( result)) xor %mf_popup) or %mf_byposition or %mf_ownerdraw, @ptrmid[ offset].wid, byval 0
'set max accelerator ket text width for all sub-menu items up to now...
for k = startingsubmenuoffset to offset
@ptrmid[ k].accelwidth = maxacceltxtwidth + %odm_icon_in_menu_width + %odm_margin_spacing
next
'modify the nested sub-menu...
modifysubmenu hdlg, hnestedmenu, ptrmid, offset, submenuid
'start new block...
startingsubmenuoffset = offset + 1
else
if (result and %mf_separator) then
'change %mft_separator to %mft_ownerdraw, maintain id = 0
modifymenu hsubmenu, i, result or %mf_byposition or %mf_ownerdraw, 0, byval 0
else
'change %mft_string to %mft_ownerdraw, add icons, calculate width of accelerator key text
getmenustring hsubmenu, i, ztxt, sizeof( ztxt), %mf_byposition
incr offset
@ptrmid[ offset].wid = getmenuitemid( hsubmenu, i)
@ptrmid[ offset].ztxt = ztxt
'modify the following select case as desired to add icons
'where you want them. you can also load icons from a resource file
select case as long @ptrmid[ offset].wid
case %idm_file_open
@ptrmid[ offset].hico = loadicon(0, byval %idi_exclamation)
'leave out checked icon to test default icon
'uncomment to see custom checked icon
' @ptrmid[ offset].hcheckedico = loadicon(0, byval %idi_hand)
case %idm_edit_cut
@ptrmid[ offset].hico = loadicon(0, byval %idi_exclamation)
case %idm_help_about
@ptrmid[ offset].hico = loadicon(0, byval %idi_application)
case %idm_help_helptopics
@ptrmid[ offset].hico = loadicon(0, byval %idi_question)
case else
'no icons to display
end select
'change %mft_string to %mft_ownerdraw
modifymenu hsubmenu, i, result or %mf_byposition or %mf_ownerdraw, @ptrmid[ offset].wid, byval 0
'determine width of accelerator key text
if instr( ztxt, $tab) then
ztxt = parse$( ztxt, $tab, 2) 'get accelerator key text
hdc = getdc( hdlg)
ncm.cbsize = len( nonclientmetrics)
systemparametersinfo %spi_getnonclientmetrics, sizeof( ncm), byval varptr(ncm), 0
if len(ncm.lfmenufont) then
hf = createfontindirect(ncm.lfmenufont)
if hf then hf = selectobject(hdc, hf)
end if
gettextextentpoint32 hdc, ztxt, len( ztxt), sizea
if sizea.cx > maxacceltxtwidth then maxacceltxtwidth = sizea.cx
if hf then deleteobject selectobject(hdc, hf)
releasedc hdlg, hdc
end if
end if
end if
next
'set max accelerator ket text width for all sub-menu items...
for i = startingsubmenuoffset to offset
@ptrmid[ i].accelwidth = maxacceltxtwidth + %odm_icon_in_menu_width + %odm_margin_spacing
next
end sub
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** callbacks **
'--------------------------------------------------------------------------------
callback function showdialog1proc()
register i as long, k as long
static hcustombrush as long ' custom background color
static hcustomsunkenbrush as long ' custom sunken background color (button depression)
static hcustombkbrush as long ' custom background highlight color
static hcustomsunkenbkbrush as long ' custom sunken background highlight color (button depression)
static hcustomfocushlbrush as long ' custom highlight color for top level menu item
static hcustomfocusselbrush as long ' custom highlight color for selected top level menu item
static rightedge as long ' 'coordinate' of the dialog's right edge
static border as long ' 'coordinate' of the right edge of the last top level menubar item
static focusitem as long ' control id of the top level menubar item that has focus
static htimer as dword ' handle to timer
static dialogisactive as long ' track if dialog is active
static menuitemdata() as menuitemtype 'maintain info for each ownerdrawn menu item
'note: this is necessary because menu items with ftype = %mft_ownerdraw do not
' return the dwtypedata correctly.
'note: if you want to have multiple dialogs active concurrently with ownerdrawn menus,
' they should have unique dialog callback functions. otherwise there will be problems
' with the static variables.
local offset as long 'general purpose local variable
local submenuid as long
local maxacceltxtwidth as long
local startingsubmenuoffset as long
local focusrect as rect ' coordinates of the top level menubar item with focus
local newcolormode as long
local hdc as long 'handles
local hmenu as long
local hsubmenu as long
local hicon as long
local hf as long
local strmenu as string 'strings
local strmenu2 as string
local ztxt as asciiz * %odm_max_string
local trect as rect 'for processing messages
local t2rect as rect
local ptrrect as rect ptr
local sizea as sizel
local ncm as nonclientmetrics
local mminfo as minmaxinfo ptr
local pt as pointapi
local lpmis as measureitemstruct ptr
local lpdis as drawitemstruct ptr
select case cbmsg
case %wm_initdialog
'initialize var
dialogisactive = %true
'get starting dimensions for dialog
getclientrect cbhndl, trect
'save total width
rightedge = trect.nright + 4
hcustombrush = createsolidbrush( getcolorfor( currentdisplaymode).bkface)
hcustomsunkenbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkfacesunken)
hcustombkbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkhighlight)
hcustomsunkenbkbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkhighlightsunken)
hcustomfocushlbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkfocushighlight)
hcustomfocusselbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkfocusselect)
hmenu = getmenu( cbhndl)
'count max# total menu & sub-menu items
i = countitems( hme
'************************************************************************
'* odrawmenu *
'* by bernard ertl *
'* july 11, 2002 *
'* revamped july 12 - changed from drawstate to drawtext *
'* - changed sizing handlers for min dialog width *
'* and maximizing *
'* - added static array to track menu item data *
'* - streamlined tracking of focus rects *
'* modified july 13 - changed wm_initdialog to eliminate reliance on*
'* getmenuiteminfo & setmenuiteminfo *
'* - changed wm_measure handler to calculate tabbed*
'* submenu items correctly *
'* - adjusted height for separators *
'* modified july 14 - added wm_nclbuttondown handler *
'* - eliminated need for static focusrect & wm_move*
'* repainting *
'* - corrected spacing of top level menu items *
'* modified july 15 - added support for nested sub-menus *
'* - overhauled painting & drawing to better *
'* conform to standard menu behavior *
'* - ensured proper key-navigation for separators *
'* modified july 16 - included option to maintain focus highlighting*
'* when dialog is inactive *
'* - corrected code for grayed/disabled items *
'* modified july 17 - grayed top menu bar when dialog is inactive *
'* - added support for %mf_checked *
'* - added support for auto-adjusting to the *
'* color display mode *
'* modified july 18 - added default checkmark if user does not *
'* specify an icon *
'* - shows icons for disabled/grayed items as *
'* monochrome now *
'* - corrected display for top level menu items *
'* that are disabled/grayed *
'* modified july 21 - added os versioning support. menu conforms to*
'* standard behavior for the current os. *
'* ** win95 tested ok *
'* ** win98 tested ok (default) *
'* ** winxp added, not fully tested *
'* *
'* credits: *
'* much of this code has been adapted from borje hagsten's & semen *
'* matusovski's code found here: *
'* http://www.powerbasic.com/support/pb...ead.php?t=4361 *
'* *
'* also, code has been adapted from borje hagsten's excellent drawmenu *
'* code. *
'* *
'* original skeleton code designed with pbforms 1.0. pb forms *
'* metastatements have been removed since i had to do some editing *
'* to the pb forms blocks and it can not be reloaded correctly. *
'* *
'* thanks to aleksander hjalmar for testing and suggesting improvements.*
'* *
'* thanks to scott turchin for the windows os version function. i *
'* modified it to suit my tastes... *
'* *
'* description: *
'* this code shows how to completely customize a menu including the top *
'* level menu bar. you can set colors and icons for all menu items. *
'* *
'* restrictions: *
'* - code requires that top level menubar items have ids in a range *
'* greater than all sublevel menu items *
'* - code assumes that all top level menubar items have sub-menus (no *
'* separators). separators are ok in sub-menus. *
'* - code assumes that all top level menubar items are unchecked *
'* - code restricts minimum sizing of dialog width to the size of the *
'* menu bar because the background coloring of the menu bar does not *
'* work correctly at all times when the toolbar is multiple lines in *
'* height. *
'************************************************************************
#compile exe
#dim all
'--------------------------------------------------------------------------------
' ** includes **
'--------------------------------------------------------------------------------
#if not %def(%winapi)
#include "win32api.inc"
#endif
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** constants **
'--------------------------------------------------------------------------------
%idd_dialog1 = 101
%idr_menu1 = 102
%idm_file_open = 1001
%idr_accelerator1 = 103
%idm_file_save = 1002
%idm_file_exit = 1003
%idm_edit_cut = 1004
%idm_edit_copy = 1005
%idm_edit_paste = 1006
%idm_help_helptopics = 1007
%idm_help_about = 1008
%idm_edit_sub_cool = 1009
%idm_edit_sub_dare = 1010
'these constants help define the menu structure
%odm_base_menubar_item_id = 9000 'this is the id threshold for top level menubar items
%odm_base_submenu_item_id = 2000 'this is the id threshold for any (sub-)menu items that contain sub-menus
%odm_max_string = 40 'number of bytes for asciiz menu strings
%odm_accel_key_spacing = 10 'seems to work good
%odm_icon_in_menu_width = 16 '16 for icon
%odm_margin_spacing = 4
%odm_text_offset_from_icon = 2 'add spacing between sub-menu item's text and icon
'these constants enumerate the different possible windows os platforms
%winunknown = 0
%win95 = 1
%win98 = 2
%winme = 3
%winnt = 4
%win2k = 5
%winxp = 6
%windotnetserver = 7
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** types **
'--------------------------------------------------------------------------------
'note: hico is the default icon image to display. if you want to display checked/unchecked states,
' hico = unchecked state icon (could be null for no image)
' hcheckedico = checked state icon (can be the same as hico too)
'note: wid **must** be the first item in the type.
type menuitemtype
wid as dword 'menu item's control id
ztxt as asciiz * %odm_max_string
hico as dword 'default icon image
hcheckedico as dword 'checked icon image
accelwidth as long 'holds textextent of any accelerator key text
end type
type colorset
bkface as long 'color for raised button face
bkfacesunken as long 'color for depressed button face
bkhighlight as long 'color for sub-menu highlighted items
bkhighlightsunken as long 'color for depressed face on sub-menu higlighted items
bkfocushighlight as long 'color for focus highlighting top level menu bar
bkfocusselect as long 'color for selected top level menu bar item
txdefault as long 'default color for text
txgrayed as long 'color for grayed text
txhighlighted as long 'color for highlighted text
end type
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** globals **
'--------------------------------------------------------------------------------
global getcolorfor() as colorset
global currentdisplaymode as long
global osversion as long
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** declarations **
'--------------------------------------------------------------------------------
declare function attachmenu1(byval hdlg as dword) as dword
declare function assignaccel(taccel as accelapi, byval wkey as word, byval wcmd _
as word, byval byfvirt as byte) as long
declare function attachaccelerator1(byval hdlg as dword) as dword
declare callback function showdialog1proc()
declare function showdialog1(byval hparent as dword) as long
declare function getwindowsversion() as long
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** pbmain **
'--------------------------------------------------------------------------------
function pbmain()
local dm as devmode
local osvo as osversioninfo
dim getcolorfor( 1)
'get current os version
'note: you can force a behavior style by explicitly assigning an equate to osversion
osversion = getwindowsversion() '%winxp
'set colors for 256 color mode
getcolorfor(0).bkface = rgb( 128, 128, 0)
getcolorfor(0).bkfacesunken = rgb( 128, 128, 0) '%yellow does not make good 3d effect
getcolorfor(0).bkhighlight = rgb( 128, 0, 0)
getcolorfor(0).bkhighlightsunken = rgb( 128, 0, 0) '%red does not make good 3d effect
select case as long osversion
case %winxp
getcolorfor(0).bkfocushighlight = getcolorfor(0).bkhighlight
getcolorfor(0).bkfocusselect = getcolorfor(0).bkfacesunken
case else
getcolorfor(0).bkfocushighlight = getcolorfor(0).bkhighlight
getcolorfor(0).bkfocusselect = getcolorfor(0).bkhighlight
end select
getcolorfor(0).txdefault = getsyscolor( %color_menutext)
getcolorfor(0).txgrayed = &h00505050 'dark gray
getcolorfor(0).txhighlighted = getsyscolor( %color_highlighttext) 'to match the sub-menu arrow graphic
'set colors for 16 bit/high color or better
getcolorfor(1).bkface = rgb( 200, 200, 152)
getcolorfor(1).bkfacesunken = rgb( 220, 220, 188)
getcolorfor(1).bkhighlight = rgb( 173, 173, 101)
getcolorfor(1).bkhighlightsunken = rgb( 187, 187, 128)
select case as long osversion
case %winxp
getcolorfor(1).bkfocushighlight = rgb(159, 220, 133)
getcolorfor(1).bkfocusselect = getcolorfor(1).bkfacesunken
case else
getcolorfor(1).bkfocushighlight = getcolorfor(1).bkhighlight
getcolorfor(1).bkfocusselect = getcolorfor(1).bkhighlight
end select
getcolorfor(1).txdefault = getsyscolor( %color_menutext)
getcolorfor(1).txgrayed = getsyscolor( %color_graytext)
getcolorfor(1).txhighlighted = getsyscolor( %color_highlighttext) 'to match the sub-menu arrow graphic
'get current display mode
dm.dmsize = len( devmode)
enumdisplaysettings byval 0, %enum_current_settings, dm
currentdisplaymode = iif&( dm.dmbitsperpel > 8, 1, 0)
showdialog1 %hwnd_desktop
end function
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** macros **
'--------------------------------------------------------------------------------
'the following macros were added for coding clarity in the showdialog1proc function
macro mhighlightmenuitem
getwindowrect cbhndl, trect
trect.nbottom = focusrect.nbottom - trect.ntop
trect.ntop = focusrect.ntop - trect.ntop
trect.nright = focusrect.nright - trect.nleft
trect.nleft = focusrect.nleft - trect.nleft
hdc = getwindowdc( cbhndl)
'draw
select case as long osversion
case %winxp
'calculate a darker shade of the focus color for the frame
i = getcolorfor( currentdisplaymode).bkfocushighlight
!xor edx, edx 'clear edx
!mov eax, i
!shr eax, 18 'divide blue value by 4
!mov dl, al
!shl edx, 16
!mov eax, i
!shr ah, 2 'divide green value by 4
!shr al, 2 'divide blue value by 4
!mov dx, ax
!mov i, edx
hf = selectobject( hdc, createpen( %ps_solid, 0, i))
'draw frame using custom colored pen
movetoex hdc, trect.nleft, trect.ntop, byval 0
lineto hdc, trect.nright-1, trect.ntop
lineto hdc, trect.nright-1, trect.nbottom-1
lineto hdc, trect.nleft, trect.nbottom-1
lineto hdc, trect.nleft, trect.ntop
deleteobject selectobject( hdc, createpen( %ps_solid, 0, getcolorfor( currentdisplaymode).bkfocushighlight))
for i = 1 to ((trect.nbottom - trect.ntop) * (trect.nright - trect.nleft)) \ 2
do
pt.x = rnd( trect.nleft+1, trect.nright-1)
loop until pt.x mod 2
do
pt.y = rnd( trect.ntop+1, trect.nbottom-1)
loop until pt.y mod 2
movetoex hdc, pt.x, pt.y, byval 0
lineto hdc, pt.x+1, pt.y
next
deleteobject selectobject( hdc, hf)
settextcolor hdc, getcolorfor( currentdisplaymode).txdefault
setbkmode hdc, %transparent
strmenu = menuitemdata( offset-1).ztxt
if len( strmenu) <> 0 then
ncm.cbsize = len( nonclientmetrics)
systemparametersinfo %spi_getnonclientmetrics, sizeof( ncm), byval varptr(ncm), 0
if len(ncm.lfmenufont) then
hf = createfontindirect(ncm.lfmenufont)
if hf then hf = selectobject(hdc, hf)
end if
t2rect = trect
'adjust for offset
decr t2rect.ntop
decr t2rect.nbottom
if menuitemdata( offset-1).hico then
t2rect.nleft = t2rect.nleft + %odm_icon_in_menu_width + %odm_margin_spacing\2 - 2 'leave room for icons
else
'when no icon in top level, adjust rect
t2rect.nleft = t2rect.nleft - 2
end if
drawtext hdc, byval strptr(strmenu), len(strmenu), t2rect, %dt_left or %dt_noclip or %dt_singleline or %dt_vcenter or %dt_center
if hf then deleteobject selectobject(hdc, hf)
end if
case %win98
drawedge hdc, trect, %bdr_raisedouter, %bf_rect
case else 'winme, winnt, win2k --- i'm not sure what the standard focus highlighting is for these oses
drawedge hdc, trect, %bdr_raisedouter, %bf_rect
end select
if hicon then drawiconex hdc, trect.nleft + %odm_margin_spacing - 2, trect.ntop + 1, hicon, 16, 16, 0, byval 0, %di_normal
releasedc cbhndl, hdc
end macro
macro mrestoremenuitem
getwindowrect cbhndl, trect
trect.nbottom = focusrect.nbottom - trect.ntop
trect.ntop = focusrect.ntop - trect.ntop
trect.nright = focusrect.nright - trect.nleft
trect.nleft = focusrect.nleft - trect.nleft
hdc = getwindowdc( cbhndl)
'erase
select case as long osversion
case %winxp
fillrect hdc, trect, hcustombrush
settextcolor hdc, getcolorfor( currentdisplaymode).txdefault
setbkmode hdc, %transparent
strmenu = menuitemdata( offset-1).ztxt
if len( strmenu) <> 0 then
ncm.cbsize = len( nonclientmetrics)
systemparametersinfo %spi_getnonclientmetrics, sizeof( ncm), byval varptr(ncm), 0
if len(ncm.lfmenufont) then
hf = createfontindirect(ncm.lfmenufont)
if hf then hf = selectobject(hdc, hf)
end if
t2rect = trect
'adjust for offset
decr t2rect.ntop
decr t2rect.nbottom
if menuitemdata( offset-1).hico then
t2rect.nleft = t2rect.nleft + %odm_icon_in_menu_width + %odm_margin_spacing\2 - 2 'leave room for icons
else
'when no icon in top level, adjust rect
t2rect.nleft = t2rect.nleft - 2
end if
drawtext hdc, byval strptr(strmenu), len(strmenu), t2rect, %dt_left or %dt_noclip or %dt_singleline or %dt_vcenter or %dt_center
if hf then deleteobject selectobject(hdc, hf)
end if
case %win98
framerect hdc, trect, hcustombrush
case else 'winme, winnt, win2k --- i'm not sure what the standard focus highlighting is for these oses
framerect hdc, trect, hcustombrush
end select
'redraw icon in case it overlapped the button frame
if hicon then drawiconex hdc, trect.nleft + %odm_margin_spacing - 2, trect.ntop + 1, hicon, 16, 16, 0, byval 0, %di_normal
releasedc cbhndl, hdc
end macro
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** menus **
'--------------------------------------------------------------------------------
function attachmenu1(byval hdlg as dword) as dword
local hmenu as long
local hpopup1 as long
local hpopup2 as long
local hpopup3 as long
menu new bar to hmenu
menu new popup to hpopup1
menu add popup, hmenu, "super fabulous &file menu", hpopup1, %mf_enabled
menu add string, hpopup1, "&open" + $tab + "ctrl+o", %idm_file_open, _
%mf_enabled
menu add string, hpopup1, "&save" + $tab + "ctrl+s", %idm_file_save, _
%mf_enabled
menu add string, hpopup1, "-", 0, 0
menu add string, hpopup1, "&exit" + $tab + "ctrl+e", %idm_file_exit, _
%mf_enabled
menu new popup to hpopup1
menu add popup, hmenu, "&edit", hpopup1, %mf_enabled
menu add string, hpopup1, "c&ut" + $tab + "ctrl+x", %idm_edit_cut, _
%mf_enabled
menu add string, hpopup1, "©" + $tab + "ctrl+c", %idm_edit_copy, _
%mf_enabled
menu add string, hpopup1, "&paste" + $tab + "ctrl+v", %idm_edit_paste, _
%mf_grayed
menu add string, hpopup1, "-", 0, 0
menu new popup to hpopup2
menu add popup, hpopup1, "&sub-menu test", hpopup2, %mf_enabled
menu add string, hpopup2, "&cool", %idm_edit_sub_cool, _
%mf_enabled
menu add string, hpopup2, "-", 0, 0
menu new popup to hpopup3
menu add popup, hpopup2, "double &nested test", hpopup3, %mf_enabled
menu add string, hpopup3, "&dare to dream" + $tab + "ctrl+d", %idm_edit_sub_dare, _
%mf_enabled
menu new popup to hpopup1
menu add popup, hmenu, "&help", hpopup1, %mf_enabled
menu add string, hpopup1, "&help topics" + $tab + "f1", _
%idm_help_helptopics, %mf_enabled
menu add string, hpopup1, "&about" + $tab + "ctrl+a", %idm_help_about, _
%mf_enabled
menu attach hmenu, hdlg
function = hmenu
end function
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** accelerators **
'
' no code was modified for accelerators. this is straight out of pbforms
'--------------------------------------------------------------------------------
function assignaccel(taccel as accelapi, byval wkey as word, byval wcmd as word, _
byval byfvirt as byte) as long
taccel.fvirt = byfvirt
taccel.key = wkey
taccel.cmd = wcmd
end function
function attachaccelerator1(byval hdlg as dword) as dword
local haccel as dword
local taccel() as accelapi
dim taccel(1 to 9)
assignaccel taccel(1), asc("o"), %idm_file_open, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(2), asc("s"), %idm_file_save, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(3), asc("e"), %idm_file_exit, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(4), asc("x"), %idm_edit_cut, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(5), asc("c"), %idm_edit_copy, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(6), asc("v"), %idm_edit_paste, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(7), %vk_f1, %idm_help_helptopics, %fvirtkey or %fnoinvert
assignaccel taccel(8), asc("a"), %idm_help_about, %fvirtkey or %fcontrol or _
%fnoinvert
assignaccel taccel(9), asc("d"), %idm_edit_sub_dare, %fvirtkey or %fcontrol or _
%fnoinvert
accel attach hdlg, taccel() to haccel
function = haccel
end function
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** support procs **
'--------------------------------------------------------------------------------
function getwindowsversion() as long
'this function courtesy of scott turchin. i've modified it for this code.
'original posting can be found here:
'http://www.powerbasic.com/support/pbforums/showthread.php?t=2295t].wid = submenuid
incr submenuid
@ptrmid[ offset].ztxt = ztxt
select case as const$ ztxt
case "&sub-menu test"
@ptrmid[ offset].hico = loadicon(0, byval %idi_question)
case "double &nested test"
@ptrmid[ offset].hico = loadicon(0, byval %idi_hand)
case else
'no icons to display
end select
'change to %mft_ownerdraw
modifymenu hsubmenu, i, (lobyt( lowrd( result)) xor %mf_popup) or %mf_byposition or %mf_ownerdraw, @ptrmid[ offset].wid, byval 0
'set max accelerator ket text width for all sub-menu items up to now...
for k = startingsubmenuoffset to offset
@ptrmid[ k].accelwidth = maxacceltxtwidth + %odm_icon_in_menu_width + %odm_margin_spacing
next
'modify the nested sub-menu...
modifysubmenu hdlg, hnestedmenu, ptrmid, offset, submenuid
'start new block...
startingsubmenuoffset = offset + 1
else
if (result and %mf_separator) then
'change %mft_separator to %mft_ownerdraw, maintain id = 0
modifymenu hsubmenu, i, result or %mf_byposition or %mf_ownerdraw, 0, byval 0
else
'change %mft_string to %mft_ownerdraw, add icons, calculate width of accelerator key text
getmenustring hsubmenu, i, ztxt, sizeof( ztxt), %mf_byposition
incr offset
@ptrmid[ offset].wid = getmenuitemid( hsubmenu, i)
@ptrmid[ offset].ztxt = ztxt
'modify the following select case as desired to add icons
'where you want them. you can also load icons from a resource file
select case as long @ptrmid[ offset].wid
case %idm_file_open
@ptrmid[ offset].hico = loadicon(0, byval %idi_exclamation)
'leave out checked icon to test default icon
'uncomment to see custom checked icon
' @ptrmid[ offset].hcheckedico = loadicon(0, byval %idi_hand)
case %idm_edit_cut
@ptrmid[ offset].hico = loadicon(0, byval %idi_exclamation)
case %idm_help_about
@ptrmid[ offset].hico = loadicon(0, byval %idi_application)
case %idm_help_helptopics
@ptrmid[ offset].hico = loadicon(0, byval %idi_question)
case else
'no icons to display
end select
'change %mft_string to %mft_ownerdraw
modifymenu hsubmenu, i, result or %mf_byposition or %mf_ownerdraw, @ptrmid[ offset].wid, byval 0
'determine width of accelerator key text
if instr( ztxt, $tab) then
ztxt = parse$( ztxt, $tab, 2) 'get accelerator key text
hdc = getdc( hdlg)
ncm.cbsize = len( nonclientmetrics)
systemparametersinfo %spi_getnonclientmetrics, sizeof( ncm), byval varptr(ncm), 0
if len(ncm.lfmenufont) then
hf = createfontindirect(ncm.lfmenufont)
if hf then hf = selectobject(hdc, hf)
end if
gettextextentpoint32 hdc, ztxt, len( ztxt), sizea
if sizea.cx > maxacceltxtwidth then maxacceltxtwidth = sizea.cx
if hf then deleteobject selectobject(hdc, hf)
releasedc hdlg, hdc
end if
end if
end if
next
'set max accelerator ket text width for all sub-menu items...
for i = startingsubmenuoffset to offset
@ptrmid[ i].accelwidth = maxacceltxtwidth + %odm_icon_in_menu_width + %odm_margin_spacing
next
end sub
'--------------------------------------------------------------------------------
'--------------------------------------------------------------------------------
' ** callbacks **
'--------------------------------------------------------------------------------
callback function showdialog1proc()
register i as long, k as long
static hcustombrush as long ' custom background color
static hcustomsunkenbrush as long ' custom sunken background color (button depression)
static hcustombkbrush as long ' custom background highlight color
static hcustomsunkenbkbrush as long ' custom sunken background highlight color (button depression)
static hcustomfocushlbrush as long ' custom highlight color for top level menu item
static hcustomfocusselbrush as long ' custom highlight color for selected top level menu item
static rightedge as long ' 'coordinate' of the dialog's right edge
static border as long ' 'coordinate' of the right edge of the last top level menubar item
static focusitem as long ' control id of the top level menubar item that has focus
static htimer as dword ' handle to timer
static dialogisactive as long ' track if dialog is active
static menuitemdata() as menuitemtype 'maintain info for each ownerdrawn menu item
'note: this is necessary because menu items with ftype = %mft_ownerdraw do not
' return the dwtypedata correctly.
'note: if you want to have multiple dialogs active concurrently with ownerdrawn menus,
' they should have unique dialog callback functions. otherwise there will be problems
' with the static variables.
local offset as long 'general purpose local variable
local submenuid as long
local maxacceltxtwidth as long
local startingsubmenuoffset as long
local focusrect as rect ' coordinates of the top level menubar item with focus
local newcolormode as long
local hdc as long 'handles
local hmenu as long
local hsubmenu as long
local hicon as long
local hf as long
local strmenu as string 'strings
local strmenu2 as string
local ztxt as asciiz * %odm_max_string
local trect as rect 'for processing messages
local t2rect as rect
local ptrrect as rect ptr
local sizea as sizel
local ncm as nonclientmetrics
local mminfo as minmaxinfo ptr
local pt as pointapi
local lpmis as measureitemstruct ptr
local lpdis as drawitemstruct ptr
select case cbmsg
case %wm_initdialog
'initialize var
dialogisactive = %true
'get starting dimensions for dialog
getclientrect cbhndl, trect
'save total width
rightedge = trect.nright + 4
hcustombrush = createsolidbrush( getcolorfor( currentdisplaymode).bkface)
hcustomsunkenbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkfacesunken)
hcustombkbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkhighlight)
hcustomsunkenbkbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkhighlightsunken)
hcustomfocushlbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkfocushighlight)
hcustomfocusselbrush = createsolidbrush( getcolorfor( currentdisplaymode).bkfocusselect)
hmenu = getmenu( cbhndl)
'count max# total menu & sub-menu items
i = countitems( hme
Comment