MDI Window?

Started by chalky, May 09, 2018, 19:27:29

Previous topic - Next topic

chalky

I am trying to create an MDI window so I can have multiple child windows within it but be able to minimise everything in one go via the parent MDI form. However, although I found this code snippet on the old forums:

SuperStrict

Import MaxGui.Drivers

Local Window1:TGadget=CreateWindow:TGadget("Window1",370,151,494,277,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE|WINDOW_STATUS|WINDOW_CLIENTCOORDS)
Local ChildWindow3:TGadget=CreateWindow:TGadget("ChildWindow3",59,159,99,105,Window1,WINDOW_TITLEBAR|WINDOW_TOOL|WINDOW_CHILD)
SetGadgetParent(ChildWindow3,Window1)
Local ChildWindow2:TGadget=CreateWindow:TGadget("ChildWindow2",59,35,99,105,Window1,WINDOW_TITLEBAR|WINDOW_TOOL|WINDOW_CHILD)
SetGadgetParent(ChildWindow2,Window1)

Repeat
  WaitEvent()
  Select EventID()
    Case EVENT_WINDOWCLOSE
      Select EventSource()
        Case Window1  Window1_WC(Window1)
      End Select
  End Select
Forever

Function Window1_WC(Window:TGadget)
  End
End Function

Extern "win32"
  Function SetParent(Child:Int,Parent:Int)="SetParent@8"
End Extern

Function SetGadgetParent(Child:TGadget,Parent:TGadget)
  SetParent(QueryGadget(Child,QUERY_HWND),QueryGadget(Parent,QUERY_HWND))
  Child._setparent(Parent)
End Function


...it doesn't work on Win7 upwards as (although the child windows ARE parented to the main form) the moment the user clicks anywhere within the 'MDI' client, all its children become 'disabled'.

I have spent ages searching the internet for a cause/solution and found various C++ code for generating parent windows and children using 'setparent' combined with 'WS_CHILD' and/or 'WS_EX_MDICHILD' style flags, but no matter what I try I cannot get anything to work:

Code superceded = see later post...

Am I trying to achieve something which simply is not possible with MaxGUI?

Henri

#1
Hi,

there is nothing in MaxGui to prevent from using MDI, but functionality is not built in. I don't see it being that difficult to add it there.
This is already standard in wxMax. There is a MDI sample in samples folder.

In MaxGui, closest thing already available is a panel, which could have a custom titlebar (colored label), and perhaps some buttons to maximize / minimize. Good thing about panels is that resizing is done automatically by setgadgetlayout.

-Henri
- Got 01100011 problems, but the bit ain't 00000001

Scaremonger

Hi,
I don't think the children are becoming disabled, I think they are just losing Focus to the parent window!
(I am on Windows 10)

If you click one of the child windows the others lose focus and when you click the parent window the children lose focus. This is normal Window behavior as only one window can have Focus at a time.

I have been playing with and extending your example and found it works pretty well, but there are some odd things that would need to be addressed:

I found that a child using the WINDOW_TOOL attribute cannot be minimised, but if you use WINDOW_TITLEBAR the children sit on top of the Status bar when minimised. You might be able to reposition them upwards by the height of the status bar but I didn't try.

Resizing the Parent window (to be smaller) will hide minimised children, so you'll probably need to catch the resize event and re-minimise or reposition any children that are currently minimised.


SuperStrict
Import MaxGui.Drivers

Extern "win32"
  Function SetParent(Child:Int,Parent:Int)="SetParent@8"
End Extern

Const MENU_NEW:Int = 101
Const MENU_OPEN:Int = 102
Const MENU_SAVE:Int = 103
Const MENU_CLOSE:Int = 104
Const MENU_EXIT:Int = 199
Const WINDOW_MINALL:Int = 801
Const HELP_ABOUT:Int = 999

Global window:TGadget=CreateWindow:TGadget("Main Window",370,151,494,277,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE|WINDOW_STATUS|WINDOW_CLIENTCOORDS)
Local filemenu:TGadget = CreateMenu("&File",0,WindowMenu(window))
CreateMenu"&New",MENU_NEW,filemenu,KEY_N,MODIFIER_COMMAND
CreateMenu"&Open",MENU_OPEN,filemenu,KEY_O,MODIFIER_COMMAND
CreateMenu"&Close",MENU_CLOSE,filemenu,KEY_W,MODIFIER_COMMAND
CreateMenu"",0,filemenu
CreateMenu"&Save",MENU_SAVE,filemenu,KEY_S,MODIFIER_COMMAND
CreateMenu"",0,filemenu
CreateMenu"E&xit",MENU_EXIT,filemenu,KEY_F4,MODIFIER_COMMAND

