In the previous article we examined one way in which a program can support multiple instances of a single type of window. In this one we are going to extend this concept further – developing a program that can support multiple instances of several different types of window, in this case three:-
The Button window – contains a list view and two buttons labelled Add and Remove. When the Add button is clicked a random integer is added to the list view, when the remove button is clicked the currently highlighted entry in the list view is removed.
The Date window – contains a list view and two buttons in the same way as the Button window but also contains a calendar gadget too, the window layout is altered to accommodate this additional control. When the Add button is clicked it is the currently selected date that is added to the list view.
The Track window – contains two track bars, with a value between 0 and 100, and a string gadget. When the track bars are moved the string gadget is updated with the value of the second track bar subtracted from the first.
Each window contains a menu bar with items to create a new instance of any of the three supported window types or to close the current window.
Things to notice about this program are:
In the Structures section 4 structures are defined. The first, BASEWINDOW, defines a WindowClass value and a Menu value – these values are common to each window type.
The remaining structures extend the BASEWINDOW structure with values for each of the unique controls that they require and which are not provided for by the BASEWINDOW structure.
In the Variables section note that again a map called ActiveWindows is created, however this time it is of integer type, it doesn’t use any of the defined structures. There is a good reason for this, we need to store three different structure types to make this program work – and we can’t do this in a single map.
Also notice that *EvtGadgets is defined using the BASEWINDOW structure.
Now look at the CreateButtonWindow procedure. As before we use #PB_Any to create the window and all the gadgets.
However this time the results are not stored directly in the ActiveWindows map. Instead we use the AllocateMemory function to allocate memory for a BUTTONWINDOW structure, we then store a pointer to this memory allocation in the ActiveWindows map. This is how we get around the problem of not being able to store all three of the different structures in the same map.
We also set the WindowClass value in the structure to #WindowClassButton to indicate which type of window, and therefore which type of structure, has been created – we will need to know this later on.
There are two more CreateWindow procedures this time – one for each class of the other window types. They work in a similar way to that described, differing only where the window’s gadgets are different and setting a different value in WindowClass.
Similarly we provide DestroyWindow and ResizeWindow procedures to take care of these functions.
We also provide a new procedure – EventsButtonWindow. This procedure knows what to do when any of the gadgets on the window are activated by the user. Similar procedures are provided for the other window types too.
In all these procedures we use the ActiveWindows map to retrieve the pointer to the memory allocation. We can then use this pointer to retrieve the references to the actual controls that we need to work with in each of these procedures:-
*ThisData = ActiveWindows(ThisKey)
*ThisData\ListView …
Each procedure only knows how to handle one type of window – so before we start work we check the WindowClass value to make sure that a window of the correct type has been supplied as the argument something like this:-
If *ThisData\WindowClass <> #WindowClassButton
The event loop is a bit different too. For each event type there is a determination like this:-
; Use *EvtGadgets\WindowClass to call the correct resize window procedure.
Select *EvtGadgets\WindowClass …
Although the memory allocations actually made by the CreateWindow procedures will be of the BUTTONWINDOW, DATEWINDOW or TRACKWINDOW type we can use *EvtGadgets this way because it is defined as the BASEWINDOW type and BASEWINDOW is the ancestor structure to the other structures.
Providing we don’t attempt to change any of the stored values using *EvtGadgets – and we’ve no reason to do so – all should be well.
Finally, we despatch the recorded event values in EvtWindow, EvtGadget, EvtType straight through to the event procedures and let them worry about getting the jobs done.
Code: Select all
;- Constants
#DateFormat = "%dd/%mm/%yyyy"
;- Enumerations
Enumeration
#WindowClassButton = 1
#WindowClassDate
#WindowClassTrack
EndEnumeration
; The menu commands will be the same on all the windows.
Enumeration
#mnuNewButton
#mnuNewDate
#mnuNewTrack
#mnuClose
EndEnumeration
;- Structures
Structure BASEWINDOW
WindowClass.i
Menu.i
EndStructure
Structure BUTTONWINDOW Extends BASEWINDOW
ListView.i
AddButton.i
RemoveButton.i
EndStructure
Structure DATEWINDOW Extends BASEWINDOW
Calendar.i
AddButton.i
RemoveButton.i
ListView.i
EndStructure
Structure TRACKWINDOW Extends BASEWINDOW
TrackBar1.i
TrackBar2.i
Label.i
Difference.i
EndStructure
;- Variables
; This map will track all the active windows as before, however as the structure for each window class
; differs we will be storing pointers to structures in the map not the gadget references directly.
NewMap ActiveWindows.i()
; These values will be used to give new windows a unique label.
Define.i Buttons, Dates, Tracks
; Event variables.
; Notice the type of *EvtGadgets.
Define.i Evt, EvtWindow, EvtGadget, EvtType, EvtMenu, EvtQuit
Define.s EvtWindowKey
Define *EvtGadgets.BASEWINDOW
;- Button Window
Procedure.i CreateButtonWindow()
; Creates a new Button window and gadgets, adds it to the tracking map,
; allocates memory for gadget references and stores these in the map.
Shared Buttons, ActiveWindows()
Protected *ThisData.BUTTONWINDOW
Protected.i ThisWindow
Protected.s ThisKey, ThisTitle
; Set the window caption.
Buttons + 1
ThisTitle = "Button Window " + StrU(Buttons)
; Open the window.
ThisWindow = OpenWindow(#PB_Any, 30, 30, 225, 300, ThisTitle, #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MinimizeGadget | #PB_Window_TitleBar)
; Set minimum window dimensions.
WindowBounds(ThisWindow, 220, 100, #PB_Ignore, #PB_Ignore)
; Check that the OpenWindow command worked.
If ThisWindow
; Convert the window reference to a string to use as the map key value.
ThisKey = StrU(ThisWindow)
; Allocate memory to store the gadget references in.
*ThisData = AllocateMemory(SizeOf(BUTTONWINDOW))
; Store the window reference and memory pointer values in the map.
ActiveWindows(ThisKey) = *ThisData
EndIf
; Check that the memory allocation worked.
If *ThisData
; Set the window class.
*ThisData\WindowClass = #WindowClassButton
; Create the menu bar.
*ThisData\Menu = CreateMenu(#PB_Any, WindowID(ThisWindow))
; If the menu creation worked, create the menu items.
If *ThisData\Menu
MenuTitle("Window")
MenuItem(#mnuNewButton, "New Button Window")
MenuItem(#mnuNewDate, "New Date Window")
MenuItem(#mnuNewTrack, "New Track Window")
MenuItem(#mnuClose, "Close Window")
EndIf
; Create the window gadgets.
*ThisData\ListView = ListViewGadget(#PB_Any, 10, 10, 200, 225)
*ThisData\AddButton = ButtonGadget(#PB_Any, 15, 245, 90, 30, "Add")
*ThisData\RemoveButton = ButtonGadget(#PB_Any, 115, 245, 90, 30, "Remove")
EndIf
; Return the reference to the new window.
ProcedureReturn ThisWindow
EndProcedure
Procedure.i DestroyButtonWindow(Window.i)
; Remove Window from the ActiveWindows map, release the allocated memory,
; close the window and set the quit flag, if appropriate.
Shared EvtQuit, ActiveWindows()
Protected *ThisData.BUTTONWINDOW
Protected.s ThisKey
; Convert the integer Window to a string.
ThisKey = StrU(Window)
; Obtain the reference structure pointer.
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained, if not stop.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop.
If *ThisData\WindowClass <> #WindowClassButton
ProcedureReturn #False
EndIf
; Release the memory allocation.
FreeMemory(*ThisData)
; Delete the map entry.
DeleteMapElement(ActiveWindows(), ThisKey)
; Close the window.
CloseWindow(Window)
; Check if there are still open windows.
If MapSize(ActiveWindows()) = 0
EvtQuit = #True
EndIf
; Set the successful return value.
ProcedureReturn #True
EndProcedure
Procedure.i ResizeButtonWindow(Window.i)
; Resize the child gadgets on Window.
Shared ActiveWindows()
Protected *ThisData.BUTTONWINDOW
Protected.i X, Y, W, H
Protected.s ThisKey
; Obtain the reference structure pointer.
ThisKey = StrU(Window)
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained, if not stop.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop.
If *ThisData\WindowClass <> #WindowClassButton
ProcedureReturn #False
EndIf
; Resize list view.
W = WindowWidth(Window) - 25
H = WindowHeight(Window) - 85
ResizeGadget(*ThisData\ListView, #PB_Ignore, #PB_Ignore, W, H)
; Recentre buttons.
X = WindowWidth(Window)/2 - 95
Y = WindowHeight(Window) - 65
ResizeGadget(*ThisData\AddButton, X, Y, #PB_Ignore, #PB_Ignore)
X = WindowWidth(Window)/2 + 5
ResizeGadget(*ThisData\RemoveButton, X, Y, #PB_Ignore, #PB_Ignore)
ProcedureReturn #True
EndProcedure
Procedure.i EventsButtonWindow(Window, Gadget, Type)
; Handle events for a button window.
Shared Buttons, ActiveWindows()
Protected *ThisData.BUTTONWINDOW
Protected.i NewValue, Index
Protected.s ThisKey
; Convert the integer Window to a string.
ThisKey = StrU(Window)
; Obtain the reference structure pointer.
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained, if not stop.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop.
If *ThisData\WindowClass <> #WindowClassButton
ProcedureReturn #False
EndIf
Select Gadget
Case *ThisData\AddButton
NewValue = Random(2147483647)
AddGadgetItem(*ThisData\ListView, -1, StrU(NewValue))
Case *ThisData\RemoveButton
Index = GetGadgetState(*ThisData\ListView)
If Index >= 0 And Index <= CountGadgetItems(*ThisData\ListView)
RemoveGadgetItem(*ThisData\ListView, Index)
EndIf
Case *ThisData\ListView
; Do nothing.
EndSelect
EndProcedure
;- Date Window
Procedure.i CreateDateWindow()
; Creates a new Date window and gadgets, adds it to the tracking map,
; allocates memory for gadget references and stores these.
Shared Dates, ActiveWindows()
Protected *ThisData.DATEWINDOW
Protected.i ThisWindow
Protected.s ThisKey, ThisTitle
Dates + 1
ThisTitle = "Date Window " + StrU(Dates)
ThisWindow = OpenWindow(#PB_Any, 30, 30, 310, 420, ThisTitle , #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MinimizeGadget | #PB_Window_TitleBar)
; Set minimum window dimensions.
WindowBounds(ThisWindow, 310, 245, #PB_Ignore, #PB_Ignore)
; Allocate memory to store the gadget references in.
If ThisWindow
ThisKey = StrU(ThisWindow)
*ThisData = AllocateMemory(SizeOf(DATEWINDOW))
ActiveWindows(ThisKey) = *ThisData
EndIf
; Check that the memory allocation worked.
If *ThisData
; Set the window class.
*ThisData\WindowClass = #WindowClassDate
; Create the menu bar.
*ThisData\Menu = CreateMenu(#PB_Any, WindowID(ThisWindow))
; If the menu creation worked, create the menu items.
If *ThisData\Menu
MenuTitle("Window")
MenuItem(#mnuNewButton, "New Button Window")
MenuItem(#mnuNewDate, "New Date Window")
MenuItem(#mnuNewTrack, "New Track Window")
MenuItem(#mnuClose, "Close Window")
EndIf
; Create the window gadgets.
*ThisData\Calendar = CalendarGadget(#PB_Any, 10, 10, 182, 162)
*ThisData\AddButton = ButtonGadget(#PB_Any, 210, 10, 90, 30, "Add")
*ThisData\RemoveButton = ButtonGadget(#PB_Any, 210, 45, 90, 30, "Remove")
*ThisData\ListView = ListViewGadget(#PB_Any, 10, 187, 290, 200)
EndIf
; Return the reference to the new window.
ProcedureReturn ThisWindow
EndProcedure
Procedure.i DestroyDateWindow(Window.i)
; Remove Window from the ActiveWindows map, release the allocated memory,
; close the window and set the quit flag, if appropriate.
Shared EvtQuit, ActiveWindows()
Protected *ThisData.DATEWINDOW
Protected.s ThisKey
; Convert the integer Window to a string.
ThisKey = StrU(Window)
; Obtain the reference structure pointer.
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop as this procedure can't destroy this window.
If *ThisData\WindowClass <> #WindowClassDate
ProcedureReturn #False
EndIf
; Release the memory allocation.
FreeMemory(*ThisData)
; Delete the map entry.
DeleteMapElement(ActiveWindows(), ThisKey)
; Close the window.
CloseWindow(Window)
; Check if there are still open windows.
If MapSize(ActiveWindows()) = 0
EvtQuit = #True
EndIf
; Set the successful return value.
ProcedureReturn #True
EndProcedure
Procedure.i ResizeDateWindow(Window.i)
; Resize the child gadgets on Window.
Shared ActiveWindows()
Protected *ThisData.DATEWINDOW
Protected.i X, Y, W, H
Protected.s ThisKey
; Obtain the reference structure pointer.
ThisKey = StrU(Window)
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained, if not stop.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop.
If *ThisData\WindowClass <> #WindowClassDate
ProcedureReturn #False
EndIf
; Resize list view.
W = WindowWidth(Window) - 20
H = WindowHeight(Window) - 220
ResizeGadget(*ThisData\ListView, #PB_Ignore, #PB_Ignore, W, H)
ProcedureReturn #True
EndProcedure
Procedure.i EventsDateWindow(Window, Gadget, Type)
; Handle events for a Date window.
Shared Buttons, ActiveWindows()
Protected *ThisData.DATEWINDOW
Protected.i NewValue, Index
Protected.s ThisKey
; Convert the integer Window to a string.
ThisKey = StrU(Window)
; Obtain the reference structure pointer.
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained, if not stop.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop.
If *ThisData\WindowClass <> #WindowClassDate
ProcedureReturn #False
EndIf
Select Gadget
Case *ThisData\AddButton
NewValue = GetGadgetState(*ThisData\Calendar)
AddGadgetItem(*ThisData\ListView, -1, FormatDate(#DateFormat, NewValue))
Case *ThisData\RemoveButton
Index = GetGadgetState(*ThisData\ListView)
If Index >= 0 And Index <= CountGadgetItems(*ThisData\ListView)
RemoveGadgetItem(*ThisData\ListView, Index)
EndIf
Case *ThisData\Calendar, *ThisData\ListView
; Do nothing.
EndSelect
EndProcedure
;- Track Window
Procedure.i CreateTrackWindow()
; Creates a new Track window and gadgets, adds it to the tracking map,
; allocates memory for gadget references and stores these.
Shared Tracks, ActiveWindows()
Protected *ThisData.TRACKWINDOW
Protected.i ThisWindow, ThisSum
Protected.s ThisKey, ThisTitle
Tracks + 1
ThisTitle = "Track Bar Window " + StrU(Tracks)
ThisWindow = OpenWindow(#PB_Any, 30, 30, 398, 130, ThisTitle, #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MinimizeGadget | #PB_Window_TitleBar)
; Set minimum window dimensions.
WindowBounds(ThisWindow, 135, 130, #PB_Ignore, 130)
; Allocate memory to store the gadget references in.
If ThisWindow
ThisKey = StrU(ThisWindow)
*ThisData = AllocateMemory(SizeOf(TRACKWINDOW))
ActiveWindows(ThisKey) = *ThisData
EndIf
; Check that the memory allocation worked.
If *ThisData
; Set the window class.
*ThisData\WindowClass = #WindowClassTrack
; Create the menu bar.
*ThisData\Menu = CreateMenu(#PB_Any, WindowID(ThisWindow))
; If the menu creation worked, create the menu items.
If *ThisData\Menu
MenuTitle("Window")
MenuItem(#mnuNewButton, "New Button Window")
MenuItem(#mnuNewDate, "New Date Window")
MenuItem(#mnuNewTrack, "New Track Window")
MenuItem(#mnuClose, "Close Window")
EndIf
; Create the window gadgets.
*ThisData\TrackBar1 = TrackBarGadget(#PB_Any, 10, 10, 375, 25, 0, 100, #PB_TrackBar_Ticks)
*ThisData\TrackBar2 = TrackBarGadget(#PB_Any, 10, 40, 375, 25, 0, 100, #PB_TrackBar_Ticks)
*ThisData\Label = TextGadget(#PB_Any, 10, 75, 80, 25, "Difference:")
*ThisData\Difference = StringGadget(#PB_Any, 90, 75, 290, 25, "0", #PB_String_ReadOnly)
EndIf
; Return the reference to the new window.
ProcedureReturn ThisWindow
EndProcedure
Procedure.i DestroyTrackWindow(Window.i)
; Remove Window from the ActiveWindows map, release the allocated memory,
; close the window and set the quit flag, if appropriate.
Shared EvtQuit, ActiveWindows()
Protected *ThisData.DATEWINDOW
Protected.s ThisKey
; Convert the integer Window to a string.
ThisKey = StrU(Window)
; Obtain the reference structure pointer.
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop as this procedure can't destroy this window.
If *ThisData\WindowClass <> #WindowClassTrack
ProcedureReturn #False
EndIf
; Release the memory allocation.
FreeMemory(*ThisData)
; Delete the map entry.
DeleteMapElement(ActiveWindows(), ThisKey)
; Close the window.
CloseWindow(Window)
; Check if there are still open windows.
If MapSize(ActiveWindows()) = 0
EvtQuit = #True
EndIf
; Set the successful return value.
ProcedureReturn #True
EndProcedure
Procedure.i ResizeTrackWindow(Window.i)
; Resize the child gadgets on Window.
Shared ActiveWindows()
Protected *ThisData.TRACKWINDOW
Protected.i X, Y, W, H
Protected.s ThisKey
; Obtain the reference structure pointer.
ThisKey = StrU(Window)
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained, if not stop.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop.
If *ThisData\WindowClass <> #WindowClassTrack
ProcedureReturn #False
EndIf
; Resize track bars.
W = WindowWidth(Window) - 20
ResizeGadget(*ThisData\TrackBar1, #PB_Ignore, #PB_Ignore, W, #PB_Ignore)
ResizeGadget(*ThisData\TrackBar2, #PB_Ignore, #PB_Ignore, W, #PB_Ignore)
; Resize string.
W = WindowWidth(Window) - 110
ResizeGadget(*ThisData\Difference, #PB_Ignore, #PB_Ignore, W, #PB_Ignore)
ProcedureReturn #True
EndProcedure
Procedure.i EventsTrackWindow(Window, Gadget, Type)
; Handle events for a Track window.
Shared Buttons, ActiveWindows()
Protected *ThisData.TRACKWINDOW
Protected.i NewValue
Protected.s ThisKey
; Convert the integer Window to a string.
ThisKey = StrU(Window)
; Obtain the reference structure pointer.
*ThisData = ActiveWindows(ThisKey)
; Check that a valid pointer was obtained, if not stop.
If *ThisData = 0
ProcedureReturn #False
EndIf
; Check that it is the correct window type, if not stop.
If *ThisData\WindowClass <> #WindowClassTrack
ProcedureReturn #False
EndIf
Select Gadget
Case *ThisData\TrackBar1, *ThisData\TrackBar2
NewValue = GetGadgetState(*ThisData\TrackBar1) - GetGadgetState(*ThisData\TrackBar2)
SetGadgetText(*ThisData\Difference, Str(NewValue))
Case *ThisData\Label, *ThisData\Difference
; Do nothing.
EndSelect
EndProcedure
;- Main
; Create the first window.
EvtWindow = CreateButtonWindow()
ResizeButtonWindow(EvtWindow)
;- Event loop
Repeat
Evt = WaitWindowEvent()
EvtWindow = EventWindow()
EvtWindowKey = StrU(EvtWindow)
EvtGadget = EventGadget()
EvtType = EventType()
EvtMenu = EventMenu()
*EvtGadgets = ActiveWindows(EvtWindowKey)
Select Evt
Case #PB_Event_Gadget
; Use *EvtGadgets\WindowClass to despatch events to the correct event procedure.
Select *EvtGadgets\WindowClass
Case #WindowClassButton
EventsButtonWindow(EvtWindow, EvtGadget, EvtType)
Case #WindowClassDate
EventsDateWindow(EvtWindow, EvtGadget, EvtType)
Case #WindowClassTrack
EventsTrackWindow(EvtWindow, EvtGadget, EvtType)
Default
; Do nothing
EndSelect
Case #PB_Event_Menu
Select EvtMenu
Case #mnuNewButton
EvtWindow = CreateButtonWindow()
ResizeButtonWindow(EvtWindow)
Case #mnuNewDate
EvtWindow = CreateDateWindow()
ResizeDateWindow(EvtWindow)
Case #mnuNewTrack
EvtWindow = CreateTrackWindow()
ResizeTrackWindow(EvtWindow)
Case #mnuClose
; Use *EvtGadgets\WindowClass to call the correct destroy window procedure.
Select *EvtGadgets\WindowClass
Case #WindowClassButton
DestroyButtonWindow(EvtWindow)
Case #WindowClassDate
DestroyDateWindow(EvtWindow)
Case #WindowClassTrack
DestroyTrackWindow(EvtWindow)
Default
; Do nothing
EndSelect
EndSelect
Case #PB_Event_CloseWindow
; Use *EvtGadgets\WindowClass to call the correct destroy window procedure.
Select *EvtGadgets\WindowClass
Case #WindowClassButton
DestroyButtonWindow(EvtWindow)
Case #WindowClassDate
DestroyDateWindow(EvtWindow)
Case #WindowClassTrack
DestroyTrackWindow(EvtWindow)
Default
; Do nothing
EndSelect
Case #PB_Event_SizeWindow
; Use *EvtGadgets\WindowClass to call the correct resize window procedure.
Select *EvtGadgets\WindowClass
Case #WindowClassButton
ResizeButtonWindow(EvtWindow)
Case #WindowClassDate
ResizeDateWindow(EvtWindow)
Case #WindowClassTrack
ResizeTrackWindow(EvtWindow)
Default
; Do nothing
EndSelect
EndSelect
Until EvtQuit = #True