View Full Version : MDI script example with some API usage
ErosOlmi
29-10-2008, 15:35
Only for those having thinBasic preview 1.7.0.0 refresh 20081029 (or above).
_________________________________________________
Attached a MDI script example I'm using to test both MDI functionalities and some API for handling future dialogs implementations.
The script creates a sort of dialog editor covering just the minimal part of a grid, snap to grid, hide/show grid, dragging.
It is experimental, there are many aspects to cover and simplify, sometimes the grid is not shown. But in general seems not bad.
Ciao
Eros
There is a few things I wanted to bring to light, from this and the sample code...
For the windows you have this style...
Style = _
%WS_CHILD | _
%WS_DLGFRAME | _
%WS_CAPTION | _
%WS_SYSMENU | _
%WS_OVERLAPPEDWINDOW | _
%WS_CLIPCHILDREN | _
%WS_CLIPSIBLINGS
DLGFRAME ' -- (Creates a window that has a border of a style typically used with dialog boxes. A window with this style cannot have a title bar.)
But you also have...
CAPTION (Title-bar)
SYSMENU
OVERLAPPEDWINDOW (Creates an overlapped window with the %WS_OVERLAPPED, %WS_CAPTION, %WS_SYSMENU, %WS_THICKFRAME, %WS_MINIMIZEBOX, and %WS_MAXIMIZEBOX styles. Same as the %WS_TILEDWINDOW style.)
Could be simplified to just this...
Style = _
%WS_CHILD | _
%WS_OVERLAPPEDWINDOW | _
%WS_CLIPCHILDREN | _
%WS_CLIPSIBLINGS
That solves the "Sometimes it does not draw the graph" issue... (Well, for me it did.)
But that is related to the BLOCKING of other MDI child windows, from the code...
"if iswindow(hMDIChild) = %FALSE then"
Plus, with MDI not actually controlling the forms, the repaint is not in sync with the active window.
However, that does not solve the issue with only having one form at a time. (You block "Multiple Documents"... in the MultipleDocumentInterface procedure?)
You should be able to create thousands of "Those" forms... You are still trying to create separate code for each "Window", but you only need code for each-TYPE of window. (If the windows are the same, only one code area controls all 1000 of them.)
If you have an...
Image-Form
Sound-Form
Login-Form
Chat-Form
Those should have separate code, but...
They all use the same code when you create...
Chat-form#1 (Joe)
Chat-form#2 (Andy)
Chat-form#3 (Sam)
Chat-form#4 (Omar)
Chat-form#5 (Nick)
You are thinking linear, not procedural. Windows manages all the controls and windows for you. You only have to provide the code which reacts to the CBHNDL which is ACTIVE. (The Active MDI.) Which should be in the MENU, as a LIST of all children in the window, so you don't have to rifle through thousands of hidden windows, or minimize them all where you can't read the title-bars.
Until that MDI-function is complete... We are not getting the function of an MDI, other than the container window. (The function of the MDI is the control of each child by ID, and of the MENU inheritance from the children, to the parent. Being an MDI-Container is only a function of the form/window.)
If you remove %WS_CLIPSIBLINGS from the main window... (Container)... and remove the "if iswindow(hMDIChild) = %FALSE then" which only allows one child to be created... It works as an MDI... Except for the menu-inheritance issue, and the issue related to an inability to create an MDI control inside a normal form. (This setup demands that the whole form becomes the container. So there is no way to have an actual program/window/form... that has a container as it would have a TAB, or BUTTON, or LIST.)
I can sooo see that being used as a FORM-EDITOR for ThinAIR in the future...
ErosOlmi
29-10-2008, 18:23
Jason,
this is just a little test of the main container. Handling many child windows is quite easy but you need to put in place an array of child window handlers and point all of them to the same callback (in case they need the same behave).
We are just at the beginning.
Let me know an example of a typical MDi application and I will try to see if current thinBasic MDi implementation is able to handle it. For example and MDI editor with more files edit at the same time? I will check.
Anyhow, thanks for the tips and suggestions. I will fix and repost source code so maybe we can make a further step.
Ciao
Eros
Let me know an example of a typical MDi application and I will try to see if current thinBasic MDi implementation is able to handle it.
http://en.wikipedia.org/wiki/Multiple_document_interface
At the bottom is several Application examples.
ThinAIR is also an example of an MDI. (Not sure if it actually is an MDI, but it is built to imitate one quite well, if it is not one.)
You will notice that in the "WINDOW" menu... there is a list of all open MDI-Childs. That is automatic, not programmed in an array, windows holds that information.
If you close all windows, "EDIT", "DEBUG", and "TOOLS" becomes disabled in the menu.
The items in the menu, react to the "ACTIVE CHILD"... If I hit "SAVE", it saves the "ACTIVE CHILD".
Also, if I created an "Image-Form", as opposed to the "Code-Form". It may have additional menu items to add to the list. "EDIT - Colors - Decrease color depth - 8 bit, 16 bit", and "EDIT - Colors - Increase color depth - 16 bit, 32 bit"... Something that would not be there if there were no "Image-Forms" present in the MDI-Container.
Those "Menus" are attached to the child, but the container removes them, and adopts them into the existing menu. (The child does not alter the parent menu itself. That is a function of the MDI container.)
Microsoft Excell is another good example of an MDI. (Multiple documents, and the parent window changes as the active document displays separate types of data.)
UltraEdit is another good example. http://en.wikipedia.org/wiki/UltraEdit
Other resources... (But MSDN has the better detailed info about the inheritance issue.)
http://helpcentral.componentone.com/nethelp/c1command/default.htm?turl=creatingawindowlistforanmdiform.htm
Short version... There are missing MDI_Menu commands/switches which give the program the info about the MDI_Menu item. (Again, we are recycling the "Menu" controls, which are not the MDI type/class version of the control. Just as we originally attempted to recycle a standard "Dialog" control for display of the MDI type/class.)
For a suggestion... To keep those who will never need this stuff...
In the "UI" section, replace the "Dialog" area with...
- SLI (Dialogs)
- MDI (Dialogs)
Where there is a "Unique" command specific to a MDI version of a SLI dialog, use a new extension.
mDIALOG SET IMAGE (MDI-Hndl, CBHNDL, ...... )
Such that there is only one MDI-Handle, and the window in the MDI needs to be communicated with the CBHNDL not the original hWnd handle it was created with. (Since there may be hundreds of those windows.)
The CBHNDL of the ACTIVEWINDOW(MDI-Hndl) is the CBHNDL desired. Since you would not be doing anything in a non-active window. Thus, you can remove that, or assume it, since this is an MDI version of SET IMAGE.
mDIALOG SET IMAGE (MDI-Hndl, ACTIVEWINDOW(MDI-Hndl), ...... ) would be...
mDIALOG SET IMAGE (MDI-Hndl, ...... )
Noting the fact that only changes can be made to an active MDI window/dialog. (Which would force them to make a window active before making a change to another window in the list. Which is actually the common thing to do, when editing multiple documents in an MDI. Like when you use a macro to "Do to all". It steps through the MDI-List.)
This may be part of the problem... Quoted from MSDN.
WM_MDICREATE
The MDI child window is created with the style bits WS_CHILD, WS_CLIPSIBLINGS, WS_CLIPCHILDREN, WS_SYSMENU, WS_CAPTION, WS_THICKFRAME, WS_MINIMIZEBOX, and WS_MAXIMIZEBOX, plus additional style bits specified in the MDICREATESTRUCT structure. The system adds the title of the new child window to the window menu of the frame window. An application should use this message to create all child windows of the client window.
You are creating the window with DIALOG NEW, then activating that non-MDI dialog in the MDI-Client with, WM_MDIACTIVATE?
Still looking for the MDI-Menu controls...
I know I saw them...
This would be, yet another, specific MDI control. (Thus my suggestion for the "m" to be added to MDI specific functions. All non-specific could be an alias, until you decide there needs to be a new set of code to replace the non-MDI function.)
This is the reduced version of your code, using only the critical elements.
I have included the TEST MENU, but as you see, it does not go to the parent, or the child.
USES "UI"
'----------------------------------------------------------------
' Main Dialog (called a MDI Frame)
'----------------------------------------------------------------
%MDIFrame_MDI = 500
%MDIFrame_NewForm = 505
%MDIFrame_SEPARATOR_510 = 510
%MDIFrame_EXIT = 515
%MDIFrame_Child = 520
GLOBAL hMDIFrame AS LONG ' Main MDI Dialog handle
GLOBAL hMDIFrame_Menu0 AS LONG
GLOBAL hMDIFrame_Menu1 AS LONG
GLOBAL hMDIFrame_Menu0c AS LONG
GLOBAL hMDIFrame_Menu1c AS LONG
GLOBAL MDI_ClientHandle AS LONG
'----------------------------------------------------------------
' First MDI Child Dialog
'----------------------------------------------------------------
%MDICHILD_BUTTON1 = 100
%MDICHILD_BUTTON2 = 105
%MDICHILD_TEXT1 = 110
GLOBAL hMDIChild AS LONG
GLOBAL Style AS LONG
GLOBAL ExStyle AS LONG
'----------------------------------------------------------------
' Main Dialog (called a MDI Frame)
'----------------------------------------------------------------
SUB TBMain() AS LONG
Style = _
%WS_OVERLAPPEDWINDOW | _
%WS_CLIPCHILDREN | _
%WS_CLIPSIBLINGS | _
%DS_CENTER
ExStyle = 0
DIALOG NEW 0, "MDI using DDT", 0, 0, 450, 320, Style, ExStyle To hMDIFrame
MENU NEW BAR TO hMDIFrame_Menu0
MENU NEW POPUP TO hMDIFrame_Menu1
MENU ADD POPUP, hMDIFrame_Menu0 ,"&File", hMDIFrame_Menu1, %MF_ENABLED
MENU ADD STRING, hMDIFrame_Menu1, "&New form", %MDIFrame_NewForm, %MF_ENABLED
MENU ADD STRING, hMDIFrame_Menu1, "-", %MDIFrame_SEPARATOR_510, %MF_ENABLED
MENU ADD STRING, hMDIFrame_Menu1, "E&xit", %MDIFrame_EXIT, %MF_ENABLED
MENU ATTACH hMDIFrame_Menu0, hMDIFrame
'---Must be MODELESS dialog without a message loop
'---Message loop will be governed by MDI_MessageLoop function
'---To be executed after this line
DIALOG SHOW MODELESS hMDIFrame , CALL MDIFrame_DLGPROC
' ----------------------
MDI_ClientHandle = MDI_CreateClient(hMDIFrame)
' IMPORTANT: MDI_MessageLoop must be invoked after MDI_CreateClient calling
MDI_MessageLoop MDI_ClientHandle
'-----------------------
END SUB
'----------------------------------------------------------------
CALLBACK FUNCTION MDIFrame_DLGPROC() AS LONG
'----------------------------------------------------------------
SELECT CASE CBMSG
CASE %WM_INITDIALOG
' ----------------------
' IMPORTANT
MDI_SubClassDialog CBHNDL
' ----------------------
CASE %WM_COMMAND
SELECT CASE CBCTL
CASE %MDIFrame_NewForm
MDIFrame_CHILD_NewForm(MDI_ClientHandle)
CASE %MDIFrame_EXIT
DIALOG END CBHNDL
CASE ELSE
END SELECT
CASE %WM_DESTROY
'--------------------
' Important otherwise process remain active
MDI_PostQuitMessage
'--------------------
RETURN(1) ' This call expects a "1" return value.
CASE ELSE
END SELECT
END FUNCTION
'----------------------------------------------------------------
' First MDI Child Dialog
'----------------------------------------------------------------
SUB MDIFrame_CHILD_NewForm(ByVal hParent AS LONG)
Style = _
%WS_CHILD | _
%WS_OVERLAPPEDWINDOW | _
%WS_CLIPCHILDREN | _
%WS_CLIPSIBLINGS
ExStyle = %WS_EX_MDICHILD | %WS_EX_CONTROLPARENT
DIALOG NEW PIXELS, hParent, "BLANK", 10, 10, 300, 100, Style, ExStyle To hMDIChild
DIALOG SHOW MODELESS hMDIChild, Call MDIChild_DLGPROC
MDI_SendMessage %WM_MDIACTIVATE, hMDIChild, 0
END SUB
'----------------------------------------------------------------
CALLBACK FUNCTION MDIChild_DLGPROC() AS LONG
SELECT CASE CBMSG
CASE %WM_CREATE
RETURN(0) ' This call expects a "0" upon success.
CASE %WM_INITDIALOG
'##############################################
' This menu, (Which should never display in the MDI-CHILD, should go directly to the MDI-CONTAINER)
' This should disapear when this child is killed with a WM_MDIDESTROY command.
'MENU NEW BAR TO hMDIFrame_Menu0c
'MENU NEW POPUP TO hMDIFrame_Menu1c
'MENU ADD POPUP, hMDIFrame_Menu0c ,"&Window", hMDIFrame_Menu1c, %MF_ENABLED
'MENU ADD STRING, hMDIFrame_Menu1c, "&Child: " & CBHNDL, %MDIFrame_NewForm, %MF_ENABLED
'MENU ATTACH hMDIFrame_Menu0c, CBHNDL
DIALOG SET TEXT CBHNDL, "Child: " & CBHNDL
MDI_SubClassDialog CBHNDL
' This should refresh the menu-display, so it shows the new additional menu items.
'MDI_SendMessage %WM_MDIREFRESHMENU, 0, 0
RETURN(1) ' This call expects a "1" return value.
CASE %WM_DESTROY
CASE %WM_SETCURSOR
CASE %WM_COMMAND
END SELECT
END FUNCTION
ErosOlmi
29-10-2008, 23:31
Jason,
I think we have to go step by step and face every problem as single aspect. Making a post stating so many aspects at the same time I think creates confusion and do not let us understand what is important to develop and what is already there.
I will try here just to give few details and later, maybe tomorrow, post a more advance example.
1. we now have a real MDI environment to work with
We were able to create a real (not simulated) MDI environment to play with. And, as you have seen, this is done with just few lines of code. Maybe the fact that this is just few lines of code gives the impression it is very easy to achieve but (bealive me) internally it is not so simple also considering we are working in an interpreted environment and not in a compiled one.
2. Windows does some hard job for us in MDI but not all.
it is programmer responsibility to keep the program in a safe state preserving all the data that let us know in which state we are and in which state every single window is
3. MDI child: cascade, Tile Horizontal/Vertical, Get active
All those commands are very easy to achieve once an MDI environment in in place like we have now in thinBasic.
Just to give you an idea on how easy it is now to implement all the feature you are asking, have a look at the attached new example.
You can create infinite new MDI child windows all responding using just one callback. You can arrange all child in the standard MDI way you stated.
Please, this script is still bad because for every new window we need to store a personalized set of data in such a way every window have its "state" that is a set of properties like, the grid flags, or whatever is required by it. But those are just details.
I just wanted to stress that before going on we need to be sure we have the "concrete" base on which to build a set of MDI functionalities in order to be easy for the programmer to deal with MDI windows in a an easy and powerful way (that is thinBasic way ;D)
Ciao
Eros
PS: later I will get your reduced example and work with it.
ErosOlmi
29-10-2008, 23:38
And this is your reduced MDI example with window responding to MDI position commands:
USES "UI"
#MINVERSION 1.7.0.0
#SCRIPTVERSION 1.0.0.1
'----------------------------------------------------------------
'
'----------------------------------------------------------------
begin const
%MDIFrame_MDI = %WM_USER + 500
%MDIFrame_NewForm
%MDIFrame_SEPARATOR_510
%MDIFrame_EXIT
%MDIFrame_Window_Cascade
%MDIFrame_Window_TileHor
%MDIFrame_Window_TileVer
end const
GLOBAL hMDIFrame AS LONG ' Main MDI Dialog handle
Global hMDIFrame_Menu As Long
Global hMDIFrame_Menu_File As Long
Global hMDIFrame_Menu_Window As Long
Global hMDIFrame_Menu_Help As Long
GLOBAL MDI_ClientHandle AS LONG
'----------------------------------------------------------------
' First MDI Child Dialog
'----------------------------------------------------------------
%MDICHILD_BUTTON1 = 100
%MDICHILD_BUTTON2 = 105
%MDICHILD_TEXT1 = 110
GLOBAL hMDIChild AS LONG
GLOBAL Style AS LONG
GLOBAL ExStyle AS LONG
'----------------------------------------------------------------
' Main Dialog (called a MDI Frame)
'----------------------------------------------------------------
SUB TBMain() AS LONG
Style = _
%WS_OVERLAPPEDWINDOW | _
%WS_CLIPCHILDREN | _
%WS_CLIPSIBLINGS | _
%DS_CENTER
ExStyle = 0
DIALOG NEW 0, "MDI using DDT", 0, 0, 450, 320, Style, ExStyle To hMDIFrame
Menu New Bar To hMDIFrame_Menu
Menu New PopUp To hMDIFrame_Menu_File
Menu Add PopUp, hMDIFrame_Menu ,"&File", hMDIFrame_Menu_File, %MF_ENABLED
Menu Add String, hMDIFrame_Menu_File, "&New form" , %MDIFrame_NewForm, %MF_ENABLED
Menu Add String, hMDIFrame_Menu_File, "-" , %MDIFrame_SEPARATOR_510, %MF_ENABLED
Menu Add String, hMDIFrame_Menu_File, "E&xit" , %MDIFrame_EXIT, %MF_ENABLED
Menu New PopUp To hMDIFrame_Menu_Window
Menu Add PopUp, hMDIFrame_Menu ,"&Window", hMDIFrame_Menu_Window, %MF_ENABLED
Menu Add String, hMDIFrame_Menu_Window, "Cascade" , %MDIFrame_Window_Cascade, %MF_ENABLED
Menu Add String, hMDIFrame_Menu_Window, "Tile Horizontal" , %MDIFrame_Window_TileHor, %MF_ENABLED
Menu Add String, hMDIFrame_Menu_Window, "Tile Vertical" , %MDIFrame_Window_TileVer, %MF_ENABLED
MENU ATTACH hMDIFrame_Menu, hMDIFrame
'---Must be MODELESS dialog without a message loop
'---Message loop will be governed by MDI_MessageLoop function
'---To be executed after this line
DIALOG SHOW MODELESS hMDIFrame , CALL MDIFrame_DLGPROC
' ----------------------
MDI_ClientHandle = MDI_CreateClient(hMDIFrame)
' IMPORTANT: MDI_MessageLoop must be invoked after MDI_CreateClient calling
MDI_MessageLoop MDI_ClientHandle
'-----------------------
END SUB
'----------------------------------------------------------------
CALLBACK FUNCTION MDIFrame_DLGPROC() AS LONG
'----------------------------------------------------------------
SELECT CASE CBMSG
CASE %WM_INITDIALOG
' ----------------------
' IMPORTANT
MDI_SubClassDialog CBHNDL
' ----------------------
CASE %WM_COMMAND
SELECT CASE CBCTL
CASE %MDIFrame_NewForm
MDIFrame_CHILD_NewForm(MDI_ClientHandle)
CASE %MDIFrame_EXIT
DIALOG END CBHNDL
case %MDIFrame_Window_Cascade
SendMessage(MDI_ClientHandle, %WM_MDICASCADE, 0, 0)
case %MDIFrame_Window_TileHor
SendMessage(MDI_ClientHandle, %WM_MDITILE, 1, 0)
case %MDIFrame_Window_TileVer
SendMessage(MDI_ClientHandle, %WM_MDITILE, 0, 0)
END SELECT
CASE %WM_DESTROY
'--------------------
' Important otherwise process remain active
MDI_PostQuitMessage
'--------------------
END SELECT
END FUNCTION
'----------------------------------------------------------------
' First MDI Child Dialog
'----------------------------------------------------------------
SUB MDIFrame_CHILD_NewForm(ByVal hParent AS LONG)
static LastX as long
static LastY as long
Style = _
%WS_CHILD | _
%WS_OVERLAPPEDWINDOW | _
%WS_CLIPCHILDREN | _
%WS_CLIPSIBLINGS
ExStyle = %WS_EX_MDICHILD | %WS_EX_CONTROLPARENT
LastX += 10
If LastX > 300 then LastX = 10
LastY += 10
If LastY > 300 then LastY = 10
DIALOG NEW PIXELS, hParent, "BLANK", LastX, LastY, 300, 100, Style, ExStyle To hMDIChild
DIALOG SHOW MODELESS hMDIChild, Call MDIChild_DLGPROC
MDI_SendMessage %WM_MDIACTIVATE, hMDIChild, 0
END SUB
'----------------------------------------------------------------
CALLBACK FUNCTION MDIChild_DLGPROC() AS LONG
SELECT CASE CBMSG
CASE %WM_CREATE
RETURN(0) ' This call expects a "0" upon success.
CASE %WM_INITDIALOG
DIALOG SET TEXT CBHNDL, "Child: " & CBHNDL
MDI_SubClassDialog CBHNDL
CASE %WM_DESTROY
CASE %WM_SETCURSOR
CASE %WM_COMMAND
END SELECT
END FUNCTION
ErosOlmi
29-10-2008, 23:55
In next UI version I will provide a set of new MDI functions in order to avoid SendMessage API usage.
Just an example:
Mdi_GetActive
Mdi_GetActiveMax
Mdi_Cascade
Mdi_Tile
Mdi_IconArrange
Mdi_Activate
Mdi_Destroy
Mdi_Maximize
Mdi_Next
Mdi_Restore
Ciao
Eros
ErosOlmi
30-10-2008, 10:04
Jason,
I've created a new example of the script in which you can create any number of child windows everyone without any need to create an array of windows.
Every child window will have its own set of information about grid, snap to grid, and other info ...
So every window have its own life and state.
All child windows will respond to MDI commands to cascade, tile and more.
I cannot post it right now but I have to update again thinBasic 1.7.0.0 because there was a bug when passing UDT structures to API function. Now I've fixed it. I will upload a new version by tomorrow and post also new script.
Ciao
Eros
ErosOlmi
02-11-2008, 09:46
Jason,
have a look at "MDI_Test_DialogEdit.tbasic" script in \thinbasic\samplescripts\UI\MDI\ directory in thinBasic preview version 1.7.0.0 refresh 20081101
I've implemented all the standard MDI window positioning and show how to use them.
But the important thing is that I've added the possibility to have specific data for every MDI window using an UDT structure allocated dynamically when each MDI child window is created (and deallocated when it is closed). Each window UDT in stored in one of the window user data and used with a pointer to it when inside the window callback. The whole process is very fast and also open the road to many personalized MDI child windows. The method is the same used when createing new windows controls or used in real compiled applications.
Regarding you indication of menu items disabled/enabled, I've checked many applications in C and other compiled basic and that job is usually a programmer responsability and not automatic by the windows MDI engine. So you (the programmer) have to add code that check MDI child activity and activate/deactivate relevant menu items.
Have a look and let me know is all ok or you feel something is still missing.
Ciao
Eros