Page 1 of 2

Gadgets, Windows and Modules..... oh my!

Posted: Fri Feb 26, 2016 4:42 pm
by SparrowhawkMMU
I was just wondering how long term PureBasic/SpiderBasic devs handled the Gadget overload syndrome which I am experiencing:

Currently I have a webapp with 6 windows. This is due to grow to somewhere around 10-12 windows by the time the app is complete. Each window has at least 10 gadgets, the more complex ones have anything up to 40-60.

Because SB is not object oriented, I cannot use polymorphism to name objects the same thing across windows. So I cannot for example use Window1.CloseButton or Window1.CloseButton.OnClicked()

Instead, I have, for example:

#CONTROL_Main_CloseButton on the main form, #CONTROL_Search_CloseButton on the search form, etc etc.

This is becoming very unwieldy.

One approach I was thinking of was namespacing each window in its own module. However, each of my windows' code needs access to a global "registry" Map and some other global variables and constants. Now I see that modules cannot access the global scope (so it is not really global, merely "mostly-global", as Miracle Max might say ;) ) Should I create a common module with the registry and constants in that and then use that module in each window module? Would that work?

Is this the way others are doing it or is there some better way to structure larger apps?

Other than controls/gadgets, I also prefix my windows with #WINDOW_, so for example #WINDOW_Main, #WINDOW_Search,. and then each Proc/Function is for example: Main_Open(), Main_GetLicenseeData(), Main_Close(), Search_Open(), Search_FetchResults() etc etc

Modules which allow me to namespace procs and hide private variables and code from the outside world seem attractive. My concern is that reading many articles on the web suggest that using modules in PB/SB quickly become unwieldy.

Any thoughts? If I am going to refactor, I'd rather do it now than when my app is complete :D

Re: Gadgets, Windows and Modules..... oh my!

Posted: Sat Feb 27, 2016 11:37 am
by Fred
I would use one module per window, and uses #PB_Any with global variables for gadget naming. It's a bit easier than constants when you have a lot of objects:

Code: Select all

DeclareModule MainWindow
  Declare Open()
  Declare Close()
EndDeclareModule


