Class hierarchy / data structure

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
Hellbent
Posts: 2112
Joined: 23 Sep 2017, 13:34

Class hierarchy / data structure

13 Apr 2022, 03:32

Hi I'm hoping that someone can help me out with a recurring problem that I'm facing to do with setting up classes to work more closely together.

In my current project I have these components, each have a class.

ControlSystem ( msg handler )

Menu ( context menu type thingy )

Tabs

Panels

Buttons

This is as I see it.

Code: Select all

class Main	{

	_SetUp(){
		
		This.ControlSystem := {} ; msg handling
		
		This.ContextMenu := {} ; 1 Context menu
		
		This.ContextMenu.Tabs := [] ; 4 Tabs
		This.ContextMenu.Tabs[ 1 ] := {}
		
		This.ContextMenu.Tabs[ 1 ].Panels := [] ; 9 Panels in Tab 1, various amounts of panels in the others.
		This.ContextMenu.Tabs[ 1 ].Panels[ 1 ] := {} 
		
		This.ContextMenu.Tabs[ 1 ].Panels[ 1 ].Buttons := [] ; between 3-7 buttons
		This.ContextMenu.Tabs[ 1 ].Panels[ 1 ].Buttons[ 1 ] := {}
		
	}

}
On top of that is the tons of data that is used to populate everything.

I have a working solution to this problem but it is just held together with little more than duct tape and there is no way I would want to ever have to go in and edit something a year later.

My knowledge of classes is very limited so if you can see how I can create a good structure please help, I know nothing of extending classes or any of that and likely never will without some assistance on the matter in a situation exactly like this one. As I said, this is something that comes up often enough that I would like to have a better strategy to deal with it than the stuff I can normally get away with doing.

To help paint a better picture of what I need this for here is what the current prototype looks and functions (more or less).
Animation.gif
Animation.gif (585.5 KiB) Viewed 396 times
If you think you can see a good structure or anything at all please help.
Thanks in advance.
User avatar
Cuadrix
Posts: 236
Joined: 07 May 2017, 08:26

Re: Class hierarchy / data structure

13 Apr 2022, 06:42

Usually I do something like this:

1. Each unique component should have it's own class.

2. Each component should keep track of their parent and children,
i.e. store reference to parent in a variable/property and children in an array of object instances

3. Each component's object should be instantiated one after another based on the envisioned hierarchy

Pseudocode:

Code: Select all

 
class Menu {
    Children := [] ; array of child object instances
     
    ; Constructor
    __New(Parent, Idx, Width, Height, X, Y) {
        this.Parent = Parent
        this.Idx = Idx
        this.Width = Width
        this.Height = Height
        this.X = X
        this.Y = Y
         
        ; Push child component (this) to parent component's child instance array
        if (this.Parent) {
            then.Parent.Children.Push(this)
        }
         
        ; Either immediately draw the component or leave the job to an independent method e.g. Draw(), Create()
    }
     
    ; For use by parent to (re)draw child components in the event of an update
    Draw() {
        ; 1. Execute this components drawing operation first
        ; Calculate relative position...
        ; X = this.Parent.X + this.X
        ; Y = this.Parent.Y + this.Y
        ; etc...
         
         
        ; 2. Now call draw function of each child component
        For i, child in this.Children
            child.Draw()
    }
     
    ; More specific methods here etc...
}
 
class Window {
    Children := [] ; array of child object instances
     
    ; Constructor
    __New(Parent, Idx, Width, Height, X, Y) {
        this.Parent = Parent
        this.Idx = Idx
        this.Width = Width
        this.Height = Height
        this.X = X
        this.Y = Y
         
        ; Push child component (this) to parent component's child instance array
        if (this.Parent) {
            then.Parent.Children.Push(this)
        }
         
        ; Either immediately draw the component or leave the job to an independent method e.g. Draw(), Create()
    }
     
