[AHK v2] ObjTree - Explore and Edit objects easily

Post your working scripts, libraries and tools.
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

[AHK v2] ObjTree - Explore and Edit objects easily

11 Nov 2017, 13:59

This is an continuation and update to old ObjTree thread.
AutoHotkey v1 version is not maintained anymore so this will require AutoHotkey v2 or AutoHotkey_H v2.

ObjTree will display a GUI containing your objects and its items, you can also edit and save the content.
Features wrote:
  • Multiple window support
  • Context menu for TreeView to expand, collapse, insert or delete items
  • Double click in ListView displays keys of selected object.
  • ReadOnly support and ReadOnly[level/depth] support if you don't want to allow editing higher level objects/items.
  • ReadOnly options for keys (KeyNoEdit=1)
  • ListViews Checkbox will be checked if value is an object and can be double clicked to display content.
  • ToolTip in TreeView shows contents of keys and first 20 keys and values of objects.
  • Optionally ToolTipObject can be used to display different information for keys and objects.
  • Selected key/object is highlighted in ListView when selected in TreeView and the other way around
Download - requires AutoHotkey v2 or AutoHotkey_H v2
Syntax and Example
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

12 Nov 2017, 02:14

Thank you very much for sharing, it looks very good :bravo:.
Very nice to have a v2 version of this :thumbup:.

Cheers.
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

12 Nov 2017, 10:50

Update wrote:
  • Fixed bugs for Menu Insert and InsertChild
  • Fixed bug for IsFunc to check for IsObject too
  • Delete OnMessage when Gui is closed
User avatar
BGM
Posts: 507
Joined: 20 Nov 2013, 20:56
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

12 Nov 2017, 20:06

Can you all help me out here, please? I want to test it; so I have ahk v2, but I get an error:
Call to nonexistent function. Specifically, ObjTree :=... the first line.

Am I missing something?
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

13 Nov 2017, 00:47

Did you download the function and included in your script or saved in a library?
User avatar
BGM
Posts: 507
Joined: 20 Nov 2013, 20:56
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

13 Nov 2017, 00:55

Doh! Thanks for answering; uh, never mind my question, please! [slaps own face]
I mistook the example to be some fancy usage of a call to a new v2 function; I've not use v2 for anything, yet.
User avatar
hoppfrosch
Posts: 443
Joined: 07 Oct 2013, 04:05
Location: Rhine-Maine-Area, Hesse, Germany
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

13 Nov 2017, 01:05

Great work! :bravo:
egocarib
Posts: 100
Joined: 21 May 2015, 18:21

Re: [AHK v2] ObjTree - Explore and Edit objects easily

13 Nov 2017, 20:26

:clap: Nifty!

Handled some very complex objects I loaded from a YAML configuration file, and made it easy to debug issues. I'll definitely be using this regularly.

I especially like that long value strings are displayed in a separate pane. My only suggestion would be to make some way to also show long key strings. In an object I'm working with, I have some very long strings as keys, and it is difficult to see those strings in full. However, it's probably not typical for objects to have very long string keys, so I might be in the minority here.
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

14 Nov 2017, 18:34

Thanks, I like the Idea and have implemented Edit control for keys as well.
I have also added Edit options, mainly to enable/disable Wrap ;)
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

17 Nov 2017, 18:55

Update wrote:
  • Fixed Font option.
  • Added option KeyNoEdit=1, this will disable editing keys, so only values can be edited.
  • Added InputBox for new item name when inserting new Items.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: [AHK v2] ObjTree - Explore and Edit objects easily

09 Feb 2019, 17:02

i ported ObjTree.ahk over to the new gui syntax, but it throws on line 144 and 294 IsObject/IsFunc "Parameter #1 invalid."

Code: Select all

;~ ObjTree2 := ObjTree(Yaml("a very very long line with some stupid information data lkj lkjlk ölk jölkj öljk ölk jöljl jölö ljölj :`n  - test`n  - what", 0)
		;~ , "My ObjTree Gui Title", "-ReadOnly")

ObjTree2 := ObjTree({MyObject:{"Key With Object":["a", "This is a test", "b", 2]}
					, "Another Object":{Key:["c", 1, "d", 2]}
					, "Empty Object":[]
					, "Another Object 2":["e", 1, "f", 2]}
		, "My ObjTree Gui Title", "-ReadOnly,Edit=wrap"
		, {MyObject:{"Key With Object":"This is a an object`nKey is a string and value is object"}
					, "Another Object":{Key:"This is an object"}
					, "Empty Object":"This object is empty"
					, "Another Object 2":"Another object"})
WinWaitClose "ahk_id " ObjTree2.hwnd

