Page 1 of 1

Multiple instances of the same window

Posted: Wed Aug 06, 2008 10:10 pm
by the.weavster
Firstly I must start with a confession: for the past x number of years I have only really used PureBasic to create shared libraries which I've used with my programs that I've created with an alternative basic. Recently I've become irritated by the fact the executables created by this other basic are REALly big and REALly slow. However it is an OOP language and I have become so used to objects I now struggle to comprehend a world without them.

I started contemplating finding yet another alternative language before deciding maybe I don't need objects at all, maybe I can survive without them, maybe PureBasic, the compiler of small, fast executables was calling me home.

One of the things I've liked about OOP is being able to design a window, complete with it's own event handlers, and then being able to open any number of instances of it at run time. So this was the first thing I've tried to recreate in PureBasic and this is my first attempt - it's straight off the press so it might (probably) have some imperfections.

I have posted it here for any/all of the following reasons:
1) in the hope it's useful to someone else
or
2) someone else is going to show me a better way of doing it
or
3) someone's going to point out my bugs

The main window definition and event handlers 'pb_MainWindow.pb':

Code: Select all

;a structure and linked list for controlling instances of this window 
;the structure has an element for each gadget on the window
;***EDIT***
;Structure MainWindow
;  WindowID.l
;  txtHello.l
  ;we could even add other properties here if we wanted
;EndStructure
;Global NewList mw.MainWindow()
;***END OF EDIT***

;the event handlers for this window
Procedure MainWindowEvents(WinID.l,Event.l,item.l,EventType.l)
  ResetList(mw())
  While NextElement(mw())
    If mw()\WindowID = WinID
      Select Event
        Case -1
          DeleteElement(mw())
        Case #PB_Event_Gadget
          Select item
            Case mw()\txtHello
              If EventType = #PB_EventType_Change
                SetGadgetText(mw()\txtHello,"Why did you do that?")
              EndIf
          EndSelect
        Case #PB_Event_Menu
  
      EndSelect
      ProcedureReturn
    EndIf
  Wend
EndProcedure

