May 01, 2024, 10:57:11 AM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


Handling MDI menus

Started by Ionic Wind Support Team, December 20, 2008, 03:09:05 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Ionic Wind Support Team

A user asked me recently if it was possible to have different menus for each MDI child window type in his program.  You can't use BEGINMENU on a child window since child windows can't have menus.  Or can they?

Windows does have the facility to sort of do this, and adapting it to Emergence's MDI Frame/Child windows is relatively easy if you know a few undocumented secrets.  What Windows allows is changing the main menu based on the active MDI child window.

Whenever you create a menu using BEGINMENU/ENDMENU the handle to the created menu is stored in a temporary variable named __menu_temp before being set as the menubar of the window. That handle can be saved off for later.

The other thing we need to know is how Emergence handles the "Window" menu of an MDI frame.  Or at least the menu ID's it uses so we can duplicate this menu for use with the child windows.

CONST EB_WM_CASCADE = 0x8005
CONST EB_WM_TILE = EB_WM_CASCADE+1
CONST EB_WM_ARRANGE = EB_WM_TILE+1

Finally we need to know what messages to use and where.  Whenever an MDI child window is activated/deactivated Windows will send a WM_MDIACTIVATE message to the child windows handler. This message contains the handle to the window being activated.  We respond to this message by sending the MDIClient window an WM_MDISETMENU message with two menu handles, the handle to the menu bar, and the handle to the "Window" submenu.  This allows the OS to add the list of child windows to the end of the "Window" submenu.

Here is an example program, adapted from the mdidemo.eba sample.  Save it as mdidemo2.eba in your Projects directory and give it a build, as a windows target of course.  It uses the same bitmap image for the toolbar.


$include "windows.inc"
'an example of how MDI windows can be used with EBASIC
'define the frame window and a maximum of 20 child windows
'requires Emergence BASIC 1.0 or greater
'Compile as a WINDOWS target

DEF frame,neww[ 20 ]:WINDOW
DEF run,wndcnt,x:INT
DEF left,top,width,height,panes[4]:INT
DEF stattext:STRING

UINT ghMainMenu,ghMainMenuWindow,ghChildMenu,ghChildMenuWindow
CONST EB_WM_CASCADE = 0x8005
CONST EB_WM_TILE = EB_WM_CASCADE+1
CONST EB_WM_ARRANGE = EB_WM_TILE+1

'open the frame
OPENWINDOW frame,0,0,640,480,@MDIFRAME|@MINBOX|@MAXBOX|@SIZE|@MAXIMIZED,0,"MDI Demo",&wndproc

'create the menu used for child windows
'borrow the frame to do it.
BEGINMENU  frame
MENUTITLE "&File"
MENUITEM "&New",0,1
MENUITEM "&Quit",0,2
MENUTITLE "&Edit"
MENUITEM "Cut",0,4000
MENUITEM "Copy",0,4001
MENUITEM "Paste",0,4002
Menutitle "Window"
MenuItem "&Cascade",0,EB_WM_CASCADE
MenuItem "&Tile",0,EB_WM_TILE
MenuItem "&Arrange Icons",0,EB_WM_ARRANGE
ENDMENU
ghChildMenu = __menu_temp
ghChildMenuWindow = _GetSubMenu(ghChildMenu,2)
'remove the menu from the frame so that the next BEGINMENU doesn't delete it.
_SetMenu(frame.hwnd,NULL)
'create the frame menu
'we create it last since it will be the initial menu shown.
BEGINMENU frame
MENUTITLE "&File"
MENUITEM "&New",0,1
MENUITEM "&Quit",0,2
Menutitle "Window"
MenuItem "&Cascade",0,EB_WM_CASCADE
MenuItem "&Tile",0,EB_WM_TILE
MenuItem "&Arrange Icons",0,EB_WM_ARRANGE
ENDMENU
ghMainMenu = __menu_temp
ghMainMenuWindow = _GetSubMenu(ghMainMenu,1)

'add a status bar
CONTROL frame,@STATUS,"Status",0,0,0,0,0,2
'get the windows client size and set up 4 panes for the status window
GETCLIENTSIZE frame,left,top,width,height
panes = width - 120,width - 80,width - 40,-1
CONTROLCMD frame,2,@SWSETPANES,4,panes
'set the initial pane text
'stattext = USING("& #### ####","Client size",width,height)
CONTROLCMD frame,2,@SWSETPANETEXT,0,stattext
CONTROLCMD frame,2,@SWSETPANETEXT,1,"Pane 2"
CONTROLCMD frame,2,@SWSETPANETEXT,2,"Pane 3"
CONTROLCMD frame,2,@SWSETPANETEXT,3,"Pane 4"
AddAccelerator frame,@FCONTROL|@FVIRTKEY,ASC("N"),1


