UserGuide - Managing multiple windows with different content
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.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.
- 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.
*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 <> #WindowClassButtonThe event loop is a bit different too. For each event type there is a determination like this:
; Use *EventGadgets\WindowClass to call the correct resize window procedure. Select *EventGadgets\WindowClass ...Although the memory allocations actually made by the CreateWindow procedures will be of the BUTTONWINDOW, DATEWINDOW or TRACKWINDOW type we can use *EventGadgets this way because it is defined as the BASEWINDOW type and BASEWINDOW is the ancestor structure to the other structures.
;- Constants #DateFormat = "%dd/%mm/%yyyy" ;- Enumerations Enumeration #WindowClassButton = 1 #WindowClassDate #WindowClassTrack EndEnumeration ; The menu commands will be the same on all the windows. Enumeration #MenuNewButton #MenuNewDate #MenuNewTrack #MenuClose 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 *EventGadgets. Define.i Event, EventWindow, EventGadget, EventType, EventMenu, EventQuit Define.s EventWindowKey Define *EventGadgets.BASEWINDOW ;- Button Window Procedure.i CreateButtonWindow() ; Creates a new Button window and adds it to the tracking map, ; allocates memory for gadget references, creates the gadgets ; and stores these references in the memory structure. 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) ; Check that the OpenWindow command worked. If ThisWindow ; Set minimum window dimensions. WindowBounds(ThisWindow, 220, 100, #PB_Ignore, #PB_Ignore) ; 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)) EndIf ; Check that the memory allocation worked. If *ThisData ; Store the window reference and memory pointer values in the map. ActiveWindows(ThisKey) = *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(#MenuNewButton, "New Button Window") MenuItem(#MenuNewDate, "New Date Window") MenuItem(#MenuNewTrack, "New Track Window") MenuItem(#MenuClose, "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") Else ; Memory allocation failed. CloseWindow(ThisWindow) EndIf ; Set the return value. If ThisWindow > 0 And *ThisData > 0 ; Return the reference to the new window. ProcedureReturn ThisWindow Else ; Return 0 ProcedureReturn 0 EndIf 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 EventQuit, 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 EventQuit = #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) ; Recenter 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 adds it to the tracking map, ; allocates memory for gadget references, creates the gadgets ; and stores these references in the memory Structure. 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) ; Check that the OpenWindow command worked. If ThisWindow ; Set minimum window dimensions. WindowBounds(ThisWindow, 310, 245, #PB_Ignore, #PB_Ignore) ; 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(DATEWINDOW)) EndIf ; Check that the memory allocation worked. If *ThisData ; Store the window reference and memory pointer values in the map. ActiveWindows(ThisKey) = *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(#MenuNewButton, "New Button Window") MenuItem(#MenuNewDate, "New Date Window") MenuItem(#MenuNewTrack, "New Track Window") MenuItem(#MenuClose, "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) Else ; Memory allocation failed. CloseWindow(ThisWindow) EndIf ; Set the return value. If ThisWindow > 0 And *ThisData > 0 ; Return the reference to the new window. ProcedureReturn ThisWindow Else ; Return 0 ProcedureReturn 0 EndIf 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 EventQuit, 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 EventQuit = #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 adds it to the tracking map, ; allocates memory for gadget references, creates the gadgets ; and stores these references in the memory Structure. 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) ; Check that the OpenWindow command worked. If ThisWindow ; Set minimum window dimensions. WindowBounds(ThisWindow, 135, 130, #PB_Ignore, 130) ; 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(TRACKWINDOW)) EndIf ; Check that the memory allocation worked. If *ThisData ; Store the window reference and memory pointer values in the map. ActiveWindows(ThisKey) = *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(#MenuNewButton, "New Button Window") MenuItem(#MenuNewDate, "New Date Window") MenuItem(#MenuNewTrack, "New Track Window") MenuItem(#MenuClose, "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) Else ; Memory allocation failed. CloseWindow(ThisWindow) EndIf ; Set the return value. If ThisWindow > 0 And *ThisData > 0 ; Return the reference to the new window. ProcedureReturn ThisWindow Else ; Return 0 ProcedureReturn 0 EndIf 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 EventQuit, 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 EventQuit = #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. EventWindow = CreateButtonWindow() ResizeButtonWindow(EventWindow) ;- Event loop Repeat Event = WaitWindowEvent() EventWindow = EventWindow() EventWindowKey = StrU(EventWindow) EventGadget = EventGadget() EventType = EventType() EventMenu = EventMenu() *EventGadgets = ActiveWindows(EventWindowKey) Select Event Case #PB_Event_Gadget ; Check that a valid pointer was obtained. If *EventGadgets > 0 ; Use *EventGadgets\WindowClass to despatch events to the correct event procedure. Select *EventGadgets\WindowClass Case #WindowClassButton EventsButtonWindow(EventWindow, EventGadget, EventType) Case #WindowClassDate EventsDateWindow(EventWindow, EventGadget, EventType) Case #WindowClassTrack EventsTrackWindow(EventWindow, EventGadget, EventType) Default ; Do nothing EndSelect EndIf Case #PB_Event_Menu Select EventMenu Case #MenuNewButton EventWindow = CreateButtonWindow() If EventWindow > 0 ResizeButtonWindow(EventWindow) EndIf Case #MenuNewDate EventWindow = CreateDateWindow() If EventWindow > 0 ResizeDateWindow(EventWindow) EndIf Case #MenuNewTrack EventWindow = CreateTrackWindow() If EventWindow > 0 ResizeTrackWindow(EventWindow) EndIf Case #MenuClose ; Check that a valid pointer was obtained. If *EventGadgets > 0 ; Use *EventGadgets\WindowClass to call the correct destroy window procedure. Select *EventGadgets\WindowClass Case #WindowClassButton DestroyButtonWindow(EventWindow) Case #WindowClassDate DestroyDateWindow(EventWindow) Case #WindowClassTrack DestroyTrackWindow(EventWindow) Default ; Do nothing EndSelect EndIf EndSelect Case #PB_Event_CloseWindow ; Check that a valid pointer was obtained. If *EventGadgets > 0 ; Use *EventGadgets\WindowClass to call the correct destroy window procedure. Select *EventGadgets\WindowClass Case #WindowClassButton DestroyButtonWindow(EventWindow) Case #WindowClassDate DestroyDateWindow(EventWindow) Case #WindowClassTrack DestroyTrackWindow(EventWindow) Default ; Do nothing EndSelect EndIf Case #PB_Event_SizeWindow If *EventGadgets > 0 ; Use *EventGadgets\WindowClass to call the correct resize window procedure. Select *EventGadgets\WindowClass Case #WindowClassButton ResizeButtonWindow(EventWindow) Case #WindowClassDate ResizeDateWindow(EventWindow) Case #WindowClassTrack ResizeTrackWindow(EventWindow) Default ; Do nothing EndSelect EndIf EndSelect Until EventQuit = #True
UserGuide Navigation
< Previous: Dynamic numbering of windows and gadgets | Overview | Next: Other Compiler keywords >