;the window definition
Procedure.l MainWindow()
  lWindowID =  OpenWindow(#PB_Any, 10, 10, 500, 500, "PureBasic - Window Instances Demonstration", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget)
  If CreateGadgetList(WindowID(lWindowID))
    lHello.l = StringGadget(#PB_Any, 10, 16, 180, 24, "Multiple Instances")
  EndIf 
  WLoop.l = @MainWindowEvents() ;drop off the pointer to this forms event loop, this will get picked up when the procedure returns 
  ;add an element to the list for our new window
  AddElement(mw())
  mw()\WindowID = lWindowID
  mw()\txtHello = lHello
ProcedureReturn lWindowID
EndProcedure
The child winow that creates clones of itself (that then communicate with each other) 'pb_ChildWindow.pb':

Code: Select all

;a structure and linked list for controlling instances of this window 
;the structure has an element for each gadget on the window
;***EDIT***
;Structure ChildWindow
;  WindowID.l
;  btnHello.l
;EndStructure
;Global NewList cw.ChildWindow()
;***END OF EDIT***

;a procedure just to demonstrate communicating between windows
Procedure SetTextToWinID(pWinID.l,cWinID.l)
  ;first one direction
  ResetList(cw())
  While NextElement(cw())
    If cw()\WindowID = cWinID
      SetGadgetText(cw()\btnHello,Str(pWinID))
      Break
    EndIf
  Wend
  ;then the other
  ResetList(cw())
  While NextElement(cw())
    If cw()\WindowID = pWinID
      SetGadgetText(cw()\btnHello,Str(cWinID))
      Break
    EndIf
  Wend  
EndProcedure

;the event handlers for this window
Procedure ChildWindowEvents(WinID.l,Event.l,item.l,EventType.l)
  ResetList(cw())
  While NextElement(cw())
    If cw()\WindowID = WinID
      Select Event
        Case -1 ;I've used this to denote the window being closed
          DeleteElement(cw())
        Case #PB_Event_Gadget
          Select item
            Case cw()\btnHello
                lCloneID.l = ProjectWindow_Add(*WinChild,0,WinID)
                SetTextToWinID(WinID,lCloneID) 
          EndSelect
        Case #PB_Event_Menu
  
      EndSelect
    ProcedureReturn
    EndIf
  Wend
EndProcedure

Procedure.l ChildWindow()
  lWindowID = OpenWindow(#PB_Any, 100, 200, 300, 100, "Click me to clone me", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget)
  If CreateGadgetList(WindowID(lWindowID)) 
    lHello.l = ButtonGadget(#PB_Any, 10, 16, 180, 24, "Click Me")
  EndIf 
  WLoop.l = @ChildWindowEvents() ;drop off the pointer to this forms event loop, this will get picked up when the procedure returns
  ;add this instance to the linked list
  AddElement(cw())
  cw()\WindowID = lWindowID
  cw()\btnHello = lHello
ProcedureReturn lWindowID
EndProcedure
And the main program file:

Code: Select all

Global AppMain.l ;the id of the main application window
Global WLoop.l ;a drop off point for the memory address of the event loop for the window we're opening

;we need a pointer for each window creation procedure (the procedure, not each individual instance of a window)
Global *WinMain
Global *WinChild

;a structure and linked list for monitoring currently open windows
Structure ProjectWindow
  WindowID.l
  WindowType.l
  EventLoop.l
  CalledFromID.l ;so a window can determine which other window called it
EndStructure
Global NewList pw.ProjectWindow()

;***EDIT***
;a structure and linked list for controlling instances of the main window 
;the structure has an element for each gadget on the window
Structure MainWindow
  WindowID.l
  txtHello.l
  ;we could even add other properties here if we wanted
EndStructure
Global NewList mw.MainWindow()

;a structure and linked list for controlling instances of the child window 
;the structure has an element for each gadget on the window
Structure ChildWindow
  WindowID.l
  btnHello.l
EndStructure
Global NewList cw.ChildWindow()
;***END OF EDIT***

;get the id of the calling window
Procedure.l ProjectWindow_CalledFrom(WinID.l)
  ResetList(pw())
  While NextElement(pw())
    If pw()\WindowID = WinID
      ProcedureReturn pw()\CalledFromID
    EndIf
  Wend
  ProcedureReturn -1 ;no parent or parent has been closed
EndProcedure

;to create a new instance of a particular window
Procedure.l ProjectWindow_Add(WinType.l,MainAppWindow.l,CallingWin.l)
  lresult.l = CallFunctionFast(WinType)
  If MainAppWindow = 1:AppMain = lresult:EndIf
  ;add the new window to the control list
  AddElement(pw())
  pw()\WindowID = lresult
  pw()\WindowType = WinType
  pw()\EventLoop = WLoop
  pw()\CalledFromID
  ProcedureReturn lresult
EndProcedure

;to close a particular instance of a window
Procedure ProjectWindow_Remove(WinID.l)
  ResetList(pw())
  While NextElement(pw())
    If pw()\WindowID = WinID
      lType.l = pw()\WindowType
      DeleteElement(pw())
      CallFunctionFast(pw()\EventLoop,WinID,-1,0,0)
      CloseWindow(WinID)
      ProcedureReturn
    EndIf
  Wend
EndProcedure

;for passing an event to the relevant windows event handlers
Procedure ProjectEvent(WinID.l,Event.l,item.l,EventType.l)
  ResetList(pw())
  While NextElement(pw())
    If pw()\WindowID = WinID
      CallFunctionFast(pw()\EventLoop,WinID,Event,item,EventType)
      ProcedureReturn
    EndIf
  Wend
EndProcedure

;include each window and set the pointer to the window definition procedure
XIncludeFile "pb_MainWindow.pb"
*WinMain = @MainWindow()
XIncludeFile "pb_ChildWindow.pb"
*WinChild = @ChildWindow()

;and we're away

;open the main app window
lMain.l = ProjectWindow_Add(*WinMain,1,-1)

;an instance of the child window called by the main window
ProjectWindow_Add(*WinChild,0,lMain)

Repeat ;the main event loop that branches off to form instances when applicable
  EventID = WaitWindowEvent()
  EventWindowID = EventWindow()
  If EventID = #PB_Event_CloseWindow And EventWindowID <> AppMain:ProjectWindow_Remove(EventWindowID):EndIf
  EventKindID.l = EventType()
  If EventID = #PB_Event_Gadget
    EventItemID.l = EventGadget()
  ElseIf EventID = #PB_Event_Menu
    EventItemID.l = EventMenu()
  EndIf
  ProjectEvent(EventWindowID,EventID,EventItemID,EventKindID)    
Until EventID = #PB_Event_CloseWindow And EventWindowID = AppMain

End
I realise there's a little overhead in having to obtain the correct position in the linked lists at the start of each procedure but I'm not too worried about the odd millisecond here and there.

Posted: Wed Aug 06, 2008 10:31 pm
by SFSxOI
very nice, thank you :)

don't worry about the odd millisecond, I got patience :)

Posted: Thu Aug 07, 2008 9:09 am
by the.weavster
I did this last thing last night and it occurred to me the moment my head hit the pillow that all the declarations of the structures and linked lists really need to be at the start of the main program code or else you could get the '...is not an array or linked list' compiler error if a window called another window that was included later than itself (I think - I'm a little rusty).

I've edited the example to make it so.

Posted: Thu Aug 07, 2008 11:12 pm
by Rescator
To save one procedure call you might ponder putting the main window repeat loop inside the ProjectEvent procedure. Nothing dramatic, it just saves a few cpu cycles here and there each time a window message is sent to your program.

Although PB lists are fast, you can gain more speed by using arrays.
I assume that windows are not dynamically generated during runtime,
but you know while programming how many windows there will be.

As PB already have constants for it's own window id's,
as long as you make sure all windows have values from say 1 to n you can simply use an array. Might be a pain if you suddenly decide to remove a window as that would create a gap in the id numbers.

Anyway, good to see your doing things the procedural way. I never liked OOP myself, besides, anything OOP can do, can also be done procedurally. (after all OOP is built on top of procedural code so)

I prefer modular and self contained procedures where possible myself, and it looks like your on the right track here.

Posted: Fri Aug 08, 2008 10:43 am
by the.weavster
Rescator wrote:Although PB lists are fast, you can gain more speed by using arrays.
I assume that windows are not dynamically generated during runtime,
but you know while programming how many windows there will be.
No, I don't know how many instances of the window the user might require at runtime so my program can not have a hard limit, that's why I've used linked lists to provide control of the windows.
Rescator wrote:I never liked OOP myself, besides, anything OOP can do, can also be done procedurally. (after all OOP is built on top of procedural code so)
I have to admit I do like OOP but I'm beginning to realise I can achieve much of the same things using structures, pointers and linked lists and get a much whizzier executable as a result.

Thanks for the tip about putting the main window repeat loop inside the ProjectEvent procedure, I'll take that on board.