Module MainWindow
  Global Window, CloseButton
  
  Procedure OnCloseButton()
    Debug "Close button on main window"
    Close()
  EndProcedure
  
  Procedure Open()
    Window = OpenWindow(#PB_Any, 10, 10, 300, 200, "Main")
    CloseButton = ButtonGadget(#PB_Any, 10, 10, 100, 30, "Close")
    BindGadgetEvent(CloseButton, @OnCloseButton())
  EndProcedure
  
  Procedure Close()
    CloseWindow(Window)
  EndProcedure
EndModule



DeclareModule SearchWindow
  Declare Open()
  Declare Close()
EndDeclareModule


Module SearchWindow
  Global Window, CloseButton
  
  Procedure OnCloseButton()
    Debug "Close button on search window"
    Close()
  EndProcedure
  
  Procedure Open()
    Window = OpenWindow(#PB_Any, 10, 400, 300, 200, "Search")
    CloseButton = ButtonGadget(#PB_Any, 10, 10, 100, 30, "Close")
    BindGadgetEvent(CloseButton, @OnCloseButton())
  EndProcedure
  
  Procedure Close()
    CloseWindow(Window)
  EndProcedure
EndModule


MainWindow::Open()
SearchWindow::Open()
Event if it's not object oriented, it does look clean to me.

Re: Gadgets, Windows and Modules..... oh my!

Posted: Sat Feb 27, 2016 11:41 am
by Fred
And if you want "global" map and stuff, just put that in a common module, and use it everywhere you need:

Code: Select all

DeclareModule Common
  NewMap MyGlobalMap()
EndDeclareModule

Module Common : EndModule ; Empty declaration

UseModule Common ; Everything is available in main scope

MyGlobalMap("Test") = 10


DeclareModule Test
  Declare DisplayMap()
EndDeclareModule


Module Test
  UseModule Common ; MyGlobalMap() is available here
  
  Procedure DisplayMap()
    Debug MyGlobalMap("Test")
  EndProcedure
EndModule

Test::DisplayMap()

Re: Gadgets, Windows and Modules..... oh my!

Posted: Sat Feb 27, 2016 3:29 pm
by SparrowhawkMMU
Thanks guys. Some great advice there.

BTW, I had no idea that I could used gadget creation commands with variable assignment. Ie as functions.

Looks like I am going to be refactoring the app then! :)

Re: Gadgets, Windows and Modules..... oh my!

Posted: Sat Feb 27, 2016 3:38 pm
by Fred
You can find more info here about dynamic objects : http://www.spiderbasic.com/documentatio ... jects.html

Re: Gadgets, Windows and Modules..... oh my!

Posted: Sun Feb 28, 2016 9:21 am
by Fred
I added a new example (MultiWindows.sb) for the next version to illustrate this.

Re: Gadgets, Windows and Modules..... oh my!

Posted: Sun Feb 28, 2016 3:21 pm
by SparrowhawkMMU
That's great, many thanks Fred

I'm glad I asked the question before I got to the end of the app build ;)

Just one thing, the link to the ImageViewer example code page on the Help page you linked to does not work - it gives a Not Found (404) error

i.e. http://www.spiderbasic.com/documentatio ... er.sb.html

Re: Gadgets, Windows and Modules..... oh my!

Posted: Mon Feb 29, 2016 7:46 am
by Fred
Thank you, I removed the faulty link.

Re: Gadgets, Windows and Modules..... oh my!

Posted: Wed Mar 02, 2016 12:35 pm
by SparrowhawkMMU
I've hit another problem (maybe I have misunderstood something):

I have started restructuring my code into modules. I am now getting module name clashes because my modules use commonly used conventions.

For example:

I have an AuthToken module that has several public procs/functions including: Get(token.s), Set(token.s), Clear()
I have another module, Registry which also has Get(key.s), Set(key.s, value.s), Clear()

I need to use both in a module that encapsulates a window's logic. The syntax checker is telling me:
Line 17: Module public item 'Clear()' is already declared in global scope.
And indeed this is mentioned in the documentation.

However, this means that I would need to have methods like:
Registry::ClearRegistry() and AuthToken::ClearToken()

This hugely negates the readability gains of using modules. It seems to me that there is little advantage in this over my existing naming convention in the global scope of using AuthToken_Clear() and Registry_Clear()


Ideally, what would be great is a new keyword, eg something like

Code: Select all

UseModuleExplicit <modulename>
This would tell the module that is importing the other module that it can access that module but must use the explicit module name when referencing public variables/procs etc to avoid name clashes

eg:

Code: Select all

; Note: not showing implementation for AuthToken or Registry

DeclareModule AuthToken
	Declare Clear()
EndDeclareModule

DeclareModule Registry
	Declare Clear()
EndDeclareModule

DeclareModule MyWindow
	Declare Init()
	Declare Clear()
EndDeclareModule

Module MyWindow
	UseModuleExplicit AuthToken
	UseModuleExplicit Registry
	
	Procedure Init()		
		AuthToken::Clear()   
		Registry::Clear()
		Clear()   ; <-  local scope
	EndProcedure

	Procedure Clear()
		Debug "in local scope"
	EndProcedure
	
EndModule
What do others think of this approach? Is there a better/existing way around having to have unique public names per module when wanting to use more than one module in another module?

Fred, is this something you would consider adding? Or does it go against the design philosophy / is not doable?

Re: Gadgets, Windows and Modules..... oh my!

Posted: Wed Mar 02, 2016 1:24 pm
by Fred
I think you are mixing a bit 2 conventions:

1) If you plan to make your module available with UseModule, you should name your public functions with the module name in it, so there are unique.

2) If not, then you can name as you want and don't use UseModule, so will will get the prefix to identify which function is called. I tend to prefer this solution, as it's the easier to maintain.

Note: you don't have to use UseModule to actually access a module. A module is by definition accessible everywhere, in the main scope or in any module, as long you use the prefixed ModuleName:: syntax.

This code looks OK to me:

Code: Select all

; Note: not showing implementation for AuthToken or Registry

DeclareModule AuthToken
   Declare Clear()
EndDeclareModule

Module AuthToken 
  Procedure Clear()      
  EndProcedure
EndModule

DeclareModule Registry
  Declare Clear()
EndDeclareModule

Module Registry   
  Procedure Clear()      
  EndProcedure
EndModule
 

DeclareModule MyWindow
   Declare Init()
   Declare Clear()
EndDeclareModule

Module MyWindow
   Procedure Init()      
      AuthToken::Clear()   
      Registry::Clear()
      Clear()
   EndProcedure

   Procedure Clear()
      Debug "in local scope"
   EndProcedure
   
 EndModule
 
 MyWindow::Init()