Local WinMenu:TGadget=CreateMenu("&Window",0,WindowMenu(window))
CreateMenu "Minimise All",WINDOW_MINALL,winmenu

Local helpmenu:TGadget=CreateMenu("&Help",0,WindowMenu(window))
CreateMenu "&About",HELP_ABOUT,helpmenu

UpdateWindowMenu window

'# Create some initial MDI Windows
Global MDIwin:TList = CreateList()
CreateChild( "Child Win1", 59, 159, 99, 105 )
CreateChild()
SetStatusText( window, "Children: "+CountList(MDIwin) )

Repeat
WaitEvent()
Select EventID()
Case EVENT_WINDOWCLOSE
If EventSource()=window Then Exit
'# Children
For Local win:TGadget = EachIn MDIwin
If EventSource() = win Then
RemoveLink( TLink(GadgetExtra( win )) )
FreeGadget( win )
Continue
End If
Next
SetStatusText( window, "Children: "+CountList(MDIwin) )
Case EVENT_MENUACTION
Select EventData()
Case MENU_NEW
CreateChild()
SetStatusText( window, "Children: "+CountList(MDIwin) )
Case MENU_CLOSE
Case MENU_EXIT
End
Case WINDOW_MINALL
For Local win:TGadget = EachIn MDIwin
MinimizeWindow( win )
Next
Case HELP_ABOUT
Notify "MDI Windows"
End Select
End Select
Print CurrentEvent.tostring()
Forever
End

Function SetGadgetParent(Child:TGadget,Parent:TGadget)
  SetParent(QueryGadget(Child,QUERY_HWND),QueryGadget(Parent,QUERY_HWND))
  Child._setparent(Parent)
End Function

Function CreateChild:TGadget( caption:String="", x:Int=60, y:Int=30, w:Int=250, h:Int=110 )
Global counter:Int = 0
Local child:TGadget
counter :+ 1
If caption="" Then caption = "Child "+counter
child = CreateWindow:TGadget( caption, x, y, w, h, window, WINDOW_TITLEBAR|WINDOW_RESIZABLE|WINDOW_CHILD )
SetGadgetParent( child, window )
'# Save gadget TLink in it's extra field!
SetGadgetExtra( child, ListAddLast( MDIWin, child ))
Return child
End Function


chalky

Wow - I have no idea how you got that working, but I'm seriously grateful to you for taking the time to do it and post the code here. It's so close to what I'm looking for, and will give me a fantastic foundation to work on - thank you!  :D

chalky

#4
Hmmm... It turns out the original problem was not restricted to my code, for if the "WINDOW_RESIZABLE" flag is removed from Scaremonger's post, the same problem exists. This is only an issue when creating non-resizable windows - so to get round it I create them resizable, then remove the sizing functionality via API calls. No matter what I tried, I couldn't get the child windows to appear "behind" the status bar or minimize "above" it - so I adopted a different approach: the MDI form is actually 3 windows which are synced via a system hook - not the most elegant of solutions, but it works:

Code superceded - see later post...

Thanks again to Scaremonger for keeping my interest going and enabling me to find a [admittedly hacked] solution...

Scaremonger

#5
Hi,
I have found a way around the issue with the status bar overlap with the child windows by adding a panel and parenting the children to the panel instead of the window.

I have also added an Eventhook to catch WINDOWSIZE events and re position the minimised children to the bottom of the screen, although if you have a lot of them they wouldn't currently stack so you might want to play with the algorithm.

I have also used SetMinWindowSize() and setMaxWindowSize() to prevent resizing but keeping the buttons.


