Dear Tim,
Based on the FiveWin framework code, here are solutions to your MDI window management questions:
1. Moving Between Windows - Standard Window Menu
The easiest way is to use the built-in MDI menu functionality. Call oMenu:AddMdi() after defining your menu, which automatically creates a "Window" submenu with standard operations (Cascade, Tile, etc.) and automatically lists all open child windows at the bottom of the menu. 0-cite-0
When you have more than 9 open windows, the framework automatically adds a "More Windows" option that opens a dialog to select from all windows: 0-cite-1
The menu also includes keyboard shortcuts - Ctrl+F6 to switch to the next window and Ctrl+F4 to close the active window: 0-cite-2
2. Using Button Bar to Switch Windows
Yes, you can create buttons on your button bar where each button calls oChild:SetFocus() to activate a specific MDI child window. Here's an example from the codebase: 0-cite-3
The SetFocus() method internally calls the MDI client's ChildActivate() to properly activate the window: 0-cite-4 0-cite-5
You could maintain references to your child windows and create/update button bar buttons dynamically as windows are opened/closed, changing their state or appearance based on which window is active.
3. Adding Icons to the Taskbar
This is not standard MDI behavior. In Windows MDI applications, only the parent TMdiFrame window appears on the taskbar - not the individual child windows. This is by design in the Windows MDI specification. The MDI children exist within the parent frame's client area and don't get their own taskbar entries. 0-cite-6
If you absolutely need separate taskbar icons, you would need to use separate top-level windows instead of MDI child windows, but that would defeat the purpose of using MDI.
4. Additional Window Management Options
The framework provides several window arrangement methods that can be called from your menu or button bar:
- oWnd:Cascade() - Overlapping windows
- oWnd:Tile() / oWnd:Tile(.t.) - Vertical/Horizontal tiling
- oWnd:ArrangeIcons() - Organize minimized windows
- oWnd:IconizeAll() - Minimize all windows
- oWnd:NextWindow() / oWnd:PrevWindow() - Cycle through windows 0-cite-7
Notes:
- For scrolling button bars, the standard TButtonBar class doesn't have built-in scrolling. You would need to implement custom scrolling logic or use a different control approach (like a listbox or browse to show open windows).
- The DlgSelectChild() dialog that appears when you have more than 9 windows provides an XBrowse-based selection interface, which is essentially what you'd need for a scrolling window list.
- Consider using the Window menu approach (oMenu:AddMdi()) as it's the standard way users expect to manage MDI windows and requires no additional code to maintain.
- You can check Len(oWnd:oWndClient:aWnd) at any time to see how many child windows are open, which is useful for enabling/disabling buttons or menu items.
Citations
File: source/classes/menu.prg (L2055-2128)
Code: Select all | Expand
METHOD AddMdi( aColors, cFile, cRes ) CLASS TMenu
local oItem
if !Empty( aColors )
::SetColors( aColors )
endif
if Empty( cFile ) .and. Empty( cRes )
MENUITEM oItem PROMPT FWString( "&Window" )
else
if !Empty( cFile )
MENUITEM oItem PROMPT FWString( "&Window" ) FILE cFile
else
if !Empty( cRes )
MENUITEM oItem PROMPT FWString( "&Window" ) RESOURCE cRes
endif
endif
endif
if ::lColors
MENU ::oMenuChild ;
COLORMENU ::nClrMenu, ::nClrText ;
COLORBMP ::nClrToBa, ::nClrBkBa ; //::nClrToBa, ::nClrBkBa
COLORRIGHT ::nClrToBr, ::nClrBkBr ;
COLORSELECT ::nClrHiLi, ::nClrHiLf, ::nClrHiTx;
COLORSEPARATOR ::nClrHiBr ;
COLORS ;
FONT ::oFont
else
MENU ::oMenuChild
endif
::oMenuChild:l2007 := ::l2007
::oMenuChild:l2010 := ::l2010
::oMenuChild:l2013 := ::l2013
::oMenuChild:l2015 := ::l2015
::oMenuChild:lColors := ::lColors
::oMenuChild:lLinVert := IF( ::l2013, .T., ::lLinVert )
::oMenuChild:lBorder := ::lBorder
MENUITEM FWString( "&Tile Vertical" ) ACTION ( oMenuItem, ::oWnd:Tile() ) ;
MESSAGE FWString( "Vertical arranges the windows as nonoverlapping tiles" ) ;
WHEN ( oMenuItem, IF( !Empty( ::oWnd ), Len( ::oWnd:oWndClient:aWnd ) > 0, .T. ) ) //HSYSBITMAP IF( ::oMenuChild:lColors, 32745, 10 )
MENUITEM FWString( "&Tile Horizontal" ) ACTION ( oMenuItem, ::oWnd:Tile( .t. ) ) ;
MESSAGE FWString( "Horizontal arranges the windows as nonoverlapping tiles" ) ;
WHEN ( oMenuItem, IF( !Empty( ::oWnd ), Len( ::oWnd:oWndClient:aWnd ) > 0, .T. ) ) //HSYSBITMAP 32748
MENUITEM FWString( "&Cascade" ) ACTION ( oMenuItem, ::oWnd:Cascade() ) ; //E158
MESSAGE FWString( "Arranges the windows so they overlap" ) ;
WHEN ( oMenuItem, IF( !Empty( ::oWnd ), Len( ::oWnd:oWndClient:aWnd ) > 0, .T. ) ) //HSYSBITMAP IF( ::oMenuChild:lColors, 32744, 9 )
MENUITEM FWString( "&Next Window" ) + Chr( 9 ) + "Ctrl+F6" ;
ACTION ( oMenuItem, ::oWnd:NextWindow() ) MESSAGE FWString( "Selects the next window" ) ;
WHEN ( oMenuItem, IF( !Empty( ::oWnd ), Len( ::oWnd:oWndClient:aWnd ) > 1, .T. ) ) //HSYSBITMAP IF( ::oMenuChild:lColors, 32739, 32739 )
SEPARATOR
MENUITEM FWString( "&Arrange Icons" ) ACTION ( oMenuItem, ::oWnd:ArrangeIcons() ) ; //E154
MESSAGE FWString( "Arrange icons at the bottom of the window" ) ;
WHEN ( oMenuItem, IF( !Empty( ::oWnd ), Len( ::oWnd:oWndClient:aWnd ) > 0, .T. ) ) // HSYSBITMAP 32738
MENUITEM FWString( "&Iconize All" ) ACTION ( oMenuItem, ::oWnd:IconizeAll() ) ;
MESSAGE FWString( "Iconize all open windows" ) ;
WHEN ( oMenuItem, IF( !Empty( ::oWnd ), Len( ::oWnd:oWndClient:aWnd ) > 0, .T. ) ) // HSYSBITMAP IF( ::oMenuChild:lColors, 32749, 11 )
MENUITEM FWString( "C&lose All" ) ACTION ( oMenuItem, ::oWnd:CloseAll() ) ;
MESSAGE FWString( "Close all open windows" ) ;
WHEN ( oMenuItem, IF( !Empty( ::oWnd ), Len( ::oWnd:oWndClient:aWnd ) > 0, .T. ) ) //HSYSBITMAP IF( ::oMenuChild:lColors, 32758, 5 )
ENDMENU
if !Empty( aColors )
::SetColors( ::aColorsBak )
endif
return oItem
File: source/classes/mdiframe.prg (L34-108)
Code: Select all | Expand
CLASS TMdiFrame FROM TWindow
DATA oWndActive
DATA oWndClient
DATA oMenuStart
DATA nMenuInfo
DATA bOnOpen
DATA lChildAutoSize AS LOGICAL INIT .f.
DATA bDlgSelectChild
CLASSDATA lRegistered AS LOGICAL
METHOD New( nTop, nLeft, nBottom, nRight, cTitle, nStyle, oMenu, oBrush,;
oIcon, nClrFore, nClrBack, lVScroll, lHScroll, nMenuInfo,;
cBorder, oWnd, lPixel, cVarName, nHelpID, lUnicode, nWidth, nHeight ) CONSTRUCTOR
METHOD ChildNew( nTop, nLeft, nBottom, nRight, cTitle, nStyle ) INLINE ;
::oWndClient:ChildNew( nTop, nLeft, nBottom, nRight, cTitle, nStyle )
METHOD Cascade() INLINE ::oWndClient:Cascade()
METHOD CloseAll() INLINE ::oWndClient:lCloseAll()
METHOD IconizeAll() INLINE ::oWndClient:IconizeAll()
METHOD Tile( lHor ) INLINE ::oWndClient:Tile( lHor )
METHOD ArrangeIcons() INLINE ::oWndClient:ArrangeIcons()
METHOD Zoom() INLINE ::oWndClient:Zoom()
METHOD CloseActive() INLINE ::oWndClient:CloseActive()
METHOD NextWindow() INLINE ::oWndClient:NextWindow()
METHOD PrevWindow() INLINE ::oWndClient:PrevWindow()
METHOD ReSize( nSizeType, nWidth, nHeight )
METHOD Command( nWParam, nLParam )
METHOD Copy() INLINE If( ::oWndActive != nil, ::oWndActive:Copy(),)
METHOD Cut() INLINE If( ::oWndActive != nil, ::oWndActive:Cut(),)
METHOD Delete() INLINE If( ::oWndActive != nil, ::oWndActive:Delete(),)
METHOD FindNext() INLINE If( ::oWndActive != nil, ::oWndActive:FindNext(),)
METHOD Open() INLINE If( ::bOnOpen != nil, Eval( ::bOnOpen, Self ),)
METHOD Paste( cText ) INLINE ;
If( ::oWndActive != nil, ::oWndActive:Paste( cText ),)
METHOD Print() INLINE ;
If( ::oWndActive != nil, ::oWndActive:Print(),)
METHOD ReDo() INLINE If( ::oWndActive != nil, ::oWndActive:ReDo(),)
METHOD UnDo() INLINE If( ::oWndActive != nil, ::oWndActive:UnDo(),)
METHOD Find( cText ) INLINE ;
If( ::oWndActive != nil, ::oWndActive:Find( cText ),)
METHOD End()
METHOD Properties() INLINE If( ::oWndActive != nil, ::oWndActive:Properties(),)
METHOD Replace() INLINE If( ::oWndActive != nil, ::oWndActive:Replace(),)
METHOD Select( nWindow ) INLINE ::oWndClient:Select( nWindow )
METHOD SelectAll() INLINE If( ::oWndActive != nil, ::oWndActive:SelectAll(),)
METHOD SetMenu( oMenu, nMenuInfo )
METHOD GotFocus()
METHOD DlgSelectChild()
ENDCLASS
File: source/classes/mdiframe.prg (L340-401)
Code: Select all | Expand
METHOD DlgSelectChild() CLASS TMdiFrame
local nWnd := 0
local aWnds := {}
local oDlg
local oLbx
local cTitle := "Select Child Window"
local nTopBtn
local nBtnW := 48
local nBtnH := 14
local nDlgW := 450 + 100 // nDlgW := 450
local nDlgH := 300
local nTopLbx := 2 //33
local nLbxW := 220 + 50
local nLbxH
local nLeftR
local oFont
nTopBtn := Int( nDlgH / 2 ) - nBtnH - 2 // 6
nLbxH := nTopBtn - nTopLbx - 2
AEval( ::oWndClient:aWnd, { | a | AAdd( aWnds, a:cCaption ) } )
//XBROWSER aWnds SELECT ( nWnd := oBrw:nArrayAt ) SHOW RECID
DEFINE FONT oFont NAME "Segoe UI" SIZE 0, -16
DEFINE DIALOG oDlg SIZE nDlgW, nDlgH TITLE cTitle
oDlg:lTruePixel := .f.
oDlg:lHelpIcon := .f.
@ nTopLbx, 2 XBROWSE oLbx DATASOURCE aWnds OF oDlg ;
COLUMNS 1 HEADERS "Caption Child Window" ;
SIZE nLbxW, nLbxH - 2 PIXEL STYLE FLAT NOBORDER FONT oFont
WITH OBJECT oLbx
:l2007 := .F.
:lHScroll := .F.
:lRecordSelector := .F.
:nRowHeight := 24
:nColorPen := CLR_HGRAY
:bClrStd := { || { CLR_BLUE, Rgb( 248, 248, 248 ) } }
:bClrHeader := { || { CLR_BLUE, RGB( 222, 222, 222 ), RGB( 222, 222, 222 ) } }
:lFullGrid := .T.
:nMarqueeStyle := MARQSTYLE_HIGHLROW
:bLDblClick := { || nWnd := oLbx:nArrayAt, oDlg:End() }
:CreateFromCode()
END
nLeftR := ( ( nDlgW + nBtnW * 2 ) - ( 2 * nBtnW ) ) / ( 2 * 2 )
@ nTopBtn, nLeftR BUTTON "&Cancel" ;
OF oDlg ACTION ( oDlg:End() ) ;
SIZE nBtnW, nBtnH FONT oFont PIXEL
@ nTopBtn, nLeftR + nBtnW * 1.5 BUTTON "&Select" ;
OF oDlg ACTION ( nWnd := oLbx:nArrayAt, oDlg:End() ) ;
SIZE nBtnW, nBtnH PIXEL FONT oFont DEFAULT
ACTIVATE DIALOG oDlg CENTERED
RELEASE FONT oFont
if !Empty( nWnd )
::oWndClient:aWnd[ nWnd ]:SetFocus()
endif
Return nil
File: source/classes/mdichild.prg (L117-117)
Code: Select all | Expand
METHOD SetFocus() INLINE ::oWnd:oWndClient:ChildActivate( Self )
File: source/classes/mdichild.prg (L304-320)
Code: Select all | Expand
METHOD KeyDown( nKey, nFlags ) CLASS TMdiChild
// There is no a standard behavior for WM_KEYDOWN messages so we have
// to process them !
if nKey == VK_F4 .and. GetKeyState( VK_CONTROL )
::SendMsg( WM_SYSCOMMAND, SC_CLOSE )
return 0
endif
if nKey == VK_F6 .and. GetKeyState( VK_CONTROL )
::SendMsg( WM_SYSCOMMAND, SC_NEXT )
return 0
endif
return ::Super:KeyDown( nKey, nFlags )
File: samples/test/testmdi6.prg (L29-38)
Code: Select all | Expand
DEFINE BUTTONBAR oBar OF oWnd
DEFINE BUTTON OF oBar ACTION oChild1:SetFocus()
DEFINE BUTTON OF oBar ACTION oChild2:SetFocus()
DEFINE BUTTON OF oBar ACTION oChild3:SetFocus()
DEFINE BUTTON OF oBar GROUP ACTION NewChild()
ACTIVATE WINDOW oWnd
File: source/classes/mdiclien.prg (L53-53)
Code: Select all | Expand
METHOD ChildActivate( oWnd ) INLINE ::SendMsg( WM_MDIACTIVATE, oWnd:hWnd )