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

Just starting out? Need help? Post your questions and find answers here.
User avatar
SparrowhawkMMU
Posts: 291
Joined: Wed Aug 19, 2015 3:02 pm
Location: United Kingdom

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

Post 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
Fred
Site Admin
Posts: 1821
Joined: Mon Feb 24, 2014 10:51 am

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

Post 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.
Fred
Site Admin
Posts: 1821
Joined: Mon Feb 24, 2014 10:51 am

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

Post 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()
User avatar
SparrowhawkMMU
Posts: 291
Joined: Wed Aug 19, 2015 3:02 pm
Location: United Kingdom

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

Post 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! :)
Last edited by SparrowhawkMMU on Tue Mar 01, 2016 1:16 pm, edited 1 time in total.
Fred
Site Admin
Posts: 1821
Joined: Mon Feb 24, 2014 10:51 am

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

Post by Fred »

You can find more info here about dynamic objects : http://www.spiderbasic.com/documentatio ... jects.html
Fred
Site Admin
Posts: 1821
Joined: Mon Feb 24, 2014 10:51 am

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

Post by Fred »

I added a new example (MultiWindows.sb) for the next version to illustrate this.
User avatar
SparrowhawkMMU
Posts: 291
Joined: Wed Aug 19, 2015 3:02 pm
Location: United Kingdom

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

Post 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
Fred
Site Admin
Posts: 1821
Joined: Mon Feb 24, 2014 10:51 am

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

Post by Fred »

Thank you, I removed the faulty link.
User avatar
SparrowhawkMMU
Posts: 291
Joined: Wed Aug 19, 2015 3:02 pm
Location: United Kingdom

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

Post 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?
Fred
Site Admin
Posts: 1821
Joined: Mon Feb 24, 2014 10:51 am

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

Post 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()
Post Reply