REM load a toolbar from a bitmap file
DEF hbitmap:UINT
DEF tbArray[10]:INT
tbArray = 2,3,4,0,5,6,7,0,8,9
hbitmap = LOADIMAGE(GETSTARTPATH + "toolbar.bmp",@IMGBITMAP | @IMGMAPCOLORS)
IF LOADTOOLBAR(frame,hbitmap,98,tbArray,10,@TBTOP | @TBFROMHANDLE)
CONTROLCMD frame,98,@TBSETLABELS,"New|Open|Save|Cut|Copy|Paste|Print|Help||"
CONTROLCMD frame,98,@TBRESIZE
ENDIF


run = 1
wndcnt = 0
'process messages until someone closes us
WAITUNTIL run = 0
CLOSEWINDOW frame
DELETEIMAGE hbitmap,@IMGBITMAP
'since the child menus are not attached to the child windows we have to destroy it.
_DestroyMenu(ghChildMenu)
END

'our window subroutine for the frame and child windows
SUB wndproc
SELECT @CLASS
CASE @IDCLOSEWINDOW
run = 0
CASE @IDCONTROL
SELECT @CONTROLID
CASE 2
OpenNewWindow()
ENDSELECT
CASE @IDMENUPICK
SELECT @MENUNUM
CASE 1
OpenNewWindow()
CASE 2
run = 0
CASE 4002
MessageBox frame,"Paste!","Menu"
ENDSELECT
case @IDSIZE
'resize the status window and update the panes
'check to make sure the status window exists
'it may not have been added to the window yet.
if CONTROLEXISTS(frame,2)
'Tell the status window we are sizing
CONTROLCMD frame,2,@SWRESIZE

'get the client size of the window and
'display it in the status bar
'calculate the right edges of the other panes.
GETCLIENTSIZE frame,left,top,width,height
panes = width - 120,width - 80,width - 40,-1
CONTROLCMD frame,2,@SWSETPANES,4,panes
'stattext = USING("& #### ####","Client size",width,height)
CONTROLCMD frame,2,@SWSETPANETEXT,0,stattext
endif
'resize the toolbar
IF CONTROLEXISTS(frame,98)
CONTROLCMD frame,98,@TBRESIZE
ENDIF
ENDSELECT
RETURN
ENDSUB

SUB OpenNewWindow
wndcnt = -1
FOR x=0 TO 19
IF neww[x] = 0
wndcnt = x
x=19
ENDIF
NEXT x
IF wndcnt > -1
name$ = "Untitled"+LTRIM$(STR$(wndcnt+1))
OPENWINDOW neww[wndcnt],@USEDEFAULT,0,0,0,@SIZE|@MINBOX|@MAXBOX,frame,name$,&childwndproc
ENDIF
RETURN
ENDSUB

SUB childwndproc
SELECT @CLASS
CASE @IDCLOSEWINDOW
CLOSEWINDOW #<WINDOW>@HITWINDOW
CASE WM_MDIACTIVATE
'switch to the child menu if we are activated.
'The WM_MDISETMENU message requires two handles, the handle to the menu bar, and the handle to the "Window" submenu
'you send the menu to the MDIClient window, which is the hClient member of the WINDOW variable.
if @lparam = #<WINDOW>@HITWINDOW.hwnd
SendMessage frame.hClient,WM_MDISETMENU,ghChildMenu,ghChildMenuWindow
else
SendMessage frame.hClient,WM_MDISETMENU,ghMainMenu,ghMainMenuWindow
endif
_DrawMenuBar(frame.hwnd)
ENDSELECT
ENDSUB


The example uses the "windows.inc" include file that is included with Emergence.  If you are using Sapero's include files you'll need to remove the underscores on some of the API calls.

Child menu messages are still sent to your frames handler, since the menu is really the frames menu.   You could forward the menu messages not belonging to the frame to the child windows handler or just deal with them in the frames handler as I did with the Paste menu item above.  If you want to forward it to the active window you could do this in the frames handler for @IDMENUPICK:


CASE @IDMENUPICK
SELECT @MENUNUM
CASE 1
OpenNewWindow()
CASE 2
run = 0
default
hChild = SendMessage(frame.hClient,WM_MDIGETACTIVE,0,0)
if hChild
pointer pWnd = _GetWindowLong(hChild,0)
if pWnd then SendMessage(#<WINDOW>pWnd,@IDMENUPICK,@LPARAM,@WPARAM)
endif
ENDSELECT


The address of the WINDOW variable used to create a window is stored in the extra window memory available to all windows.  GetWindowLong retrieves this address so we can get a pointer to the active MDI child window.

Have fun!
Paul.
Ionic Wind Support Team