ObjTree(ByRef obj, Title := "ObjTree", Options := "+ReadOnly +Resize, GuiShow=w640 h480", ToolTip := ""){
	return new _ObjTree(obj, Title, Options, ToolTip)
}
Class _ObjTree {
	__New(ByRef obj, Title := "ObjTree", Options := "+ReadOnly +Resize, edit=-wrap, GuiShow=w640 h480", ToolTip := ""){
		If RegExMatch(Options, "i)^\s*([-\+]?ReadOnly)(\d+)?\s*$", option)
			Options := "+AlwaysOnTop +Resize, GuiShow=w640 h480", this.ReadOnly := option.1, this.ReadOnlyLevel := option.2
		else this.ReadOnly := "+ReadOnly"
		Loop Parse, Options, "`,", A_Space
		{
		 opt := Trim(SubStr(A_LoopField, 1, InStr(A_LoopField, "=")-1))
		 If RegExMatch(A_LoopField, "i)([-\+]?ReadOnly)(\d+)?", option)
			this.ReadOnly := option.1, this.ReadOnlyLevel := option.2
		 If (InStr("Font,GuiShow,Edit", opt))
			%opt% := SubStr(A_LoopField, InStr(A_LoopField, "=") + 1, StrLen(A_LoopField))  
		 else GuiOptions := RegExReplace(A_LoopField, "i)[-\+]?ReadOnly\s?")
		}
		this.Gui := GuiCreate(GuiOptions, Title), this.hwnd := this.gui.hwnd
		, fun := this.Close.Bind(this), this.Gui.OnEvent("Close", fun) ;, this.Gui.OnEvent("Escape", fun)
		if (Font)
			Gui.SetFont(SubStr(Font, 1, Pos := InStr(Font, ":") - 1), SubStr(Font, Pos + 2, StrLen(Font)))
		; Get Gui size
		if !RegExMatch(GuiShow, "\b[w]([0-9]+\b).*\b[h]([0-9]+\b)", size)
			size := [640, 480]
		; Get hwnd of new window
		this.Hwnd := this.gui.hwnd, IsAHK_H := this.IsAHK_H()
		; Apply Gui options and create Gui
		this.Gui.Add("Button", "x0 y0 NoTab Hidden Default", "Show/Expand Object")
		TV := this.TV := this.Gui.Add("TreeView", "xs w" (size.1*0.3) " h" (size.2) " ys " (IsAHK_H?"aw1/3 ah":"") " +0x800 +ReadOnly")
		LV := this.LV := this.Gui.Add("ListView", "x+1 w" (size.1*0.7) " h" (size.2*0.5) " ys " (IsAHK_H?"aw2/3 ah1/2 ax1/3":"") " AltSubmit Checked " this.ReadOnly, "[IsObj] Key/Address|Value/Address| ItemID")
		fun := this.TVSelect.Bind(this, LV)
		TV.OnEvent("ItemSelect", fun) 
		LV.ModifyCol(3, "0")
		fun := this.LVSelect.Bind(this, TV)
		LV.OnEvent("Click", fun)
		fun := this.LVDoubleClick.Bind(this, TV)
		LV.OnEvent("DoubleClick", fun)
		fun := this.LVEdit.Bind(this, TV)
		LV.OnEvent("ItemEdit", fun)
		fun := this.LVCheck.Bind(this, TV)
		LV.OnEvent("ItemCheck", fun)
		EditKey := this.EditKey := this.Gui.Add("Edit", "y+1 w" (size.1*0.7) " h" (size.2*0.11) (IsAHK_H?" axr aw2/3 ah1/5 ax1/3 ay1/2 +HScroll":"") " " this.ReadOnly " " Edit)
		EditKey.Enabled := false
		fun := this.EditKeyEdit.Bind(this, TV, LV)
		EditKey.OnEvent("Change", fun)
		EditValue := this.EditValue := this.Gui.Add("Edit", "y+1 w" (size.1*0.7) " h" (size.2*0.39) (IsAHK_H?" axr aw2/3 ah3/10 ax1/3 ay1/5 +HScroll":"") " " this.ReadOnly " " Edit)
		EditValue.Enabled := false
		fun := this.EditValueEdit.Bind(this, TV, LV)
		EditValue.OnEvent("Change", fun)
		; Items will hold TV_Item <> Object relation
		
		this.Items := {}
		this.obj := obj
		
		; Create Menus to be used for all ObjTree windows (ReadOnly windows have separate Menu)
		this.Menu := MenuCreate()
		fun := this.TVExpandAll.Bind(this.TV)


		this.Menu.Add("E&xpand All", fun)
		
		fun := this.TVCollapseAll.Bind(this, this.TV)
		this.Menu.Add("C&ollapse All", fun)
		; Convert object to TreeView and create a clone for our object
		; Changes can be optionally saved when ObjTree is closed when -ReadOnly is used
		If (this.ReadOnly="-ReadOnly"){
			this.newObj := this.CreateClone(obj), this.Items[newObj] := 0, this.TVAdd(this.newObj, 0)
			; Add additional Menu items when not Readonly
			, this.Menu.Add(), fun := this.TVInsert.Bind(this, this.TV), this.Menu.Add("&Insert", fun)
			, fun := this.TVInsertChild.Bind(this, this.TV), this.Menu.Add("I&nsertChild", fun)
			, this.Menu.Add(), fun := this.TVDelete.Bind(this, this.TV), this.Menu.Add("&Delete", fun)
		} else this.Items[this.newObj := obj] := 0, this.TVAdd(obj, 0)
		if !IsAHK_H{
			ObjTree_Attach(TV.hwnd, "w1/2 h")
			, ObjTree_Attach(LV.hwnd, "w1/2 h1/2 x1/2 y0")
			, ObjTree_Attach(EditKey.hwnd, "w2/2 h1/5 x1/3 y1/2")
			, ObjTree_Attach(EditValue.hwnd, "w2/2 h3/10 x1/3 y1/5")
		}
		this.Tooltip := ToolTip, fun := this.TVContextMenu.Bind(this), this.TV.OnEvent("ContextMenu", fun), this.gui.Show(GuiShow)
		, this.WM_Notify := this.Notify.Bind(this, TV), OnMessage(78, this.WM_Notify) ;WM_NOTIFY
	}
	IsAHK_H() {   ; Written by SKAN, modified by HotKeyIt
		; www.autohotkey.com/forum/viewtopic.php?p=233188#233188  CD:24-Nov-2008 / LM:27-Oct-2010
		If FSz := DllCall("Version\GetFileVersionInfoSizeW", "Str", A_AhkPath, "UInt", 0 ){
		VarSetCapacity( FVI, FSz, 0 ), DllCall("Version\GetFileVersionInfoW", "Str", A_AhkPath, "UInt", 0, "UInt", FSz, "PTR", &FVI )
		If DllCall( "Version\VerQueryValueW", "PTR", &FVI, "Str", "\VarFileInfo\Translation", "PTR*", Transl, "PTR", 0 )
			&& (Trans := format("{1:.8X}", NumGet(Transl+0, "UInt")))
			&& DllCall( "Version\VerQueryValueW", "PTR", &FVI, "Str", "\StringFileInfo\" SubStr(Trans, -4) SubStr(Trans, 1, 4) "\FILEVERSION", "PTR*", InfoPtr, "UInt", 0 )
			return !!InStr(StrGet(InfoPtr), "H")
		}
	}
	Notify(TV, wParam, lParam){
		static ToolTipText, TVN_GETINFOTIP := 0XFFFFFE70 - 14 - 0 ;TVN_FIRST := 0xfffffe70 / 0=Unicode
		/*
			ObjTree is also used to Monitor messages for TreeView: ObjeTree(obj=wParam, Title=lParam, Options=msg, ishwnd=hwnd)
			when ishwnd is a handle, this routine is taken
		*/
		
		; Check if this message is relevant
		If (NumGet(lParam, A_PtrSize*2, "Uint")!=TVN_GETINFOTIP)
			Return
		; HDR.Item contains the relevant TV_ID
		TV_Text := TV.GetText(TV_Item := NumGet(lParam+A_PtrSize*5, "PTR"))
		
		; Check if this GUI uses a ToolTip object that contains the information in same structure as the TreeView
		If ToolTipText := this.ToolTip { ; Gui has own ToolTip object
			; following will resolve the item in ToolTip object
			object := [TV_Text], item := TV_Item
			While item := TV.GetParent(item)
				object.Push(TV.GetText(item))
			; Resolve our item/value in ToolTip object
			While object.MaxIndex(){
				if !IsObject(ToolTipText){
					ToolTipText := ""
					break
				}
				ToolTipText := ToolTipText[object.Pop()]
			}
			; Item is not an object and is not empty, display value in ToolTip
			If !IsObject(ToolTipText)&&ToolTipText!=""
				Return NumPut((ToolTipText.="", &ToolTipText), lParam+A_PtrSize*3, "PTR") ;HDR.pszText[""] := &(ToolTipText.="")
			ToolTipText := ""
		}
		; Gui has no ToolTip object or item could not be resolved
		; Get the value of item and display in ToolTip
		; Check if Item is an object and if so, display first 20 keys (first 50 chars) and values (first 100 chars)
		object := this.items[TV_Item, TV_Text]
		if !IsObject(object)
			ToolTipText := object ""
		else if (IsObject(object) && IsFunc(object))
			ToolTipText := "[Func]`t`t" object.Name "`nBuildIn:`t`t" object.IsBuiltIn "`nVariadic:`t" object.IsVariadic "`nMinParams:`t" object.MinParams "`nMaxParams:`t" object.MaxParams
		else
			for key, v in object
			{
				ToolTipText.=(ToolTipText?"`n":"") SubStr(key, 1, 50) (StrLen(key)>50?"...":"") " = " (IsObject(v)?"[Obj] ":SubStr(v, 1, 100) (StrLen(v)>100?"...":""))
				If (A_Index>20){
					ToolTipText.="`n..."
					break
				}
			}
		Return NumPut(&ToolTipText, lParam+A_PtrSize*3, "PTR") ;(HDR.pszText[""] := &ToolTipText)
	}
	Close(){
		this.gui.Opt("+OwnDialogs")
		If this.changed && "Yes"=MsgBox("Do you want to save changes?", , 4){
			for k, v in this.obj
				this.obj.Delete(k)
			for k, v in this.newObj
				this.obj[k] := v
		}
		this.newObj := ""
		this.gui.destroy()
		OnMessage(78, this.WM_Notify, 0)
	}
	TVContextMenu(TV, Item, IsRightClick){
		TV.Modify(Item)
		this.Menu.Show()
	}
	CreateClone(obj){
		clone := ObjClone(obj)
		for k, v in obj{
			If IsObject(v)
				clone[k] := this.CreateClone(v)
		}
		Return clone
	}
	EditKeyEdit(TV, LV){
		static count := 0
		id := LV.GetText(row := LV.GetNext("Selected"), 3)	, obj := this.items[id], key := TV.GetText(id), newkey := this.EditKey.Value
		if newkey=key
			return
		If Obj.HasKey(newKey)
			Return (this.gui.Opt("+OwnDialogs"), this.EditKey.Text := key, MsgBox("Key " newKey " already exists"))
		obj[newkey] := obj[key], obj.Delete(key), TV.Modify(id, , newkey), LV.Modify(row, , newkey)
		, TV.Modify(this.items[this.items[id]], "Sort"), LV.ModifyCol(1, "Sort AutoHdr"), this.changed := 1
	}
	EditValueEdit(TV, LV){
		this.changed := 1
		, this.items[TV.GetSelection(), LV.GetText(row := LV.GetNext("Selected"))] := value := this.EditValue.Value
		, LV.Modify(Row, , , value)
	}
	TVAdd(obj, parent := ""){
		for k, v in obj
		{
			If (IsObject(v) && !this.Items.Haskey(v))
				this.Items[v] := this.TV.Add(IsObject(k)?Chr(177) " " (&k):k, parent, "Sort"), this.Items[this.Items[v]] := obj
				, this.TVAdd(v, this.Items[v])
			else
				this.Items[lastParent := this.TV.Add(IsObject(k)?Chr(177) " " (&k):k, parent, "Sort")] := obj
			;~ If (IsObject(k) && !this.Items.HasKey(v))
				;~ this.Items[k] := this.TV.Add(Chr(177) " " (&k), IsObject(v)?this.Items[v]:lastParent, "Sort"), this.Items[this.Items[k]] := k
				;~ this.TVAdd(k, this.Items[k])
		}
	}
	TVExpandAll(Menu){
		this.Modify(item := this.GetSelection(), "+Expand")
		if this.GetChild(item)
			While(item := this.GetNext(item, "F")) && this.GetParent(item)
				this.Modify(item, "+Expand")
	}
	TVInsert(TV){
		this.gui.Opt("+OwnDialogs")
		Loop this.ReadOnlyLevel
			if !TV_Item := TV.GetParent(TV_Item?TV_Item:TV.GetSelection())
				Return MsgBox("New Items can be inserted only from level " this.ReadOnlyLevel "!")
		if !parent := TV.GetParent(obj := TV.GetSelection())
			item := this.newObj.Push(obj := []), this.items[obj] := TV.Add(item, , "Sort"), this.items[this.items[obj]] := obj
		else
			this.Items[item := this.TV.Add(k := this.Items[obj].Push(""), parent, "Sort")] := this.Items[obj]
		this.changed := 1
	}
	TVInsertChild(TV){
		Loop this.ReadOnlyLevel-1
			if !TV_Item := TV.GetParent(TV_Item?TV_Item:TV.GetSelection())
				Return MsgBox("New Items can be inserted only from level " this.ReadOnlyLevel "!")
		this.gui.Opt("+OwnDialogs")
		if !IsObject(v := this.items[parent := TV.GetSelection(), k := TV.GetText(parent)]){
			if "Yes"=MsgBox(k " is not an object, would you like to convert it to object?", , 4)
				this.Items[parent, k] := obj := {(k):v}, this.Items[obj] := TV.Add(k, parent, "Sort")
			else Return
		} else
			this.Items[item := TV.Add(this.Items[parent, k].Push(""), parent, "Sort")] := this.Items[parent, k]
		this.changed := 1
	}
	TVDelete(TV){
		this.gui.Opt("+OwnDialogs")
		Loop this.ReadOnlyLevel
			if !TV_Item := TV.GetParent(TV_Item?TV_Item:TV.GetSelection())
				Return MsgBox("New Items can be deleted only from level " this.ReadOnlyLevel "!")
		k := TV.GetText(item := TV.GetSelection())
		If "Yes"=MsgBox("Do you want to Delete " k, , 4){
			If IsObject(this.items[item])
				this.TVDeepDelete(TV, item)
			else
				this.items.Delete(item), TV.Delete(item)
			this.changed := 1
		}
	}
	TVDeepDelete(TV, parent){
		If child := TV.GetChild(parent){
			Loop {
				if TV.GetChild(child)
					this.TVDeepDelete(TV, child)
				this.items[parent].Delete(TV.GetText(child)), this.items.Delete(this.items[child]), this.items.Delete(child), TV.Delete(child), next := TV.GetNext(child, "F")
				if TV.GetParent(next)!=parent
					return
				child := next
			}
		} else 
			this.items[parent].Delete(TV.GetText(parent)), TV.Delete(parent), this.items.Delete(parent)
	}
	TVCollapseAll(TV, parent := 0){
		if parent="C&ollapse All"
			parent := TV.GetSelection()
		If child := TV.GetChild(parent)
			Loop {
				if TV.GetChild(child)
					this.TVCollapseAll(TV, child)
				next := TV.GetNext(child, "F")
				if TV.GetParent(next)!=parent
					return TV.Modify(parent, "-Expand")
				child := next
			}
	}
	TVSelect(LV, TV, item){
		this.LV.Delete(), this.EditValue.Text := "", this.EditValue.Enabled := false	, text := TV.GetText(item), TV.Modify(item, "Select")
		if !TV.Getparent(item)
			obj := this.newObj, next := item := 0
		else obj := this.items[item], next := item := TV.GetParent(item)
		Loop this.ReadOnlyLevel
			if !TV_Item := TV.GetParent(TV_Item?TV_Item:TV.GetSelection())
				ReadOnly := 1
		Loop {
			While item := TV.GetNext(item, "Full")
				if next=TV.GetParent(item)
					break
			if item=0||TV.GetParent(item)!=next
				break
			k := TV.GetText(item), v := obj[k]
			LV.Add(((IsObject(v) || IsObject(k)) ? "Check" : "") (text = (IsObject(k) ? (Chr(177) " " (&k)) : k) ? (LV_CurrRow := A_Index, " Select"):"")
						, IsObject(k)?(Chr(177) " " (&k)):k, IsObject(v) && IsFunc(v)?"[" (v.IsBuiltIn?"BuildIn ":"") (v.IsVariadic?"Variadic ":"") "Func] " v.Name:IsObject(v)?(Chr(177) " " (&v)):v, item)
			If (LV_CurrRow=A_Index)
				LV.Modify(LV_CurrRow, "Vis Select"), this.EditValue.Enabled := !IsObject(v)&&!ReadOnly, this.EditValue.Text := v, this.EditKey.Enabled := !IsObject(k)&&!ReadOnly, this.EditKey.Text := k
		}
		Loop 2
			LV.ModifyCol(A_Index, "AutoHdr") ;autofit contents
	}
	LVCheck(TV, LV, item){
		if LV.GetCount()<item
			return
		LV.Modify(LV.GetNext("Selected"), "-Select"), LV.Modify(item, "Select"), this.LVSelect(TV, LV, item), LV.Modify(item, (TV.GetChild(LV.GetText(item, 3))?"":"-") "Check")
	}
	LVEdit(TV, LV, item){
		id := LV.GetText(item, 3)	, obj := this.items[id], key := TV.GetText(id), newkey := LV.GetText(item)
		Loop this.ReadOnlyLevel
			if !TV_Item := TV.GetParent(TV_Item?TV_Item:TV.GetSelection())
				Return (this.gui.Opt("+OwnDialogs"), LV.Modify(item, , key), MsgBox("New Items can be edited only from level " this.ReadOnlyLevel "!"))
		If Obj.HasKey(newKey)
			Return (this.gui.Opt("+OwnDialogs"), LV.Modify(item, , key), MsgBox("Key " newKey " already exists"))
		obj[newkey] := obj[key], obj.Delete(key), TV.Modify(id, , newkey), TV.Modify(this.items[this.items[id]], "Sort"), LV.ModifyCol(1, "Sort")
		, this.EditKey.Text := newkey, this.changed := 1
	}
	LVSelect(TV, LV, item){
		if LV.GetCount()<item
			return
		TV.Modify(id := LV.GetText(item, 3), "Select")
		Loop this.ReadOnlyLevel
			if !TV_Item := TV.GetParent(TV_Item?TV_Item:TV.GetSelection())
				ReadOnly := 1
		this.EditKey.Enabled := !ReadOnly, this.EditKey.Text := LV.GetText(item)
		if !IsObject(this.items[id, LV.GetText(item)]){
			this.EditValue.Enabled := !ReadOnly, this.EditValue.Text := this.items[id, LV.GetText(item)]
		} else this.EditValue.Text := "", this.EditValue.Enabled := false
	}
	LVDoubleClick(TV, LV){
		if IsObject(value := this.items[TV.GetSelection(), key := LV.GetText(LV.GetNext("Selected"))])
			TV.Modify(item := this.items[value], "+Expand Select"), (item := TV.GetChild(item))?this.TVSelect(LV, TV, item):(this.gui.Opt("+OwnDialogs"), MsgBox("Object `"" key "`" is empty!"))
	}
}
/*
  Function:		Attach
          Determines how a control is resized with its parent.

  hCtrl:			
          - hWnd of the control if aDef is not empty.					
          - hWnd of the parent to be reset if aDef is empty. If you omit this parameter function will use
          the first hWnd passed to it.
          With multiple parents you need to specify which one you want to reset.					
          - Handler name, if parameter is string and aDef is empty. Handler will be called after the function has finished 
          moving controls for the parent. Handler receives hWnd of the parent as its only argument.

  aDef:			
          Attach definition string. Space separated list of attach options. If omitted, function working depends on hCtrl parameter.
          You can use following elements in the definition string:
          
          - 	"x, y, w, h" letters along with coefficients, decimal numbers which can also be specified in m/n form (see example below).
          -   "r". Use "r1" (or "r") option to redraw control immediately after repositioning, set "r2" to delay redrawing 100ms for the control
            (prevents redrawing spam).
          -	"p" (for "proportional") is the special coefficient. It will make control's dimension always stay in the same proportion to its parent 
            (so, pin the control to the parent). Although you can mix pinned and non-pinned controls and dimensions that is rarely what you want. 
            You will generally want to pin every control in the parent.
          -	"+" or "-" enable or disable function for the control. If control is hidden, you may want to disable the function for 
            performance reasons, especially if control is container attaching its children. Its perfectly OK to leave invisible controls 
            attached, but if you have lots of them you can use this feature to get faster and more responsive updates. 
            When you want to show disabled hidden control, make sure you first attach it back so it can take its correct position
            and size while in hidden state, then show it. "+" must be used alone while "-" can be used either alone or in Attach definition string
            to set up control as initially disabled.

  Remarks:
          Function monitors WM_SIZE message to detect parent changes. That means that it can be used with other eventual container controls
          and not only top level windows.

          You should reset the function when you programmatically change the position of the controls in the parent control.
          Depending on how you created your GUI, you might need to put "autosize" when showing it, otherwise resetting the Gui before its 
          placement is changed will not work as intented. Autosize will make sure that WM_SIZE handler fires. Sometimes, however, WM_SIZE
          message isn't sent to the window. One example is for instance when some control requires Gui size to be set in advance in which case
          you would first have "Gui, Show, w100 h100 Hide" line prior to adding controls, and only Gui, Show after controls are added. This
          case will not trigger WM_SIZE message unless AutoSize is added.
        
        
  Examples:
  (start code)
          Attach(h, "w.5 h1/3 r2")	;Attach control's w, h and redraw it with delay.
          Attach(h, "-")				;Disable function for control h but keep its definition. To enable it latter use "+".
          Attach(h, "- w.5")			;Make attach definition for control but do not attach it until you call Attach(h, "+").
          Attach()					;Reset first parent. Use when you have only 1 parent.
          Attach(hGui2)				;Reset Gui2.
          Attach("Win_Redraw")		;Use Win_Redraw function as a Handler. Attach will call it with parent's handle as argument.
          Attach(h, "p r2")			;Pin control with delayed refreshing.

          
          ; This is how to do delayed refresh of entire window.
          ; To prevent redraw spam which can be annoying in some cases, ; you can choose to redraw entire window only when user has finished resizing it.
          ; This is similar to r2 option for controls, except it works with entire parent.
          
          Attach("OnAttach")			;Set Handler to OnAttach function
          ...
          
          OnAttach( Hwnd ) {
            global hGuiToRedraw := hwnd
            SetTimer, Redraw, -100
          }

          Redraw:
            Win_Redraw(hGuiToRedraw)
          return
  (end code)
  Working sample:
  (start code)
    #SingleInstance, force
      Gui, +Resize
      Gui, Add, Edit, HWNDhe1 w150 h100
      Gui, Add, Picture, HWNDhe2 w100 x+5 h100, pic.bmp 

      Gui, Add, Edit, HWNDhe3 w100 xm h100
      Gui, Add, Edit, HWNDhe4 w100 x+5 h100
      Gui, Add, Edit, HWNDhe5 w100 yp x+5 h100
      
      gosub SetAttach					;comment this line to disable Attach
      Gui, Show, autosize			
    return

    SetAttach:
      Attach(he1, "w.5 h")		
      Attach(he2, "x.5 w.5 h r")
      Attach(he3, "y w1/3")
      Attach(he4, "y x1/3 w1/3")
      Attach(he5, "y x2/3 w1/3")
    return
  (end code)

  About:
      o 1.1 by majkinetor
      o Licensed under BSD <http://creativecommons.org/licenses/BSD/> 
 */
ObjTree_Attach(hCtrl := "", aDef := "") {
   ObjTree_Attach_(hCtrl, aDef, "", "")
}

ObjTree_Attach_(hCtrl, aDef, Msg, hParent){
  static
  static Handler := "", adrWindowInfo := "", adrSetWindowPos := ""
  local s1, s2, enable := 0, reset := 0, oldCritical
  ListLines "Off"
  if (aDef = "") {							;Reset if integer, Handler if string
    if IsFunc(hCtrl)
      return Handler := hCtrl
  
    If adrWindowInfo=""
      return			;Resetting prior to adding any control just returns.
    hParent := hCtrl != "" ? hCtrl+0 : hGui
    loop parse, _%hParent%a, A_Space
    {
      hCtrl := A_LoopField, SubStr(_%hCtrl%, 1, 1), aDef := SubStr(_%hCtrl%, 1, 1)="-" ? SubStr(_%hCtrl%, 2) : _%hCtrl%, _%hCtrl% := ""
      _%hCtrl% := ""
      gosub Attach_GetPos
      loop parse, aDef, A_Space
      {
        z := StrSplit(A_LoopField, ":")
        z1 := z.1
        _%hCtrl% .= A_LoopField="r" ? "r " : (z.1 ":" z.2 ":" c%z1% " ")
      }
      _%hCtrl% := SubStr(_%hCtrl%, 1, -1)				
    }
    reset := 1, _%hParent%_s := _%hParent%_pw " " _%hParent%_ph
  }

  if (hParent = "")  {						;Initialize controls 
    if !adrSetWindowPos
      adrSetWindowPos := DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32", "PTR"), "astr", "SetWindowPos", "ptr")
      , adrWindowInfo := DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32", "PTR"), "astr", "GetWindowInfo", "ptr")
      , OnMessage(5, A_ThisFunc), VarSetCapacity(B, 60), NumPut(60, B), adrB := &B
      , hGui := DllCall("GetParent", "ptr", hCtrl, "ptr") 

    hParent := DllCall("GetParent", "ptr", hCtrl, "ptr") 
    
    if !_%hParent%_s
      DllCall(adrWindowInfo, "ptr", hParent, "ptr", adrB), _%hParent%_pw := NumGet(B, 28, "UInt") - NumGet(B, 20, "UInt"), _%hParent%_ph := NumGet(B, 32, "UInt") - NumGet(B, 24, "UInt"), _%hParent%_s := !_%hParent%_pw || !_%hParent%_ph ? "" : _%hParent%_pw " " _%hParent%_ph
    
    if InStr(" " aDef " ", "p")
      aDef := StrReplace(aDef, "p", "xp yp wp hp", , 1)
    if aDef="-"
      return SubStr(_%hCtrl%, 1, 1) != "-" ? _%hCtrl% := "-" _%hCtrl% : ""
    else if (aDef = "+")
      if SubStr(_%hCtrl%, 1, 1) != "-" 
         return
      else _%hCtrl% := SubStr(_%hCtrl%, 2), enable := 1 
    else {
      gosub Attach_GetPos
	  if hCtrl<0
		return
      _%hCtrl% := ""
      loop parse, aDef, A_Space
      {			
        if (l := A_LoopField) = "-"	{
          _%hCtrl% := "-" _%hCtrl%
          continue
        }
        f := SubStr(l, 1, 1), k := StrLen(l)=1 ? 1 : SubStr(l, 2)
        if (j := InStr(l, "/"))
          k := SubStr(l, 2, j-2) / SubStr(l, j+1)
        _%hCtrl% .= f ":" k ":" c%f% " "
      }
      return (_%hCtrl% := SubStr(_%hCtrl%, 1, -1), _%hParent%a .= InStr(_%hParent%, hCtrl) ? "" : (_%hParent%a = "" ? "" : " ")  hCtrl)
    }
  }
  if _%hParent%a=""
    return				;return if nothing to anchor.

  if !reset && !enable {					
    _%hParent%_pw := aDef & 0xFFFF, _%hParent%_ph := aDef >> 16
    if _%hParent%_ph=0
      return		;when u create gui without any control, it will send message with height=0 and scramble the controls ....
  } 

  if !_%hParent%_s
    _%hParent%_s := _%hParent%_pw " " _%hParent%_ph

  oldCritical := A_IsCritical
  critical 5000

  s := StrSplit(_%hParent%_s, A_Space)
  loop parse, _%hParent%a, A_Space
  {
    hCtrl := A_LoopField, aDef := _%hCtrl%, uw := uh := ux := uy := r := 0, hCtrl1 := SubStr(_%hCtrl%, 1, 1)
    if (hCtrl1 = "-")
      if reset=""
        continue
      else aDef := SubStr(aDef, 2)	
    
    gosub Attach_GetPos
    loop parse, aDef, A_Space
    {
      z := StrSplit(A_LoopField, ":")		; opt:coef:initial
      If z.1="r"
        r := z.2
      z1 := z.1
      if z.2="p"
         c%z1% := z.3 * (z.1="x" || z.1="w" ?  _%hParent%_pw/s.1 : _%hParent%_ph/s.2), u%z1% := true
      else c%z1% := z.3 + z.2*(z.1="x" || z.1="w" ?  _%hParent%_pw-s.1 : _%hParent%_ph-s.2), u%z1% := true
    }
    flag := 4 | (r=1 ? 0x100 : 0) | (uw OR uh ? 0 : 1) | (ux OR uy ? 0 : 2)			; nozorder=4 nocopybits=0x100 SWP_NOSIZE=1 SWP_NOMOVE=2
    ;m(hParent, _%hParent%a, hCtrl, _%hCtrl%)
    DllCall(adrSetWindowPos, "ptr", hCtrl, "ptr", 0, "uint", cx, "uint", cy, "uint", cw, "uint", ch, "uint", flag)
    r+0=2 ? ObjTree_Attach_redrawDelayed(hCtrl) : ""
  }
  critical oldCritical
  return Handler != "" ? %Handler%(hParent) : ""

 Attach_GetPos:									;hParent & hCtrl must be set up at this point
    DllCall(adrWindowInfo, "ptr", hParent, "ptr", adrB), lx := NumGet(B, 20, "UInt"), ly := NumGet(B, 24, "UInt"), DllCall(adrWindowInfo, "ptr", hCtrl, "ptr", adrB)
    , cx := NumGet(B, 4, "UInt"), cy := NumGet(B, 8, "UInt"), cw := NumGet(B, 12, "UInt")-cx, ch := NumGet(B, 16, "UInt")-cy, cx-=lx, cy-=ly
 return
}

ObjTree_Attach_redrawDelayed(hCtrl){
  static s
  s .= !InStr(s, hCtrl) ? hCtrl " " : ""
  SetTimer A_ThisFunc, -100
  return
 Attach_redrawDelayed:
  loop parse, s, A_Space
    DllCall("InvalidateRect", "ptr", A_LoopField, "ptr", 0, "int", true)
  s := ""
 return
}
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: [AHK v2] ObjTree - Explore and Edit objects easily

09 Feb 2019, 17:24

It may be this:
v2-changes
https://www.autohotkey.com/v2/v2-changes.htm
IsFunc(Fn) now throws an exception if given an object. It should be used only to validate function names.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
HotKeyIt
Posts: 2364
Joined: 29 Sep 2013, 18:35
Contact:

Re: [AHK v2] ObjTree - Explore and Edit objects easily

09 Feb 2019, 19:16

Many thanks for porting it swagfag, I updated it on github.
I simply excluded Function support for now.

Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: someguyinKC and 25 guests