    ; For use by parent to (re)draw child components in the event of an update
    Draw() {
        ; Steps:
        ; 1. Execute this components drawing operation first
        ; Calculate relative position...
        ; X = this.Parent.X + this.X
        ; Y = this.Parent.Y + this.Y
        ; etc...
         
        ; 2. Now call draw function of each child component
        For i, child in this.Children
            child.Draw()
    }
     
    ; More specific methods here etc...
}
 
; ****************************************
 
; First create parent window
MainWindow := new Window(0, 1, 500, 500, 200, 200)
 
; Then create window's child components, (pos relative to parent)
MainMenu := new Menu(MainWindow, 2, 500, 32, 0, 0)

; EditButton := new Button(MainMenu, 3, 80, 32, 0, 0)
;
; etc...
;
; ****************************************
Now, I'm not exactly sure whether it's possible to store object references in normal arrays in Autohotkey, so the method might have to be tweaked a little
User avatar
Hellbent
Posts: 2112
Joined: 23 Sep 2017, 13:34

Re: Class hierarchy / data structure

14 Apr 2022, 06:53

Thank you @Cuadrix.

That works well for the structure of the windows, tabs, controls, but it leaves open the control system.
How would I go about implementing the message handling using this setup?

In my case things go up and down the chain. For example, the context menu can hide all the panels / buttons attached to it, but panels not attached are only hidden by some of the buttons, and some buttons hide everything including themselves regardless of whether or not the panel they are on is attached to the menu.

Some of the controls change the current tab. Some of the controls move the windows and panels.
Some of the buttons launch separate Overlays with there own programing that only exist while the button is pressed.


Second question. This one isn't that important but insights might be useful.
Looking at the code I will be adding below you will see that much of that construction has loops written all over it. The question is what is a good way to organize and collect the data that is used to populate the different panels and buttons?

Much of that is handled by the tab and button classes as defaults, but there are usually a large number of unique values that can't be defaulted and sometimes requires reading from a file or getting info from other sources. The place I can see as the clear dividing line is at the panel level. Panels act largely as independent windows that just happen to sometimes sit inside a tab in a menu. While the panels in this case are all roughly the same with mainly the number of buttons they have as the thing that separates them and the sub-routines they launch. Other panels have very different control layouts and may or may not detach from the menu.