SuperStrict
Import MaxGui.Drivers
Rem ## CHANGES
Added Panel as parent of all children (Fixed the status bar overlay issue)
Added EventHook()
Added API SetWindowLong() and GetWindowLong()
Added WS_CLIPSIBLINGS to child window (Although I don't think this made much difference)
Added EVENT_WINDOWSIZE to move minimised child windows.
End Rem

Extern "win32"
Function SetParent(Child:Int,Parent:Int)="SetParent@8"
Function SetWindowLong:Int(HWND:Int,nIndex:Int,dwNewLong:Int) = "SetWindowLongA@12"
Function GetWindowLong:Int(HWND:Int,nIndex:Int) = "GetWindowLongA@8"
End Extern

Const MENU_NEW:Int = 101
Const MENU_OPEN:Int = 102
Const MENU_SAVE:Int = 103
Const MENU_CLOSE:Int = 104
Const MENU_EXIT:Int = 199
Const WINDOW_MINALL:Int = 801
Const HELP_ABOUT:Int = 999

'# Create Window and a Panel to contain the MDI Children.
Global window:TGadget=CreateWindow("Main Window",370,151,494,277,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE|WINDOW_STATUS|WINDOW_CLIENTCOORDS)
Global panel:TGadget = CreatePanel(0,0,ClientWidth(window),ClientHeight(window),window,PANEL_ACTIVE)
SetGadgetLayout panel, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED, EDGE_ALIGNED
SetGadgetColor( panel, $ff,0,0 )

Local filemenu:TGadget = CreateMenu("&File",0,WindowMenu(window))
CreateMenu"&New",MENU_NEW,filemenu,KEY_N,MODIFIER_COMMAND
CreateMenu"&Open",MENU_OPEN,filemenu,KEY_O,MODIFIER_COMMAND
CreateMenu"&Close",MENU_CLOSE,filemenu,KEY_W,MODIFIER_COMMAND
CreateMenu"",0,filemenu
CreateMenu"&Save",MENU_SAVE,filemenu,KEY_S,MODIFIER_COMMAND
CreateMenu"",0,filemenu
CreateMenu"E&xit",MENU_EXIT,filemenu,KEY_F4,MODIFIER_COMMAND

Local WinMenu:TGadget=CreateMenu("&Window",0,WindowMenu(window))
CreateMenu "Minimise All",WINDOW_MINALL,winmenu

Local helpmenu:TGadget=CreateMenu("&Help",0,WindowMenu(window))
CreateMenu "&About",HELP_ABOUT,helpmenu

UpdateWindowMenu window

'# Create some initial MDI Windows
Global MDIwin:TList = CreateList()
CreateChild( "Child Win1", 59, 159, 99, 105 )
CreateChild()
SetStatusText( window, "Children: "+CountList(MDIwin) )

'Add event hook
AddHook EmitEventHook, EventHook

Repeat
WaitEvent()
Select EventID()
Case EVENT_WINDOWCLOSE
If EventSource()=window Then Exit
'# Children
For Local win:TGadget = EachIn MDIwin
If EventSource() = win Then
RemoveLink( TLink(GadgetExtra( win )) )
FreeGadget( win )
Continue
End If
Next
SetStatusText( window, "Children: "+CountList(MDIwin) )
Case EVENT_MENUACTION
Select EventData()
Case MENU_NEW
CreateChild()
SetStatusText( window, "Children: "+CountList(MDIwin) )
Case MENU_CLOSE
Case MENU_EXIT
End
Case WINDOW_MINALL
For Local win:TGadget = EachIn MDIwin
MinimizeWindow( win )
Next
Case HELP_ABOUT
Notify "MDI Windows"
End Select
End Select
Print CurrentEvent.tostring()
Forever
RemoveHook( EmitEventHook, EventHook )
End

Function SetGadgetParent(Child:TGadget,Parent:TGadget)
  SetParent(QueryGadget(Child,QUERY_HWND),QueryGadget(Parent,QUERY_HWND))
  Child._setparent(Parent)
End Function

Function CreateChild:TGadget( caption:String="", x:Int=60, y:Int=30, w:Int=250, h:Int=110 )
Global counter:Int = 0
Local child:TGadget
counter :+ 1
If caption="" Then caption = "Child "+counter
child = CreateWindow( caption, x, y, w, h, panel, WINDOW_TITLEBAR|WINDOW_RESIZABLE )
'# Add WS_CLIPSIBLINGS to window style
Local hwnd:Int  = QueryGadget( child, QUERY_HWND )
Local style:Int = GetWindowLong( hwnd, GWL_EXSTYLE ) | WS_CLIPSIBLINGS
SetWindowLong( hwnd, GWL_EXSTYLE, style )
'# Set the child parent
SetGadgetParent( child, panel )
'# Save gadget TLink in it's extra field!
SetGadgetExtra( child, ListAddLast( MDIWin, child ))
'#
SetMinWindowSize( child,250,110)
SetMaxWindowSize( child,250,110)
SetGadgetShape( child,x,y,250,110)
Return child
End Function

Function EventHook:Object( id:Int, data:Object, context:Object )
Local event:TEvent = TEvent(data)
If Not event Then Return data
Select event.id
Case EVENT_WINDOWSIZE
'Print "Windowsize"
If Event.Source=window Then
Local winX:Int
Local winY:Int
For Local win:TGadget = EachIn MDIwin
If WindowMinimized( win ) Then
Local rect:Int[] = [0,0,0,0] 'x,y,w,h
Local hwnd:Int = QueryGadget( win, QUERY_HWND )
GetWindowRect hwnd, Rect
Local height:Int = Rect[3]-Rect[1]
Local width:Int = Rect[2]-Rect[0]
winY = event.y-height
SetGadgetShape( win, winX, winY, GadgetWidth(win), GadgetHeight(win) )
winX :+ width
End If
Next
Return Null
End If
Case EVENT_MOUSEMOVE
Default
' Print "HOOK: "+event.tostring()
End Select
Return data
End Function




chalky

#6
Very neat - using a panel results in much less housekeeping, and enables window colouring. Your hook code is very clever - there's no way I would've worked that out for myself.

There is still a general window problem due to MaxGUI not creating non-resizable windows correctly - even those created without the WINDOW_RESIZABLE flag set can still be minimized via their system menu and the MaxGUI MinimizeWinow command. I have therefore kept my code which disables this when necessary, and added a MinimizeAll function to ensure only resizable child windows get minimized:

SuperStrict

Import maxgui.Drivers

Extern "win32"
  Function GetMenuItemCount:Int(hMenu:Int)="GetMenuItemCount@4"
  Function GetSystemMenu:Int(hwnd:Int,bRevert:Int)="GetSystemMenu@8"
  Function RemoveMenu:Int(hMenu:Int,nPosition:Int,wFlags:Int)="RemoveMenu@12"
  Function SetParent(Child:Int,Parent:Int)="SetParent@8"
End Extern

Global mdiForm:TGadget=CreateWindow:TGadget("Main Window",200,150,480,260,Null,WINDOW_TITLEBAR|WINDOW_RESIZABLE|WINDOW_STATUS|WINDOW_CLIENTCOORDS)
Global mdiPanel:TGadget

Local mnuFile:TGadget=CreateMenu("&File",0,WindowMenu(mdiForm))
Local mnuForm:TGadget=CreateMenu("&Window",0,WindowMenu(mdiForm))
Local mnuHelp:TGadget=CreateMenu("&Help",0,WindowMenu(mdiForm))

Const MENU_NEW:Int=101
Const MENU_OPEN:Int=102
Const MENU_SAVE:Int=103
Const MENU_CLOSE:Int=104
Const MENU_EXIT:Int=199
Const WINDOW_MINALL:Int=801
Const HELP_ABOUT:Int=999

Global mdiChildList:TList=CreateList()
Global mdiPos:Long

CreateMenu"&New",MENU_NEW,mnuFile,KEY_N,MODIFIER_COMMAND
CreateMenu"&Open",MENU_OPEN,mnuFile,KEY_O,MODIFIER_COMMAND
CreateMenu"&Close",MENU_CLOSE,mnuFile,KEY_W,MODIFIER_COMMAND
CreateMenu"",0,mnuFile
CreateMenu"&Save",MENU_SAVE,mnuFile,KEY_S,MODIFIER_COMMAND
CreateMenu"",0,mnuFile
CreateMenu"E&xit",MENU_EXIT,mnuFile,KEY_F4,MODIFIER_COMMAND
CreateMenu "Minimise All",WINDOW_MINALL,mnuForm
CreateMenu "&About",HELP_ABOUT,mnuHelp
UpdateWindowMenu mdiForm

mdiPanel=CreatePanel(0,0,ClientWidth(mdiForm),ClientHeight(mdiForm)-2,mdiForm,PANEL_ACTIVE)
SetGadgetLayout(mdiPanel,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED,EDGE_ALIGNED)
SetPanelColor(mdiPanel,191,205,219)
SetMinWindowSize(mdiForm,160,40)

AddHook EmitEventHook,MDIHook

CreateMDIChild("Fixed Size Window",False)
SetStatusText(mdiForm,"Children: "+CountList(mdiChildList))

Repeat
  PollEvent()
  If EventID() Then
    Select EventID()
      Case EVENT_WINDOWCLOSE
        If EventSource()=mdiForm Then
          Exit
        Else
          CloseMDIChild()
        EndIf
      Case EVENT_MENUACTION
        ProcessMenu(EventData())
    EndSelect
  EndIf
Forever
End

Function CloseMDIChild()
  For Local mdiChild:TGadget=EachIn mdiChildList
    If EventSource()=mdiChild Then
      RemoveLink(TLink(GadgetExtra(mdiChild)))
      FreeGadget(mdiChild)
      Continue
    EndIf
  Next
  SetStatusText(mdiForm,"Children: "+CountList(mdiChildList))
EndFunction

Function CreateMDIChild:TGadget(Caption:String="",Resizable:Long=True)
  Local mdiChild:TGadget
  Local Style:Long=WINDOW_TITLEBAR|WINDOW_CHILD|WINDOW_RESIZABLE

  mdiPos:+40
  If mdiPos=400
    mdiPos=40
  EndIf
  If Caption="" Then
    Caption="Child Window "+(CountList(mdiChildList)+1)
  EndIf
  mdiChild=CreateWindow:TGadget(Caption,mdiPos,mdiPos,280,120,mdiPanel,Style)
  SetGadgetParent(mdiChild,mdiPanel)
  '# Save gadget TLink in it's extra field!
  SetGadgetExtra(mdiChild,ListAddLast(mdiChildList,mdiChild))
  If Not Resizable Then
     DisableResize(mdiChild)
   EndIf
  Return mdiChild
End Function

Function DisableResize(Window:TGadget)
  Local cHWND:Long=QueryGadget(Window,QUERY_HWND)
  Local hMenu:Long
  Local iMenu:Long
  Local style:Long

  ' remove sizing functionality from window style
  style=GetWindowLongA(cHWND,GWL_STYLE)                 ' get current style
  style:~(WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX)  ' remove resize functionality bits
  SetWindowLongA(cHWND,GWL_STYLE,style)                 ' set new style
  ' get system menu info
  hMenu=GetSystemMenu(cHWND,0)
  iMenu=GetMenuItemCount(hMenu)
  ' then remove sizing functionality from it
  RemoveMenu(hMenu,iMenu-3,MF_BYPOSITION)               ' remove "maximize"
  RemoveMenu(hMenu,iMenu-4,MF_BYPOSITION)               ' remove "minimize"
  RemoveMenu(hMenu,iMenu-5,MF_BYPOSITION)               ' remove "size"
  RemoveMenu(hMenu,iMenu-7,MF_BYPOSITION)               ' remove "restore"
End Function

Function MDIHook:Object(hId:Int,hData:Object,hContext:Object)
  Local Event:TEvent=TEvent(hData)

  If Event Then
    Select Event.id
      Case EVENT_WINDOWSIZE
        If Event.Source=mdiForm Then
          Local winX:Int
          Local winY:Int
          For Local mdiChild:TGadget=EachIn mdiChildList
            If WindowMinimized(mdiChild) Then
              Local Rect:Int[]=[0,0,0,0]
              Local hWnd:Int=QueryGadget(mdiChild,QUERY_HWND)
              GetWindowRect(hWnd,Rect)
              Local Height:Int=Rect[3]-Rect[1]
              Local Width:Int=Rect[2]-Rect[0]
              winY=Event.y-Height
              SetGadgetShape(mdiChild,winX,winY,GadgetWidth(mdiChild),GadgetHeight(mdiChild))
              winX:+Width
            EndIf
          Next
          Return Null
        EndIf
    EndSelect
  EndIf
  Return hData
End Function

Function MinimizeAll()
  Local cHWND:Long
  Local style:Long
 
  For Local mdiChild:TGadget=EachIn mdiChildList
    cHWND=QueryGadget(mdiChild,QUERY_HWND)
    style=GetWindowLongA(cHWND,GWL_STYLE)  ' get current style
    If style & WS_THICKFRAME Then          ' check for sizable borders
      MinimizeWindow(mdiChild)
    EndIf
  Next
EndFunction

Function ProcessMenu(MenuID:Long)
  Select MenuID
    Case MENU_NEW
      CreateMDIChild()
      SetStatusText(mdiForm,"Children: "+CountList(mdiChildList))
    Case MENU_CLOSE
    Case MENU_EXIT
      End
    Case WINDOW_MINALL
      MinimizeAll()
    Case HELP_ABOUT
      Notify "MDI Windows"
  EndSelect
EndFunction

Function SetGadgetParent(mdiChild:TGadget,mdiParent:TGadget)
  SetParent(QueryGadget(mdiChild,QUERY_HWND),QueryGadget(mdiParent,QUERY_HWND))
  mdiChild._setparent(mdiParent)
End Function


That's good enough for me. Thanks again Scaremonger - I'd never have got there without your help.