I have setup the example code with most of the elements I am using except for the windows launched by the buttons ( I'll add them in later)
I have it so that This.Selected.Tab can be set to 1 or 2 manually but needs a system for the buttons to trigger the change.

If you have any comments on the example and the way I set it up please let me know.
20220414072251.png
20220414072251.png (15.28 KiB) Viewed 300 times

Code: Select all

#SingleInstance, Force

return
GuiClose:
GuiContextMenu:
*ESC::ExitApp


class Main	{
	static init := Main._SetUp()
	_Setup(){
		local x 
		
		This.HiddenState := 0
		
		This.Selected := {}
		This.Selected.Tab := 2
		This.Selected.Panel := 1
		This.Selected.Button := 1
		
		This.Index := 0
		
		This.ContextMenu := New ContextMenuClass( ++This.Index , A_ScreenWidth - 600 , 100 , 550 , 500 )
		
		This.ContextMenu.Tabs := []
		This.ContextMenu.Buttons := []
		
				;&&&&&&&&&&&& 
				;Buttons 
				
				x := 60
				Loop, 4
					This.ContextMenu.Buttons[ A_Index ] := New ButtonsClass( This.ContextMenu , ++This.Index , x += 45 , 5 , 30 , 30 , "42464a" , Method := "_SwitchTabs" )
				
				;~ This.ContextMenu.Buttons[ 5 ] := { X: "" , Y: "" , W: "" , H: "" , Method: "_MoveContextMenu" }
				;~ This.ContextMenu.Buttons[ 6 ] := { X: "" , Y: "" , W: "" , H: "" , Method: "_LaunchAll" }
				;~ This.ContextMenu.Buttons[ 7 ] := { X: "" , Y: "" , W: "" , H: "" , Method: "_ReAttachAll" }
				
				;***************************************************************************************************
				;Tab 1
				
				This.ContextMenu.Tabs[ 1 ] := New TabsClass( This.ContextMenu , ++This.Index , 50 , 50 , 450 , 400 )
				This.ContextMenu.Tabs[ 1 ].Panels := []
						
						;########################### ;Tab 1
						;Panel 1
						
						This.ContextMenu.Tabs[ 1 ].Panels[ 1 ] := New PanelsClass( This.ContextMenu.Tabs[ 1 ] , ++This.Index , 10 , 10 , 430 , 50 )
						This.ContextMenu.Tabs[ 1 ].Panels[ 1 ].Buttons := []
								
								;&&&&&&&&&&&& ;Tab 1 , Panel 1
								;Buttons 
								
								x := 60
								Loop, 6	
									This.ContextMenu.Tabs[ 1 ].Panels[ 1 ].Buttons[ A_Index ] := New ButtonsClass( This.ContextMenu.Tabs[ 1 ].Panels[ 1 ] , ++This.Index , x += 45 , 5 , 40 , 40 )
								
								This.ContextMenu.Tabs[ 1 ].Panels[ 1 ].Buttons[ 7 ] := New ButtonsClass( This.ContextMenu.Tabs[ 1 ].Panels[ 1 ] , ++This.Index , This.ContextMenu.Tabs[ 1 ].Panels[ 1 ].W - 45 , 5 , 40 , 40 , "ff0000" )
						
						;########################### ;Tab 1
						;Panel 2
						
						This.ContextMenu.Tabs[ 1 ].Panels[ 2 ] := New PanelsClass( This.ContextMenu.Tabs[ 1 ] , ++This.Index , 10 , 70 , 430 , 50 )
						This.ContextMenu.Tabs[ 1 ].Panels[ 2 ].Buttons := []
								
								;&&&&&&&&&&&& ;Tab 1 , Panel 2
								;Buttons 
								
								x := 60
								Loop, 4	
									This.ContextMenu.Tabs[ 1 ].Panels[ 2 ].Buttons[ A_Index ] := New ButtonsClass( This.ContextMenu.Tabs[ 1 ].Panels[ 2 ] , ++This.Index , x += 45 , 5 , 40 , 40 )
								
								This.ContextMenu.Tabs[ 1 ].Panels[ 2 ].Buttons[ 5 ] := New ButtonsClass( This.ContextMenu.Tabs[ 1 ].Panels[ 2 ] , ++This.Index , This.ContextMenu.Tabs[ 1 ].Panels[ 2 ].W - 45 , 5 , 40 , 40 , "ff0000" )
						
						;########################### ;Tab 1 
						;Panel 3
						
						This.ContextMenu.Tabs[ 1 ].Panels[ 3 ] := New PanelsClass( This.ContextMenu.Tabs[ 1 ] , ++This.Index , 10 , 130 , 430 , 50 )
						This.ContextMenu.Tabs[ 1 ].Panels[ 3 ].Buttons := []
								
								;&&&&&&&&&&&& ;Tab 1 , Panel 3
								;Buttons 
								
								x := 60
								Loop, 3	
									This.ContextMenu.Tabs[ 1 ].Panels[ 3 ].Buttons[ A_Index ] := New ButtonsClass( This.ContextMenu.Tabs[ 1 ].Panels[ 3 ] , ++This.Index , x += 45 , 5 , 40 , 40 )
								
								This.ContextMenu.Tabs[ 1 ].Panels[ 3 ].Buttons[ 4 ] := New ButtonsClass( This.ContextMenu.Tabs[ 1 ].Panels[ 3 ] , ++This.Index , This.ContextMenu.Tabs[ 1 ].Panels[ 3 ].W - 45 , 5 , 40 , 40 , "ff0000" )
							
			
				;***************************************************************************************************
				;Tab 2
				
				This.ContextMenu.Tabs[ 2 ] := New TabsClass( This.ContextMenu , ++This.Index , 50 , 50 , 450 , 400 )
				This.ContextMenu.Tabs[ 2 ].Panels := []
						
						;########################### ;Tab 2
						;Panel 1
						
						This.ContextMenu.Tabs[ 2 ].Panels[ 1 ] := New PanelsClass( This.ContextMenu.Tabs[ 2 ] , ++This.Index , 10 , 10 , 430 , 50 )
						This.ContextMenu.Tabs[ 2 ].Panels[ 1 ].Buttons := []
								
								;&&&&&&&&&&&& ;Tab 2 , Panel 1
								;Buttons 
								
								x := 60
								Loop, 4	
									This.ContextMenu.Tabs[ 2 ].Panels[ 1 ].Buttons[ A_Index ] := New ButtonsClass( This.ContextMenu.Tabs[ 2 ].Panels[ 1 ] , ++This.Index , x += 45 , 5 , 40 , 40 )
								
								This.ContextMenu.Tabs[ 2 ].Panels[ 1 ].Buttons[ 7 ] := New ButtonsClass( This.ContextMenu.Tabs[ 2 ].Panels[ 1 ] , ++This.Index , This.ContextMenu.Tabs[ 2 ].Panels[ 1 ].W - 45 , 5 , 40 , 40 , "ff0000" )
						
						;########################### ;Tab 2
						;Panel 2
						
						This.ContextMenu.Tabs[ 2 ].Panels[ 2 ] := New PanelsClass( This.ContextMenu.Tabs[ 2 ] , ++This.Index , 10 , 70 , 430 , 50 )
						This.ContextMenu.Tabs[ 2 ].Panels[ 2 ].Buttons := []
								
								;&&&&&&&&&&&& ;Tab 2 , Panel 2
								;Buttons 
								
								x := 60
								Loop, 6	
									This.ContextMenu.Tabs[ 2 ].Panels[ 2 ].Buttons[ A_Index ] := New ButtonsClass( This.ContextMenu.Tabs[ 2 ].Panels[ 2 ] , ++This.Index , x += 45 , 5 , 40 , 40 )
								
								This.ContextMenu.Tabs[ 2 ].Panels[ 2 ].Buttons[ 5 ] := New ButtonsClass( This.ContextMenu.Tabs[ 2 ].Panels[ 2 ] , ++This.Index , This.ContextMenu.Tabs[ 2 ].Panels[ 2 ].W - 45 , 5 , 40 , 40 , "ff0000" )
						
						;########################### ;Tab 2 
						;Panel 3
						
						This.ContextMenu.Tabs[ 2 ].Panels[ 3 ] := New PanelsClass( This.ContextMenu.Tabs[ 2 ] , ++This.Index , 10 , 130 , 430 , 50 )
						This.ContextMenu.Tabs[ 2 ].Panels[ 3 ].Buttons := []
								
								;&&&&&&&&&&&& ;Tab 2 , Panel 3
								;Buttons 
								
								x := 60
								Loop, 5	
									This.ContextMenu.Tabs[ 2 ].Panels[ 3 ].Buttons[ A_Index ] := New ButtonsClass( This.ContextMenu.Tabs[ 2 ].Panels[ 3 ] , ++This.Index , x += 45 , 5 , 40 , 40 )
								
								This.ContextMenu.Tabs[ 2 ].Panels[ 3 ].Buttons[ 4 ] := New ButtonsClass( This.ContextMenu.Tabs[ 2 ].Panels[ 3 ] , ++This.Index , This.ContextMenu.Tabs[ 2 ].Panels[ 3 ].W - 45 , 5 , 40 , 40 , "ff0000" )
						
						;########################### ;Tab 2 
						;Panel 4
						
						This.ContextMenu.Tabs[ 2 ].Panels[ 4 ] := New PanelsClass( This.ContextMenu.Tabs[ 2 ] , ++This.Index , 10 , 190 , 430 , 50 )
						This.ContextMenu.Tabs[ 2 ].Panels[ 4 ].Buttons := []
								
								;&&&&&&&&&&&& ;Tab 2 , Panel 3
								;Buttons 
								
								x := 60
								Loop, 6	
									This.ContextMenu.Tabs[ 2 ].Panels[ 4 ].Buttons[ A_Index ] := New ButtonsClass( This.ContextMenu.Tabs[ 2 ].Panels[ 4 ] , ++This.Index , x += 45 , 5 , 40 , 40 )
								
								This.ContextMenu.Tabs[ 2 ].Panels[ 4 ].Buttons[ 7 ] := New ButtonsClass( This.ContextMenu.Tabs[ 2 ].Panels[ 4 ] , ++This.Index , This.ContextMenu.Tabs[ 2 ].Panels[ 4 ].W - 45 , 5 , 40 , 40 , "ff0000" )
								
		This.ContextMenu.Draw()
		
	}

}

class ContextMenuClass	{
	Children := []
	__New( Index , X , Y , W , H ){
		
		Gui, New, +AlwaysOnTop +HwndHwnd
		This.Hwnd := hwnd
		This.Index := Index
		This.X := X
		This.Y := Y
		This.W := W
		This.H := H
	}
	Draw(){
		Gui, % This.Hwnd ":Show", % "x" This.x " y" This.y " w" This.w " h" This.h " NA" , % "ContextMenu"
		for k , v in This.Children	{
			
			if( Main.Selected.Tab = k - 4 || k < 5 )
				This.Children[ k ].Draw()
		
		}
	}
}

class TabsClass	{
	Children := []
	__New( Parent , Index , X , Y , W , H ){
		
		This.Parent := Parent
		Gui, New, % " +AlwaysOnTop +HwndHwnd -Caption +Parent" This.Parent.Hwnd
		Gui, Color, 22262a
		This.Hwnd := hwnd
		This.Index := Index
		This.X := X
		This.Y := Y
		This.W := W
		This.H := H
		This.Parent.Children.Push( This )
	}
	Draw(){
		local k , v 
		Gui, % This.Hwnd ":Show", % "x" This.x " y" This.y " w" This.w " h" This.h " NA"
		for k , v in This.Children
			This.Children[ k ].Draw()
	}

}

class PanelsClass	{
	Children := []
	__New( Parent , Index , X , Y , W , H ){
		
		This.Parent := Parent
		Gui, New, % " +AlwaysOnTop +HwndHwnd -Caption +Parent" This.Parent.Hwnd
		Gui, Color, 00ff00
		This.Hwnd := hwnd
		This.Index := Index
		This.X := X
		This.Y := Y
		This.W := W
		This.H := H
		This.Parent.Children.Push( This )
	}
	Draw(){
		Gui, % This.Hwnd ":Show", % "x" This.x " y" This.y " w" This.w " h" This.h " NA"
		for k , v in This.Children
			This.Children[ k ].Draw()
	}

}

class ButtonsClass	{
	;~ Children := [] ? Spawns the new sub-windows
	__New( Parent , Index , X , Y , W , H , Color := "FF00FF" , Method := "_SwitchTabs" ){
		
		This.Parent := Parent
		Gui, New, % " +AlwaysOnTop +HwndHwnd -Caption +Parent" This.Parent.Hwnd
		Gui, Color, % Color
		This.Hwnd := hwnd
		This.Index := Index
		This.Method := Method
		This.X := X
		This.Y := Y
		This.W := W
		This.H := H
		This.Parent.Children.Push( This )
	}
	Draw(){
		Gui, % This.Hwnd ":Show", % "x" This.x " y" This.y " w" This.w " h" This.h " NA"
	}

}

I would say that I am decent at understanding code, so any examples don't need to be too elaborate or complete. Just something that gets the basic point across and perhaps a more technical example here and there.

Thanks again.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: anonymous_user and 197 guests