├── CGui.Demo.exe ├── .gitignore ├── sample inihandler.ahk ├── to check.ahk ├── old demo.ahk ├── coordinate tests.ahk ├── README.md ├── CGui.ahk └── CGui v2.ahk /CGui.Demo.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilC/CGui/HEAD/CGui.Demo.exe -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ini 2 | *.exe 3 | *.zip 4 | *.lnk 5 | *.scc 6 | *.png 7 | *.tmp.* 8 | Setup.ahk 9 | !Setup*.exe 10 | !*.Demo.exe 11 | -------------------------------------------------------------------------------- /sample inihandler.ahk: -------------------------------------------------------------------------------- 1 | ; CGui Patch to implement desired Persistent settings technique ======================================================== 2 | ; OnChange is a class function that normally does nothing. The rest of this class is specific to your implementation 3 | 4 | ; Implement GuiControl persistence with IniRead / IniWrite 5 | class CGui extends _CGui { 6 | ;class CWindow extends _CScrollGui { 7 | Class _CGuiControl extends _CGui._CGuiControl { 8 | __New(aParams*){ 9 | base.__New(aParams*) 10 | ; Work out name of INI 11 | SplitPath, A_ScriptName,,,,ScriptName 12 | this._ScriptName .= ScriptName ".ini" 13 | } 14 | ; hook into the onchange event 15 | OnChange(){ 16 | ; IniWrite 17 | if (this._PersistenceName){ 18 | IniWrite, % this.value, % this._ScriptName, Settings, % this._PersistenceName 19 | } 20 | } 21 | 22 | ; Set a GuiControl to be persistent. 23 | ; If called on a GuiControl, and there is an existing setting for it, set the control to the setting value 24 | MakePersistent(Name){ 25 | ; IniRead 26 | this._PersistenceName := Name 27 | IniRead, val, % this._ScriptName, Settings, % this._PersistenceName, -1 28 | if (val != -1){ 29 | this.value := val 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /to check.ahk: -------------------------------------------------------------------------------- 1 | #SingleInstance force 2 | 3 | ; Check activation of child gui on click in a control in the gui. 4 | ; eg if click inside an editbox of an inactive gui, the *GUI* should become active also. 5 | 6 | ; Adding +Border makes Edits in child Guis non-selectable. 7 | Menu,File,Add,Test1,GuiClose 8 | Menu,File,Add,Test2,GuiClose 9 | Menu,File,Add,Test3,GuiClose 10 | Menu,File,Add,Test4,GuiClose 11 | Menu,File,Add,Test5,GuiClose 12 | Menu,Test,Add,Test,:File 13 | Gui, new, hwndhMain 14 | Gui, % hMain ":Add",Edit,w100 h50 15 | Gui, % hMain ":Menu",Test 16 | Gui, % hMain ":Show", w400 h200 17 | Gui, new, % "hwndhLeft -Resize -Border +Parent" hMain 18 | Gui, % hLeft ":Show", w200 h200 x0 y0 19 | 20 | Gui, new, % "hwndhLChild -Resize +Border +Parent" hLeft 21 | Gui, % hLChild ":Show", x80 y80 w200 h50, Left 22 | Gui,% hLChild ":Add", Edit, w50 h50 , hLChild 23 | 24 | Gui, new, % "hwndhLChild2 -Resize +Border +Parent" hLeft 25 | Gui, % hLChild2 ":Show", x100 y100 w200 h50, Left 26 | Gui,% hLChild2 ":Add", Edit, w50 h50 , hLChild 27 | 28 | 29 | Gui, new, % "hwndhRight -Resize -Border +Parent" hMain 30 | Gui, % hRight ":Show", w200 h200 x200 y0 31 | Gui, new, % "hwndhRChild +Resize +Parent" hRight 32 | Gui, % hRChild ":Show", x100 y100 w200 h200, Right 33 | 34 | return 35 | Esc:: 36 | GuiClose: 37 | ExitApp 38 | -------------------------------------------------------------------------------- /old demo.ahk: -------------------------------------------------------------------------------- 1 | CGui_Demo_Running := 1 ; any test code in CGui main file should see this and not run. 2 | BorderState := 1 3 | 4 | main := new _CGui(0,"+Resize") 5 | main.Show("w300 h300 y0", "CGui Demo - " main._hwnd) 6 | Menu, Menu1, Add, Border, ToggleBorder 7 | Menu, Menu1, Add, Destroy, DestroyChild 8 | Gui, Menu, Menu1 9 | 10 | ;main.FocusTest := new _Cgui(main, BoolToSgn(BorderState) "Border +Resize +Parent" main._hwnd) 11 | main.FocusTest := main.Gui("new", BoolToSgn(BorderState) "Border +Resize +Parent" main._hwnd) 12 | main._DebugWindows := 0 13 | main.FocusTest._DebugWindows := main._DebugWindows 14 | main.name := "main" 15 | main.FocusTest.name := "Child" 16 | 17 | main.FocusTest.Show("w150 h150 x00 y00", main.FocusTest._hwnd) 18 | 19 | main.VGTest := main.Gui("new", "-Border +Resize +Parent" main._hwnd) 20 | main.VGTest.Show("x200 y200") 21 | main.VGTest.myText := main.VGTest.Gui("Add", "Text", "x0 y0 w100", main.VGTest._hwnd " (" Format("{:i}",main.VGTest._hwnd) ")" ) 22 | main.VGTest.name := "Child2" 23 | main.VGTest._DebugWindows := main._DebugWindows 24 | 25 | if (main._DebugWindows || main.FocusTest._DebugWindows){ 26 | Gui, New, hwndhDebug 27 | Gui, % hDebug ":Show", w300 h180 x0 y0 28 | Gui, % hDebug ":Add", Text, % "hwndhDebugOuter w400 h400" , 29 | } 30 | 31 | Loop 8 { 32 | main.FocusTest.Gui("Add", "Edit", "w300", "Item " A_Index) 33 | } 34 | if (main._DebugWindows || main.FocusTest._DebugWindows){ 35 | UpdateDebug() 36 | } 37 | return 38 | 39 | UpdateDebug() { 40 | global main 41 | global hDebug, hDebugOuter 42 | str := "" 43 | str .= "PARENT hwnd: `t`t" main._hwnd " (" Format("{:i}",main._hwnd) ")" 44 | str .= "`nCHILD hwnd: `t`t" main.FocusTest._hwnd " (" Format("{:i}",main.FocusTest._hwnd) ")" 45 | str .= "`n`nOuter WINDOW: `t" main._SerializeRECT(main._WindowRECT) 46 | str .= "`nOuter PAGE: `t`t" main._SerializeRECT(main._PageRECT) 47 | str .= "`nOuter RANGE: `t`t" main._SerializeRECT(main._RangeRECT) 48 | str .= "`n`nInner WINDOW: `t: " main._SerializeRECT(main.FocusTest._WindowRECT) 49 | str .= "`nInner PAGE: `t`t: " main._SerializeRECT(main.FocusTest._PageRECT) 50 | str .= "`nInner RANGE: `t`t: " main._SerializeRECT(main.FocusTest._RangeRECT) 51 | str .= "`n`nCHILD2 WINDOW: `t: " main._SerializeRECT(main.VGTest._WindowRECT) 52 | ;str .= "`n`nTest RECT: `t`t: " main._SerializeRECT(main.FocusTest._TestRECT) 53 | GuiControl, % hDebug ":", % hDebugOuter, % str 54 | Sleep 100 55 | } 56 | 57 | BoolToSgn(bool){ 58 | if (bool){ 59 | return "+" 60 | } else { 61 | return "-" 62 | } 63 | } 64 | 65 | ToggleBorder: 66 | BorderState := !BorderState 67 | Gui, % main.FocusTest._hwnd ":" BoolToSgn(BorderState) "Border" 68 | 69 | return 70 | 71 | DestroyChild: 72 | main.VGTest.Destroy() 73 | main.VGTest := "" 74 | return 75 | 76 | #include CGui.ahk 77 | -------------------------------------------------------------------------------- /coordinate tests.ahk: -------------------------------------------------------------------------------- 1 | ;#include <_Struct> 2 | ;#include 3 | ; ToDo: Scroll window hotkeys so we can check that values are still correct when window scrolled 4 | ; Known Bug: Dragging Child to top / left causes crazy numbers (65536...) Singed / Unsigned / 2's comppliment issue? 5 | WM_MOVE := 0x0003, WM_SIZE := 0x0005 6 | OnMessage(WM_MOVE, "OnMove") 7 | 8 | Gui, New, +Resize hwndhMain 9 | Gui, % hMain ":Show", w200 h200 10 | Gui, New, % "+Border hwndhChild +Parent" hMain 11 | Gui, % hChild ":Show", % "w200 h200 x0 y0" 12 | 13 | ;MsgBox % sizeof(WinStructs.POINT) 14 | return 15 | 16 | Esc:: 17 | GuiClose: 18 | ExitApp 19 | 20 | F12:: 21 | ;POINT := new _Struct(WinStructs.POINT) 22 | ;POINT.x := 0 23 | ;POINT.y := 0 24 | ; Find 0,0 of Child Page relative to Parent's Range 25 | CreatePoint(0,0, POINT) 26 | _DLL_MapWindowPoints(hChild, hMain, POINT) 27 | POINT := GetPoints(POINT) 28 | ; Subtract size of top and left borders 29 | POINT := ConvertCoords(POINT, hChild) 30 | ToolTip % "x: " POINT.x "`ny: " POINT.y 31 | return 32 | 33 | OnMove(wParam, lParam, msg, hwnd){ 34 | global hChild 35 | ; Filter messages only for child window. 36 | if (hwnd = hChild){ 37 | ; x and y are coords of Child Page relative to Parent's Range 38 | x := lParam & 0xffff 39 | y := lParam >> 16 40 | ; Convert coords 41 | POINT := ConvertCoords({x: x, y: y}, hwnd) 42 | ToolTip % "x: " POINT.x "`ny: " POINT.y 43 | } 44 | } 45 | 46 | BoolToSgn(bool){ 47 | if (bool){ 48 | return "+" 49 | } else { 50 | return "-" 51 | } 52 | } 53 | 54 | CreatePoint(x,y, ByRef POINT){ 55 | VarSetCapacity(POINT, 8) 56 | NumPut(x, POINT, 0, "Uint") 57 | NumPut(y, POINT, 4, "Uint") 58 | return POINT 59 | } 60 | 61 | GetPoints(ByRef POINT){ 62 | px := NumGet(POINT,0) 63 | py := NumGet(POINT,4) 64 | return {x: px, y: py} 65 | } 66 | 67 | _DLL_MapWindowPoints(hwndFrom, hwndTo, ByRef lpPoints, cPoints := 1){ 68 | ; https://msdn.microsoft.com/en-gb/library/windows/desktop/dd145046(v=vs.85).aspx 69 | ;r := DllCall("User32.dll\MapWindowPoints", "Ptr", hwndFrom, "Ptr", hwndTo, "Ptr", lpPoints[], "Uint", cPoints, "Uint") 70 | r := DllCall("User32.dll\MapWindowPoints", "Ptr", hwndFrom, "Ptr", hwndTo, "Ptr", &lpPoints, "Uint", cPoints, "Uint") 71 | return lpPoints 72 | } 73 | 74 | ConvertCoords(coords,hwnd){ 75 | static WS_BORDER := 0x00800000, SM_CYCAPTION := 4 76 | VarSetCapacity(wi,60) 77 | DllCall("GetWindowInfo","PTR",hwnd,"PTR",&wi) 78 | ; Find size of frame (sizing handles - usually 3px) 79 | Frame := NumGet(&wi,48,"UInt") 80 | ; Does this window have a "caption" (Title) 81 | Caption := NumGet(&wi,36,"UInt") 82 | Caption := Caption & WS_BORDER 83 | if (Caption = WS_BORDER){ 84 | ; Yes - get size of caption 85 | TitleBar := DllCall("GetSystemMetrics","Int", SM_CYCAPTION) 86 | } else { 87 | ; No, window is -Border 88 | TitleBar := 0 89 | } 90 | ; Adjust coords 91 | coords.x -= Frame 92 | coords.y -= TitleBar + Frame 93 | return coords 94 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CGui 2 | A wrapper for AHK's GUI functionality, Experimental! 3 | 4 | **Requires AHK >= 1.1.20.00 or >= 2.0.a063** 5 | 6 | ##What? 7 | #####Does it do? 8 | A Class that you extend to allow you to turn a Gui or GuiControl into an object (Class). 9 | #####Are the objectives? 10 | * To simplify the syntax of AHK, whilst maintining it's functionality. 11 | * To provide extra Gui-Related features on top of what AHK normally offers. 12 | 13 | #####Are the current features? 14 | * Add, Edit Guis and GuiControls as objects 15 | * Pass objects as parameters, instead of HWNDs etc. 16 | * Persistent settings systems catered for, sample basic IniRead/Write based system included. 17 | * Example script demonstrating features 18 | 19 | ##How? 20 | #####Do I use it? 21 | * Include the script. 22 | * Derive any classes you wish to alter them. 23 | * instantiate your first window class `MyClass := new MyClass()` 24 | * Put `base.__New(aParams*)` at the start of the `__New()` constructor for your class. 25 | * Access Gui functions through `this.GUI()`, using the same syntax. 26 | eg `Gui, Add, Edit, x0 y0 w100, Text` 27 | would become `this.Gui("Add", "Edit", "x0 y0 w100", "Text")` 28 | * When adding a Gui item that you will wish to interrogate later, store a reference, like so: 29 | `this.myedit := this.Gui("Add", "Edit", "x0 y0 w100", "Text")` 30 | * Do not pass *vLabels* or *gLabels* in Option strings 31 | * Use `GuiControl` method to manipulate GuiControls 32 | * Set *gLabels* with `this.GuiContol("+g", , )` 33 | eg `this.GuiControl("+g", this.myedit, this.EditChanged)` 34 | * *vLabels* are not required - Get / Set Control properties with .value, eg `myedit.value`. 35 | * (Currently Unavailable) Use `GuiOption` to set gui options, pass objects instead of HWNDs, eg: 36 | ```AutoHotkey 37 | this.ChildWindow := new CWindow(this, "-Border") 38 | this.ChildWindow.GuiOption("+Parent", this) 39 | ``` 40 | * Functions can sometimes be chained, like so: 41 | `this.ChildWindow := new CWindow(this, "-Border").GuiOption("+Parent", this)` 42 | * An object's *HWND* is always available via it's `_hwnd` property. 43 | * An object's parent is available via it's `_parent` property. 44 | 45 | ##Why? 46 | Because it allows you to write powerful, easy to understand code, like this - 15 commands to set up a Gui with an edit box that saves between runs, plus a couple of *gLabels* that call class methods, and not a *HWND* in sight. 47 | ```AutoHotkey 48 | ; Call base method of class to create window 49 | base.__New() 50 | this.GUI_WIDTH := 200 51 | ; Start using GUI commands 52 | this.Gui("Margin",5,5) 53 | ; Add some text, dont bother storing result 54 | this.Gui("Add", "Text", "Center xm ym w" this.GUI_WIDTH, "Persistent (Remembered on Reload)") 55 | 56 | ; Add an Edit box, store a reference on this 57 | this.myedit := this.Gui("Add", "Edit","xm yp+20 w" this.GUI_WIDTH,"ChangeMe") 58 | ; Call custom INI routine to tie this Edit box to a settings value. 59 | ; This command is likely to be unique to your implementation 60 | ; It sets the current value of the control to the value from the settings file, and sets up an event to write settings as they change. 61 | this.myedit.MakePersistent("somename") 62 | ; Also set a g-label for this Edit box. Note that this is independent of the Persistence system 63 | this.GuiControl("+g", this.myedit, this.EditChanged) 64 | 65 | ; Add a Button 66 | this.mybtn := this.Gui("Add","Button","xm yp+30 w" this.GUI_WIDTH,"v Copy v") 67 | ; Wire up the button 68 | this.GuiControl("+g", this.mybtn, this.Test) ; pass object to bind g-label to, and method to bind to 69 | 70 | ; Add an edit box, but don't make it persistent 71 | this.Gui("Add", "Text", "Center xm yp+30 w" this.GUI_WIDTH, "Not Persistent (Lost on Reload)") 72 | this.myoutput := this.Gui("Add","Edit","xm yp+20 w" this.GUI_WIDTH,"") 73 | 74 | ; Add a child window 75 | ; Use GuiOption method to set parent, so we can pass the object instead of the HWND 76 | ; Note that we can chain commands. 77 | this.ChildWindow := new CWindow(this, "-Border").GuiOption("+Parent", this) 78 | this.ChildWindow.Gui("Add","Text", "Center x0 y40 w" this.GUI_WIDTH, "CHILD GUI") 79 | this.ChildWindow.Gui("Show", "x2 y150 w" this.GUI_WIDTH " h100") 80 | 81 | ; Show the main Gui 82 | this.Gui("Show", "h260","Class Test") 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /CGui.ahk: -------------------------------------------------------------------------------- 1 | ; REQUIRES AHK >= v1.1.20.00 2 | ; DEPENDENCIES: 3 | ; _Struct(): https://raw.githubusercontent.com/HotKeyIt/_Struct/master/_Struct.ahk - docs: http://www.autohotkey.net/~HotKeyIt/AutoHotkey/_Struct.htm 4 | ; sizeof(): https://raw.githubusercontent.com/HotKeyIt/_Struct/master/sizeof.ahk - docs: http://www.autohotkey.net/~HotKeyIt/AutoHotkey/sizeof.htm 5 | ; WinStructs: https://github.com/ahkscript/WinStructs 6 | #SingleInstance force 7 | 8 | #include <_Struct> 9 | #include 10 | #include sample inihandler.ahk 11 | 12 | mc := new MyClass(0, "+Resize") 13 | 14 | ; Example class using CGui 15 | class MyClass extends CGui { 16 | __New(aParams*){ 17 | global BorderState 18 | 19 | base.__New(aParams*) 20 | this.Show("w300 h300 y0", "CGui Demo - " this._hwnd) 21 | 22 | this.FocusTest := new this._FocusTest(this, "+Border +Resize +Parent" this._hwnd) 23 | this.FocusTest.Show("w150 h150 x150 y150") 24 | 25 | this.VGTest := new this._VGTest(this, "-Border +Parent" this._hwnd) 26 | this.VGTest.Show("w170 h110 x0 y0", "") 27 | 28 | } 29 | 30 | class _VGTest extends CGui { 31 | __New(aParams*){ 32 | base.__New(aParams*) 33 | 34 | this.MyEdit1 := this.Gui("Add", "Edit", "w150") 35 | this.GuiControl("+g", this.MyEdit1, this.EditChanged) 36 | 37 | this.MyButton := this.Gui("Add", "Button", "w150", "v Copy v") 38 | this.GuiControl("+g", this.MyButton, this.ButtonPressed) 39 | 40 | this.Gui("Add", "Text", "w150 Center", "This box is Persistent") 41 | 42 | this.MyEdit2 := this.Gui("Add", "Edit", "w150") 43 | this.MyEdit2.MakePersistent("MyEdit2") 44 | } 45 | 46 | ButtonPressed(){ 47 | this.MyEdit2.value := this.MyEdit1.value 48 | } 49 | 50 | EditChanged(){ 51 | this.ToolTip(this.MyEdit1.value) 52 | } 53 | } 54 | 55 | class _FocusTest extends CGui { 56 | __New(aParams*){ 57 | base.__New(aParams*) 58 | 59 | Loop 8 { 60 | this.Gui("Add", "Edit", "w300", "Item " A_Index) 61 | } 62 | 63 | } 64 | } 65 | 66 | } 67 | 68 | Esc:: 69 | GuiClose: 70 | ExitApp 71 | 72 | ; Wraps All Gui commands - Guis and GuiControls 73 | class _CGui extends _CGuiBase { 74 | _type := "w" ; Window Type 75 | 76 | _ChildGuis := {} 77 | _ChildControls := {} 78 | 79 | ; ScrollInfo array - Declared as associative, but consider 0-based indexed. 0-based so SB_HORZ / SB_VERT map to correct elements. 80 | _ScrollInfos := {0: 0, 1: 0} 81 | 82 | ; ========================================== GUI COMMAND WRAPPERS ============================= 83 | ; Equivalent to Gui, New 84 | __New(parent := 0, options := 0, aParams*){ 85 | Static SB_HORZ := 0, SB_VERT := 1 86 | static WM_MOVE := 0x0003, WM_SIZE := 0x0005 87 | static WM_HSCROLL := 0x0114, WM_VSCROLL := 0x0115 88 | Static WM_MOUSEWHEEL := 0x020A, WM_MOUSEHWHEEL := 0x020E 89 | ;static WM_CLOSE := 0x0010 90 | 91 | this._parent := parent 92 | 93 | Gui, new, % "hwndhwnd " options 94 | this._hwnd := hwnd 95 | 96 | if (this._parent != 0){ 97 | this._parent._ChildGuis[this._hwnd] := this 98 | } 99 | 100 | ; Initialize page and range classes so that all values read 0 101 | this._RangeRECT := new this.RECT() 102 | this._PageRECT := new this.RECT() 103 | this._WindowRECT := new this.RECT() 104 | ;this._TestRECT := new this.RECT() 105 | 106 | ; Initialize scroll info array 107 | this._ScrollInfos := {0: this._DLL_GetScrollInfo(SB_HORZ), 1: this._DLL_GetScrollInfo(SB_VERT)} 108 | 109 | ; Register for ReSize messages 110 | this._RegisterMessage(WM_SIZE,this._OnSize) 111 | 112 | ; Register for scroll (drag of thumb) messages 113 | this._RegisterMessage(WM_HSCROLL,this._OnScroll) 114 | this._RegisterMessage(WM_VSCROLL,this._OnScroll) 115 | 116 | ; Register for move message. 117 | this._RegisterMessage(WM_MOVE,this._OnMove) 118 | 119 | ; Mouse Wheel 120 | this._RegisterMessage(WM_MOUSEWHEEL,this._OnWheel) 121 | this._RegisterMessage(WM_MOUSEHWHEEL,this._OnWheel) 122 | 123 | ; Close Gui - need method 124 | ;this._RegisterMessage(WM_CLOSE, this._OnExit) 125 | 126 | } 127 | 128 | __Delete(){ 129 | ;MsgBox % "GUI DELETE - " this._hwnd 130 | Gui, % this._hwnd ":Destroy" 131 | ;this._parent._GuiChildChangedRange() 132 | ; If top touches range top, left touches page left, right touches page right, or bottom touches page bottom... 133 | ; Removing this GuiControl should trigger a RANGE CHANGE. 134 | ; Same for Gui, Hide? 135 | } 136 | 137 | Destroy(){ 138 | this._ChildControls := "" 139 | for msg in _CGui._MessageArray { 140 | for hwnd in _CGui._MessageArray[msg] { 141 | if (hwnd = this._hwnd){ 142 | ; ALWAYS use .Remove(key, "") else indexes of remaining keys will be altered. 143 | _CGui._MessageArray[msg].Remove(hwnd,"") 144 | } 145 | } 146 | } 147 | ;MsgBox % this._hwnd 148 | this._parent._ChildGuis.Remove(this._hwnd, "") 149 | this._parent._GuiChildChangedRange() 150 | ; Remove all child objects that could stop __Delete firing. 151 | for key in this { 152 | if (this[key]._parent = this){ 153 | this[key]._parent := "" 154 | } 155 | } 156 | } 157 | 158 | ; Simple patch to prefix Gui commands with HWND 159 | PrefixHwnd(cmd){ 160 | return this._hwnd ":" cmd 161 | } 162 | 163 | ; Equivalent to Gui, Show 164 | Show(options := "", title := ""){ 165 | Gui, % this.PrefixHwnd("Show"), % options, % title 166 | ;this._GuiSetWindowRECT() 167 | if (this._parent != 0){ 168 | ; Parent GUIs get a WM_MOVE message at start - emulate for children. 169 | this._OnMove() 170 | } 171 | } 172 | 173 | ; Wrapper for Gui commands 174 | Gui(cmd, aParams*){ 175 | if (cmd = "add"){ 176 | ; Create GuiControl 177 | obj := new this._CGuiControl(this, aParams*) 178 | return obj 179 | } else if (cmd = "new"){ 180 | obj := new _CGui(this, aParams*) 181 | return obj 182 | } 183 | } 184 | 185 | ; Wraps GuiControl to use hwnds and function binding etc 186 | GuiControl(cmd := "", ctrl := "", Param3 := ""){ 187 | m := SubStr(cmd,1,1) 188 | if (m = "+" || m = "-"){ 189 | ; Options 190 | o := SubStr(cmd,2,1) 191 | if (o = "g"){ 192 | ; Bind g-label to _glabel property 193 | fn := Param3.Bind(this) 194 | ctrl._glabel := fn 195 | return this 196 | } 197 | } else { 198 | GuiControl, % this._hwnd ":" cmd, % ctrl._hwnd, % Param3 199 | return this 200 | } 201 | } 202 | 203 | ; Wraps GuiControlGet 204 | GuiControlGet(cmd := "", ctrl := "", param4 := ""){ 205 | GuiControlGet, ret, % this._hwnd ":" cmd, % ctrl._hwnd, % Param4 206 | return ret 207 | } 208 | ; ========================================== DIMENSIONS ======================================= 209 | 210 | /* 211 | ; The PAGE (Size of window) of a Gui / GuiControl changed. For GuiControls, this is the size of the control 212 | _GuiPageGetRect(){ 213 | RECT := this._DLL_GetClientRect() 214 | return RECT 215 | } 216 | */ 217 | 218 | ; A Child of this window changed it's usage of this window's RANGE (in other words, it moved or changed size) 219 | ; old = the Child's old WindowRECT 220 | _GuiChildChangedRange(Child := 0, old := 0){ 221 | static opposites := {top: "bottom", left: "right", bottom: "top", right: "left"} 222 | shrank := 0 223 | 224 | if (child = 0){ 225 | ; Destroy 226 | Count := 0 227 | for childHwnd in this._ChildGuis { 228 | if (!Count){ 229 | this._RangeRECT := new this.RECT() 230 | this._RangeRECT.Union(this._ChildGuis[childHwnd]._WindowRECT) 231 | Count++ 232 | continue 233 | } 234 | this._RangeRECT.Union(this._ChildGuis[childHwnd]._WindowRECT) 235 | } 236 | this._GuiSetScrollbarSize() 237 | return 238 | } 239 | if (!this._RangeRECT.contains(Child._WindowRECT)){ 240 | ;if (this._RangeRECT.Union(Child._WindowRECT)){ 241 | this._RangeRECT.Union(Child._WindowRECT) 242 | this._GuiSetScrollbarSize() 243 | } else { 244 | ; Detect if child touching edge of range. 245 | ; set the new _WindowRECT and find out how much we moved, and in what direction. 246 | moved := this._GuiGetMoveAmount(old, Child._WindowRECT) 247 | for dir in opposites { 248 | ;if (dir = "down"){ 249 | ;ToolTip % "moved " this._SerializeRECT(moved) 250 | ;} 251 | if (moved[dir] > 0){ 252 | ;shrank := 1 253 | opp := opposites[dir] 254 | ;ToolTip % this.name "-" dir "`nOld: " old[opp] " = " this._RangeRECT[opp] " ?" 255 | if (old[opp] = this._RangeRECT[opp]){ 256 | shrank := 1 257 | break 258 | } 259 | } 260 | } 261 | if (shrank){ 262 | ; The child was touching an edge of our RANGE, and moved away from it ... 263 | ; ... Union WindowRECTs of all *other* children to see if this child is the only one needing that part of the range ... 264 | ; ... And if so, shrink our Range. 265 | 266 | Count := 0 267 | for childHwnd in this._ChildGuis { 268 | if (childHwnd = child._hwnd){ 269 | ;MsgBox % "Skipping " this._ChildGuis[childHwnd].name 270 | continue 271 | } 272 | if (!Count){ 273 | this._RangeRECT := new this.RECT() 274 | this._RangeRECT.Union(this._ChildGuis[childHwnd]._WindowRECT) 275 | ;MsgBox % "including " this._ChildGuis[childHwnd].name 276 | Count++ 277 | continue 278 | } 279 | ;MsgBox % "including " this._ChildGuis[childHwnd].name 280 | this._RangeRECT.Union(this._ChildGuis[childHwnd]._WindowRECT) 281 | Count++ 282 | } 283 | ;if (!this._RangeRECT.contains(old[childHwnd]._WindowRECT)){ 284 | if (!this._RangeRECT.contains(Child._WindowRECT)){ 285 | this._RangeRECT.Union(Child._WindowRECT) 286 | this._GuiSetScrollbarSize() 287 | } 288 | } 289 | /* 290 | if (shrank){ 291 | ToolTip % this.name " shrank`n" this._SerializeRECT(this._RangeRECT) "`nChild: " this._SerializeRECT(Child._WindowRECT) 292 | } else { 293 | ToolTip 294 | } 295 | */ 296 | } 297 | ;ToolTip % A_ThisFunc "`nOld: " this._SerializeRECT(oldrange) "`nNew: " this._SerializeRECT(this._RangeRECT) 298 | } 299 | 300 | /* 301 | ; Works out how big a window's caption / borders are and alters passed coords accordingly. 302 | ConvertCoords(coords,hwnd){ 303 | static WS_BORDER := 0x00800000, SM_CYCAPTION := 4 304 | VarSetCapacity(wi,60) 305 | DllCall("GetWindowInfo","PTR",hwnd,"PTR",&wi) 306 | ; Find size of frame (sizing handles - usually 3px) 307 | Frame := NumGet(&wi,48,"UInt") 308 | ; Does this window have a "caption" (Title) 309 | Caption := NumGet(&wi,36,"UInt") 310 | Caption := Caption & WS_BORDER 311 | if (Caption = WS_BORDER){ 312 | ; Yes - get size of caption 313 | TitleBar := DllCall("GetSystemMetrics","Int", SM_CYCAPTION) 314 | } else { 315 | ; No, window is -Border 316 | TitleBar := 0 317 | } 318 | ; Adjust coords 319 | coords := {x: coords.x, y: coords.y} 320 | ;ToolTip % coords.x 321 | coords.x -= Frame 322 | coords.y -= TitleBar + Frame 323 | return coords 324 | } 325 | */ 326 | 327 | ; ========================================== SCROLL BARS ====================================== 328 | 329 | ; Is the scrollbar at maximum? (ie all the way at the end). 330 | _IsScrollBarAtMaximum(bar){ 331 | end := this._ScrollInfos[bar].nPos + this._ScrollInfos[bar].nPage 332 | diff := ( this._ScrollInfos[bar].nMax - end ) * -1 333 | if (diff > 0){ 334 | return diff 335 | } else { 336 | return 0 337 | } 338 | } 339 | 340 | ; Set the POSITION component of a scrollbar 341 | _GuiSetScrollbarPos(nTrackPos, bar){ 342 | Static SB_HORZ := 0, SB_VERT := 1 343 | static SIF_POS := 0x4 344 | 345 | this._ScrollInfos[bar].fMask := SIF_POS 346 | this._ScrollInfos[bar].nPos := nTrackPos 347 | this._DLL_SetScrollInfo(bar, this._ScrollInfos[bar]) 348 | } 349 | 350 | ; Set the SIZE component(s) of a scrollbar - PAGE and RANGE 351 | ; bars = 0 = SB_HORZ 352 | ; bars = 1 = SB_VERT 353 | ; bars = 2 (or omit bars) = both bars 354 | _GuiSetScrollbarSize(bars := 2, PageRECT := 0, RangeRECT := 0, mode := "b"){ 355 | Static SB_HORZ := 0, SB_VERT := 1 356 | static SIF_DISABLENOSCROLL := 0x8 357 | static SIF_RANGE := 0x1, SIF_PAGE := 0x2, SIF_POS := 0x4, SIF_ALL := 0x17 358 | ; Index Min / Max property names of a RECT by SB_HORZ = 0, SB_VERT = 1 359 | static RECTProperties := { 0: {min: "Left", max: "Right"}, 1: {min: "Top", max: "Bottom"} } 360 | 361 | ; Determine what part of the scrollbars we wish to set. 362 | if (mode = "p"){ 363 | ; Set PAGE 364 | mask := SIF_PAGE 365 | } else if (mode = "r"){ 366 | ; Set RANGE 367 | mask := SIF_RANGE 368 | } else { 369 | ; Default to SET PAGE + RANGE 370 | mask := SIF_PAGE | SIF_RANGE 371 | } 372 | ;mask |= SIF_DISABLENOSCROLL ; If the scroll bar's new parameters make the scroll bar unnecessary, disable the scroll bar instead of removing it 373 | ;mask := SIF_ALL 374 | 375 | ; If no RECTs passed, use class properties 376 | if (PageRECT = 0){ 377 | PageRECT := this._PageRECT 378 | } 379 | if (RangeRECT = 0){ 380 | RangeRECT := this._RangeRECT 381 | } 382 | 383 | ; Alter scroll bars due to client size 384 | Loop 2 { 385 | bar := A_Index - 1 ; SB_HORZ = 0, SB_VERT = 1 386 | if ( ( bar=0 && (bars = 0 || bars = 2) ) || ( bar=1 && bars > 0 ) ){ 387 | ; If this scroll bar was specified ... 388 | ; ... Adjust this window's ScrollBar Struct as appropriate, ... 389 | this._ScrollInfos[bar].fMask := mask 390 | if (mask & SIF_RANGE){ 391 | ; Range bits set 392 | this._ScrollInfos[bar].nMin := RangeRECT[RECTProperties[bar].min] 393 | this._ScrollInfos[bar].nMax := RangeRECT[RECTProperties[bar].max] 394 | } 395 | 396 | if (mask & SIF_PAGE){ 397 | ; Page bits set 398 | this._ScrollInfos[bar].nPage := PageRECT[RECTProperties[bar].max] 399 | } 400 | ; ... Then update the Scrollbar. 401 | this._DLL_SetScrollInfo(bar, this._ScrollInfos[bar]) 402 | } 403 | 404 | ; If a vertical scrollbar is showing, and you are scrolled all the way to the bottom of the page... 405 | ; ... If you grab the bottom edge of the window and size up, the contents must scroll downwards. 406 | ; I call this a Size-Scroll. 407 | if (this._ScrollInfos[bar].nPage <= this._ScrollInfos[bar].nMax){ 408 | ; Page (Size of window) is less than Max (Size of contents) - scrollbars will be showing. 409 | diff := this._IsScrollBarAtMaximum(bar) 410 | 411 | if (diff > 0){ 412 | ; diff is positive, Size-Scroll required 413 | ; Set up vars for call 414 | if (bar) { 415 | h := 0 416 | v := diff 417 | } else { 418 | h := diff 419 | v := 0 420 | } 421 | ; Size-Scroll the contents. 422 | this._DLL_ScrollWindows(h,v) 423 | if (bar){ 424 | this._ScrollInfos[bar].nPos -= v 425 | } else { 426 | this._ScrollInfos[bar].nPos -= h 427 | } 428 | } 429 | } 430 | } 431 | } 432 | 433 | ; Sets cbSize, returns blank scrollinfo 434 | _BlankScrollInfo(){ 435 | lpsi := new _Struct(WinStructs.SCROLLINFO) 436 | lpsi.cbSize := sizeof(WinStructs.SCROLLINFO) 437 | return lpsi 438 | } 439 | 440 | ; ========================================== DLL CALLS ======================================== 441 | 442 | ; ACCEPTS x, y 443 | ; Returns a POINT 444 | _DLL_ScreenToClient(hwnd, x, y){ 445 | ; https://msdn.microsoft.com/en-gb/library/windows/desktop/dd162952(v=vs.85).aspx 446 | lpPoint := new _Struct(WinStructs.POINT, {x: x, y: y}) 447 | r := DllCall("User32.dll\ScreenToClient", "Ptr", hwnd, "Ptr", lpPoint[], "Uint") 448 | return lpPoint 449 | } 450 | 451 | _DLL_MapWindowPoints(hwndFrom, hwndTo, ByRef lpPoints, cPoints := 2){ 452 | ; https://msdn.microsoft.com/en-gb/library/windows/desktop/dd145046(v=vs.85).aspx 453 | ;lpPoints := new _Struct(WinStructs.RECT) 454 | r := DllCall("User32.dll\MapWindowPoints", "Ptr", hwndFrom, "Ptr", hwndTo, "Ptr", lpPoints[], "Uint", cPoints, "Uint") 455 | return lpPoints 456 | } 457 | 458 | ; Wraps ScrollWindow() DLL Call. 459 | _DLL_ScrollWindows(XAmount, YAmount, hwnd := 0){ 460 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb787591%28v=vs.85%29.aspx 461 | if (!hwnd){ 462 | hwnd := this._hwnd 463 | } 464 | ;tooltip % "Scrolling " hwnd 465 | return DllCall("User32.dll\ScrollWindow", "Ptr", hwnd, "Int", XAmount, "Int", YAmount, "Ptr", 0, "Ptr", 0) 466 | } 467 | 468 | ; Wraps ScrollWindow() DLL Call. 469 | _DLL_ScrollWindow(bar, Amount, hwnd := 0){ 470 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb787591%28v=vs.85%29.aspx 471 | if (!hwnd){ 472 | hwnd := this._hwnd 473 | } 474 | if (bar){ 475 | XAmount := 0 476 | YAmount := Amount 477 | } else { 478 | XAmount := Amount 479 | YAmount := 0 480 | } 481 | ;tooltip % "Scrolling " hwnd 482 | return DllCall("User32.dll\ScrollWindow", "Ptr", hwnd, "Int", XAmount, "Int", YAmount, "Ptr", 0, "Ptr", 0) 483 | } 484 | 485 | /* 486 | ; Wraps GetClientRect() Dll call, returns RECT class (Not Structure! Class!) 487 | _DLL_GetClientRect(hwnd := 0){ 488 | if (hwnd = 0){ 489 | hwnd := this._hwnd 490 | } 491 | RECT := new this.RECT() 492 | DllCall("User32.dll\GetClientRect", "Ptr", hwnd, "Ptr", RECT[]) 493 | return RECT 494 | } 495 | */ 496 | 497 | ; Wraps SetScrollInfo() Dll call. 498 | ; Returns Dll Call return value 499 | _DLL_SetScrollInfo(fnBar, ByRef lpsi, fRedraw := 1, hwnd := 0){ 500 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb787595%28v=vs.85%29.aspx 501 | if (hwnd = 0){ 502 | ; Normal use - operate on youurself. Passed hwnd = inspect another window 503 | hwnd := this._hwnd 504 | } 505 | return DllCall("User32.dll\SetScrollInfo", "Ptr", hwnd, "Int", fnBar, "Ptr", lpsi[], "UInt", fRedraw, "UInt") 506 | } 507 | 508 | ;_DLL_GetScrollInfo(fnBar, ByRef lpsi, hwnd := 0){ 509 | ; returns a SCROLLINFO structure 510 | _DLL_GetScrollInfo(fnBar, hwnd := 0){ 511 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb787583%28v=vs.85%29.aspx 512 | static SIF_ALL := 0x17 513 | if (hwnd = 0){ 514 | ; Normal use - operate on youurself. Passed hwnd = inspect another window 515 | hwnd := this._hwnd 516 | } 517 | lpsi := this._BlankScrollInfo() 518 | lpsi.fMask := SIF_ALL 519 | r := DllCall("User32.dll\GetScrollInfo", "Ptr", hwnd, "Int", fnBar, "Ptr", lpsi[], "UInt") 520 | return lpsi 521 | ;Return r 522 | } 523 | 524 | _DLL_GetParent(hwnd := 0){ 525 | if (hwnd = 0){ 526 | hwnd := this._hwnd 527 | } 528 | return DllCall("GetParent", "Uint", hwnd, "Uint") 529 | } 530 | 531 | ; ========================================== MESSAGES ========================================= 532 | 533 | ; All messages route through here. Only one message of each kind will be registered, to avoid noise and make debugging easier. 534 | _MessageHandler(wParam, lParam, msg, hwnd){ 535 | ; Call the callback associated with this Message and HWND 536 | (_CGui._MessageArray[msg][hwnd]).(wParam, lParam, msg, hwnd) 537 | } 538 | 539 | ; Register a message with the Message handler. 540 | _RegisterMessage(msg, callback){ 541 | newmessage := 0 542 | if (!ObjHasKey(_CGui, "_MessageArray")){ 543 | _Cgui._MessageArray := {} 544 | } 545 | if (!ObjHasKey(_CGui._MessageArray, msg)){ 546 | _CGui._MessageArray[msg] := {} 547 | newmessage := 1 548 | } 549 | 550 | ; Add the callback to _MessageArray, so that _MessageHandler can look it up and route to it. 551 | ; Store Array on _CGui, so any class can call it's own .RegisterMessage property. 552 | fn := callback.Bind(this) 553 | _CGui._MessageArray[msg][this._hwnd] := fn 554 | 555 | ; Only subscribe to message if this message has not already been subscribed to. 556 | if (newmessage){ 557 | fn := this._MessageHandler.Bind(this) 558 | OnMessage(msg, fn) 559 | } 560 | } 561 | 562 | ; A scrollbar was dragged 563 | _OnScroll(wParam, lParam, msg, hwnd){ 564 | ;SoundBeep 565 | ; Handles: 566 | ; WM_VSCROLL https://msdn.microsoft.com/en-gb/library/windows/desktop/bb787577(v=vs.85).aspx 567 | ; WM_HSCROLL https://msdn.microsoft.com/en-gb/library/windows/desktop/bb787575(v=vs.85).aspx 568 | Critical 569 | static WM_HSCROLL := 0x0114, WM_VSCROLL := 0x0115 570 | Static SB_HORZ := 0, SB_VERT := 1 571 | static SB_LINEUP := 0x0, SB_LINEDOWN := 0x1, SB_PAGEUP := 0x2, SB_PAGEDOWN := 0x3, SB_THUMBPOSITION := 0x4, SB_THUMBTRACK := 0x5, SB_TOP := 0x6, SB_BOTTOM := 0x7, SB_ENDSCROLL := 0x8 572 | 573 | if (msg = WM_HSCROLL || msg = WM_VSCROLL){ 574 | bar := msg - 0x114 575 | } else { 576 | SoundBeep 577 | return 578 | } 579 | ScrollInfo := this._DLL_GetScrollInfo(bar) 580 | ;OutputDebug, % "SI: " ScrollInfo.nTrackPos ", Bar: " bar 581 | 582 | if (wParam = SB_LINEUP || wParam = SB_LINEDOWN || wParam = SB_PAGEUP || wParam = SB_PAGEDOWN){ 583 | ; Line scrolling (Arrows at end of scrollbar clicked) or Page scrolling (Area between handle and arrows clicked) 584 | ; Is an unimplemented flag 585 | ; wParam is direction 586 | ; msg is horiz / vert 587 | if (wParam = SB_PAGEUP || wParam = SB_PAGEDOWN){ 588 | wParam -= 2 589 | line := ScrollInfo.nPage 590 | } else { 591 | line := 20 592 | } 593 | amt := 0 594 | max := ScrollInfo.nMax - ScrollInfo.nPage 595 | if (wParam){ 596 | ; down 597 | amt := ScrollInfo.nPos + line 598 | if (amt > max){ 599 | amt := max 600 | } 601 | } else { 602 | ; up 603 | amt := ScrollInfo.nPos - line 604 | if (amt < ScrollInfo.nMin){ 605 | amt := ScrollInfo.nMin 606 | } 607 | } 608 | newpos := amt 609 | amt := ScrollInfo.nPos - amt 610 | if (amt){ 611 | ; IMPORTANT! Update scrollbar pos BEFORE scrolling window. 612 | ; Scrolling window trips _OnMove for children, and scrollbar pos is used by them to determine coordinates. 613 | this._GuiSetScrollbarPos(newpos, bar) 614 | this._DLL_ScrollWindow(bar, amt) 615 | } 616 | /* 617 | } else if (wParam = SB_THUMBTRACK){ 618 | ; "The user is dragging the scroll box. This message is sent repeatedly until the user releases the mouse button" 619 | ; This is bundled in with the drags, as same code seems good. 620 | } else if (wParam = SB_THUMBPOSITION || wParam = SB_ENDSCROLL){ 621 | ; This is bundled in with the drags, as same code seems good. 622 | this._GuiSetScrollbarPos(ScrollInfo.nTrackPos, bar) 623 | } else if (wParam = SB_TOP || wParam = SB_BOTTOM) { 624 | ; "Scrolls to the upper left" / "Scrolls to the lower right" 625 | ; Not entirely sure what these are for, disable for now 626 | SoundBeep, 100, 100 627 | */ 628 | } else if (wParam = SB_ENDSCROLL){ 629 | ; Seem to not need to implement this. 630 | ; Routing it through to the main drag block breaks page scrolling (arrow buttons or wheel) 631 | } else { 632 | ; Drag of scrollbar 633 | ; Handles SB_THUMBTRACK, SB_THUMBPOSITION, SB_ENDSCROLL Flags (Indicated by wParam has set LOWORD, Highest value is 0x8 which is SB_ENDSCROLL) ... 634 | ; These Flags generally only get set once each per drag. 635 | ; ... Also handles drag of scrollbar (wParam has set HIWORD = "current position of the scroll box"), so wParam will be very big. 636 | ; This HIWORD "Flag" gets set lots of times per drag. 637 | 638 | ; Set the scrollbar pos BEFORE scrolling the window. 639 | ; Scrolling the window will cause WM_MOVE to trigger for children... 640 | ; ... And the position of the window needs to match the postion of the scrollbars at that point in time. 641 | pos := (ScrollInfo.nTrackPos - this._ScrollInfos[bar].nPos) * -1 642 | this._GuiSetScrollbarPos(ScrollInfo.nTrackPos, bar) 643 | 644 | if (bar){ 645 | ; Vertical Bar 646 | h := 0 647 | ;v := (ScrollInfo.nTrackPos - this._ScrollInfos[bar].nPos) * -1 648 | v := pos 649 | } else { 650 | ; Horiz Bar 651 | ;h := (ScrollInfo.nTrackPos - this._ScrollInfos[bar].nPos) * -1 652 | h := pos 653 | v := 0 654 | } 655 | ;OutputDebug, % "[ " this._FormatHwnd() " ] " this._SetStrLen(A_ThisFunc) " Scrolling window by (x,y) " h ", " v " - new Pos: " this._ScrollInfos[bar].nPos 656 | ;ToolTip % ScrollInfo.nPos "," ScrollInfo.nPage "," ScrollInfo.nMax 657 | this._DLL_ScrollWindows(h, v) 658 | } 659 | } 660 | 661 | ; Adjust this._PageRECT when Gui Size Changes (ie it was Resized) 662 | ; Handles WM_SIZE message 663 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms632646%28v=vs.85%29.aspx 664 | _OnSize(wParam, lParam, msg, hwnd){ 665 | ; ToDo: Need to check if hwnd matches this._hwnd ? 666 | static SIZE_RESTORED := 0, SIZE_MINIMIZED := 1, SIZE_MAXIMIZED := 2, SIZE_MAXSHOW := 3, SIZE_MAXHIDE := 4 667 | 668 | if (wParam = SIZE_RESTORED || wParam = SIZE_MAXIMIZED){ 669 | old := this._WindowRECT.clone() 670 | w := lParam & 0xffff 671 | h := lParam >> 16 672 | if (w != this._PageRECT.Right || h != this._PageRECT.Bottom){ 673 | ; Gui Size Changed - update PAGERECT 674 | this._PageRECT.Right := w 675 | this._PageRECT.Bottom := h 676 | } 677 | this._GuiSetWindowRECT() 678 | ; Adjust Scrollbars if required 679 | this._GuiSetScrollbarSize() 680 | ;this._parent._GuiSetScrollbarSize() 681 | this._parent._GuiChildChangedRange(this, old) 682 | } 683 | } 684 | 685 | ; Called when a GUI Moves. 686 | ; If the GUI moves outside it's parent's RECT, enlarge the parent's RANGE 687 | ; ToDo: Needs work? buggy? 688 | ; OnMove seems to be called for a window when you scroll a window containing it. 689 | _OnMove(wParam := 0, lParam := 0, msg := 0, hwnd := 0){ 690 | Critical 691 | old := this._WindowRECT.clone() 692 | this._GuiSetWindowRECT(wParam, lParam, msg, hwnd) 693 | ;ToolTip % A_ThisFunc "`nOld: " this._SerializeRECT(old) "`nNew: " this._SerializeRECT(this._WindowRECT) 694 | ;if (!this._WindowRECT.Equals(old)){ 695 | ;if (!this._parent._RangeRECT.contains(this._WindowRECT)){ 696 | this._parent._GuiChildChangedRange(this, old) 697 | ;} 698 | return 699 | } 700 | 701 | ; Handles mouse wheel messages 702 | _OnWheel(wParam, lParam, msg, hwnd){ 703 | Static MK_SHIFT := 0x0004 704 | Static SB_LINEMINUS := 0, SB_LINEPLUS := 1 705 | Static WM_MOUSEWHEEL := 0x020A, WM_MOUSEHWHEEL := 0x020E 706 | Static WM_HSCROLL := 0x0114, WM_VSCROLL := 0x0115 707 | Static SB_HORZ := 0, SB_VERT := 1 708 | Critical 709 | 710 | MSG := (Msg = WM_MOUSEWHEEL ? WM_VSCROLL : WM_HSCROLL) 711 | bar := msg - 0x114 712 | has_scrollbars := 0 713 | MouseGetPos,,,,hcurrent,2 714 | if (hcurrent != ""){ 715 | has_scrollbars := this._HasScrollbar(bar, hcurrent) 716 | } 717 | if (has_scrollbars = 0){ 718 | ; No Sub-item found under cursor, get which main parent gui is under the cursor. 719 | MouseGetPos,,,hcurrent 720 | has_scrollbars := this._HasScrollbar(bar, hcurrent) 721 | } 722 | ; Drill down through Hwnds until one is found with scrollbars showing. 723 | while (!has_scrollbars){ 724 | hcurrent := this._DLL_GetParent(hcurrent) 725 | has_scrollbars := this._HasScrollbar(bar, hcurrent) 726 | if (hcurrent = 0){ 727 | ; No parent found - end 728 | break 729 | } 730 | } 731 | ; No candidates found - route scroll to main window 732 | if (!has_scrollbars){ 733 | ;MsgBox % this.FormatHex(hwnd) 734 | hcurrent := hwnd 735 | return 736 | } 737 | 738 | if (!ObjHasKey(_CGui._MessageArray[msg], hcurrent)){ 739 | return 740 | } 741 | 742 | SB := ((wParam >> 16) > 0x7FFF) || (wParam < 0) ? SB_LINEPLUS : SB_LINEMINUS 743 | 744 | (_CGui._MessageArray[msg][hcurrent]).(sb, 0, msg, hcurrent) 745 | ;return 0 746 | } 747 | 748 | _OnExit(wParam, lParam, msg, hwnd){ 749 | ; Need to find close message 750 | MsgBox EXIT 751 | } 752 | 753 | 754 | ; ========================================== MISC ============================================= 755 | 756 | ; Converts a rect to a debugging string 757 | _SerializeRECT(RECT) { 758 | return "T: " RECT.Top "`tL: " RECT.Left "`tB: " RECT.Bottom "`tR: " RECT.Right 759 | } 760 | 761 | _HasScrollbar(bar, hwnd){ 762 | sb := this._DLL_GetScrollInfo(bar, hwnd) 763 | if (sb.nPage && (sb.nPage <= sb.nMax)){ 764 | return 1 765 | } else { 766 | return 0 767 | } 768 | } 769 | 770 | BoolToSgn(bool){ 771 | if (bool){ 772 | return "+" 773 | } else { 774 | return "-" 775 | } 776 | } 777 | 778 | 779 | ToolTip(Text, duration := 500){ 780 | fn := this.ToolTipTimer.bind(this) 781 | this._TooltipActive := fn 782 | SetTimer, % fn, % "-" duration 783 | ToolTip % Text 784 | } 785 | 786 | ToolTipTimer(){ 787 | ToolTip 788 | } 789 | 790 | ; ========================================== CLASSES ========================================== 791 | 792 | ; Wraps GuiControls into an Object 793 | class _CGuiControl extends _CGuiBase { 794 | _type := "c" ; Control Type 795 | _glabel := 0 796 | ; Equivalent to Gui, Add 797 | __New(parent, ctrltype, options := "", text := ""){ 798 | this._parent := parent 799 | this._ctrltype := ctrltype 800 | Gui, % this._parent.PrefixHwnd("Add"), % ctrltype, % "hwndhwnd " options, % text 801 | ;this._parent.Gui("add", ctrltype, "hwndhwnd " options 802 | this._hwnd := hwnd 803 | 804 | ; Hook into OnChange event 805 | fn := this._OnChange.bind(this) 806 | GuiControl % "+g", % this._hwnd, % fn 807 | 808 | ; Add self to parent's list of GuiControls 809 | this._parent._ChildControls[this._hwnd] := this 810 | 811 | ; Set up RECTs for scrollbars 812 | this._WindowRECT := new this.RECT() 813 | this._GuiSetWindowRECT() 814 | 815 | ; Tell parent to adjust scrollbars 816 | this._parent._GuiChildChangedRange(this, this._WindowRECT) 817 | 818 | } 819 | 820 | __Delete(){ 821 | ;MsgBox % "CONTROL DELETE - " this._hwnd 822 | ;Gui, % this._parent.PrefixHwnd("Add"), % ctrltype, % "hwndhwnd " options, % text 823 | DllCall("DestroyWindow", "UInt", this._hwnd) 824 | 825 | ; If top touches range top, left touches page left, right touches page right, or bottom touches page bottom... 826 | ; Removing this GuiControl should trigger a RANGE CHANGE. 827 | ; Same for Hiding a GuiControl? 828 | } 829 | 830 | __Get(aParam){ 831 | if (aParam = "value"){ 832 | return this._parent.GuiControlGet(,this) 833 | } 834 | } 835 | 836 | __Set(aParam, aValue){ 837 | if (aParam = "value"){ 838 | return this._parent.GuiControl(,this, aValue) 839 | } 840 | } 841 | 842 | ; Removes a GUIControl. 843 | ; Does NOT remove the last reference to the Control on the parent, call Destroy, then unset manually to fire __Delete() 844 | ; eg this.childcontrol.Destroy() 845 | ; this.childcontrol := "" 846 | Destroy(){ 847 | this._parent._ChildControls.Remove(this._hwnd, "") 848 | this._parent._GuiChildChangedRange() 849 | } 850 | 851 | _OnChange(){ 852 | ; Provide hook into change event 853 | this.OnChange() 854 | 855 | ; Call bound function if present 856 | if (ObjHasKey(this,"_glabel") && this._glabel != 0){ 857 | (this._glabel).() 858 | } 859 | } 860 | 861 | ; Override to hook into change event independently of g-labels 862 | OnChange(){ 863 | 864 | } 865 | } 866 | 867 | } 868 | 869 | ; ================================================================================================= 870 | ; ================================================================================================= 871 | 872 | ; A base class, purely for inheriting. 873 | class _CGuiBase { 874 | ; ========================================== CLASSES ========================================== 875 | 876 | ; RECT class. Wraps _Struct to provide functionality similar to C 877 | ; https://msdn.microsoft.com/en-us/library/system.windows.rect(v=vs.110).aspx 878 | class RECT { 879 | __New(RECT := 0){ 880 | ; Initialize RECT 881 | if (RECT = 0){ 882 | RECT := {Top: 0, Bottom: 0, Left: 0, Right: 0} 883 | } 884 | ; Create Structure 885 | this.RECT := new _Struct(WinStructs.RECT, RECT) 886 | } 887 | 888 | __Get(aParam := ""){ 889 | static keys := {Top: 1, Left: 1, Bottom: 1, Right: 1} 890 | if (aParam = ""){ 891 | ; Blank param passed via [] or [""] - pass back RECT Structure 892 | return this.RECT[""] 893 | } 894 | if (ObjHasKey(keys, aParam)){ 895 | ; Top / Left / Bottom / Right property requested - return property from Structure 896 | return this.RECT[aParam] 897 | } 898 | } 899 | 900 | __Set(aParam := "", aValue := ""){ 901 | static keys := {Top: 1, Left: 1, Bottom: 1, Right: 1} 902 | 903 | if (aParam = ""){ 904 | ; Blank param passed via [""] - pass back RECT Structure 905 | return this.RECT[] := aValue 906 | } 907 | 908 | if (ObjHasKey(keys, aParam)){ 909 | ; Top / Left / Bottom / Right property specified - Set property of Structure 910 | this.RECT[aParam] := aValue 911 | } 912 | } 913 | ; Syntax Sugar 914 | 915 | ; Does this RECT contain the passed rect ? 916 | Contains(RECT){ 917 | ;tooltip % A_ThisFunc "`n" this.RECT.Bottom ">=" RECT.Bottom " ?" 918 | return (this.RECT.Top <= RECT.Top && this.RECT.Left <= RECT.Left && this.RECT.Bottom >= RECT.Bottom && this.RECT.Right >= RECT.Right) 919 | } 920 | 921 | ; Is this RECT equal to the passed RECT? 922 | Equals(RECT){ 923 | return (this.RECT.Top = RECT.Top && this.RECT.Left = RECT.Left && this.RECT.Bottom = RECT.Bottom && this.RECT.Right = RECT.Right) 924 | } 925 | 926 | ; Expands the current RECT to include the new RECT 927 | ; Returns TRUE if it the RECT grew. 928 | Union(RECT){ 929 | Expanded := 0 930 | if (RECT.Top < this.RECT.Top){ 931 | this.RECT.Top := RECT.Top 932 | Expanded := 1 933 | } 934 | if (RECT.Left < this.RECT.Left){ 935 | this.RECT.Left := RECT.Left 936 | Expanded := 1 937 | } 938 | if (RECT.Right > this.RECT.Right){ 939 | this.RECT.Right := RECT.Right 940 | Expanded := 1 941 | } 942 | if (RECT.Bottom > this.RECT.Bottom){ 943 | this.RECT.Bottom := RECT.Bottom 944 | Expanded := 1 945 | } 946 | return Expanded 947 | } 948 | } 949 | 950 | ; ========================================== POSITION ========================================= 951 | 952 | ; Sets the Window RECT. 953 | _GuiSetWindowRECT(wParam := 0, lParam := 0, msg := 0, hwnd := 0){ 954 | Static SB_HORZ := 0, SB_VERT := 1 955 | if (this._type = "w"){ 956 | ; WinGetPos is relative to the SCREEN 957 | frame := DllCall("GetParent", "Uint", this._hwnd) 958 | ;MsgBox % "frame - " this.FormatHex(frame) ", parent - " this._parent._hwnd 959 | WinGetPos, PosX, PosY, Width, Height, % "ahk_id " this._hwnd 960 | ;WinGetPos, PosX, PosY, Width, Height, % "ahk_id " frame 961 | if (this._parent = 0){ 962 | Bottom := PosY + height 963 | Right := PosX + Width 964 | } else { 965 | ; The x and y coords do not change when the window scrolls? 966 | ; Base code off these instead? 967 | ; x/y is coord of child RANGE relative to window RANGE. 968 | ;x := lParam & 0xffff 969 | ;y := lParam >> 16 970 | /* 971 | ; Method flawed - wraps round to 65535 when you move off the top/left edge. 972 | ; Adjust to compensate for child border size 973 | if (lParam = 0){ 974 | ; called without params (ie not from a message) - work out x and y coord 975 | POINT := new _Struct(WinStructs.POINT) 976 | POINT.x := 0 977 | POINT.y := 0 978 | ; Find 0,0 of Child Page relative to Parent's Range 979 | this._DLL_MapWindowPoints(this._hwnd, this._parent._hwnd, POINT, 1) 980 | } else { 981 | POINT := {x: lParam & 0xffff, y: lParam >> 16} 982 | } 983 | ;POINT := this.ConvertCoords(POINT, this._hwnd) 984 | */ 985 | 986 | RECT := new _Struct(WinStructs.RECT) 987 | DllCall("GetWindowRect", "uint", this._hwnd, "Ptr", RECT[]) 988 | POINT := this._DLL_ScreenToClient(this._parent._hwnd, RECT.Left, RECT.Top) 989 | ScrollInfo := this._parent._DLL_GetScrollInfo(SB_VERT) 990 | x_offset := this._parent._ScrollInfos[SB_HORZ].nPos 991 | y_offset := this._parent._ScrollInfos[SB_VERT].nPos 992 | PosX := POINT.x + x_offset 993 | PosY := POINT.y + y_offset 994 | 995 | ; Offset for scrollbar position 996 | x_offset := this._parent._ScrollInfos[SB_HORZ].nPos 997 | y_offset := this._parent._ScrollInfos[SB_VERT].nPos 998 | 999 | PosX := POINT.x + x_offset 1000 | PosY := POINT.y + y_offset 1001 | Right := (PosX + Width) 1002 | Bottom := (PosY + height) 1003 | ;ToolTip % "Pos: " PosX "," PosY 1004 | ;ToolTip % this.name "`n" this._SerializeRECT(this._WindowRECT) " - " y_offset 1005 | } 1006 | } else { 1007 | GuiControlGet, Pos, % this._parent._hwnd ":Pos", % this._hwnd 1008 | Right := PosX + PosW 1009 | Bottom := PosY + PosH 1010 | } 1011 | 1012 | this._WindowRECT.Left := PosX 1013 | this._WindowRECT.Top := PosY 1014 | this._WindowRECT.Right := Right 1015 | this._WindowRECT.Bottom := Bottom 1016 | ;ToolTip % this.name "`n" this._SerializeRECT(this._WindowRECT) " - " y_offset 1017 | ;this._TestRECT.Left := PosX 1018 | ;this._TestRECT.Top := PosY 1019 | ;this._TestRECT.Right := Right 1020 | ;this._TestRECT.Bottom := Bottom 1021 | ;ToolTip % A_ThisFunc "`n" this._SerializeRECT(this._TestRECT) " - " y_offset 1022 | 1023 | } 1024 | 1025 | ; Returns a RECT indicating the amount the window moved 1026 | _GuiGetMoveAmount(old, new){ 1027 | ;ToolTip % "O: " this._SerializeRECT(old) "`nN: " this._SerializeRECT(new) 1028 | moved := new this.RECT() 1029 | if ( (new.Right - new.Left) = (old.Right - old.Left) && (new.Bottom - new.Top) = (old.Bottom - old.Top) ){ 1030 | ; Moved 1031 | moved.Left := (new.Left - old.Left) * -1 ; invert so positive means "we moved left" 1032 | moved.Top := (new.Top - old.Top) * -1 1033 | moved.Right := new.Right - old.Right 1034 | moved.Bottom := new.Bottom - old.Bottom 1035 | } else { 1036 | ; resized 1037 | /* 1038 | moved.Left := (new.Left - old.Left) 1039 | moved.Top := (new.Top - old.Top) 1040 | moved.Right := (new.Right - old.Right) * -1 1041 | moved.Bottom := (new.Bottom - old.Bottom) * -1 1042 | */ 1043 | ; Swap values, as code calling this is based around Move rather than size, and checks opposite edges. 1044 | moved.Right := (new.Left - old.Left) 1045 | moved.Bottom := (new.Top - old.Top) 1046 | moved.Left := (new.Right - old.Right) * -1 1047 | moved.Top := (new.Bottom - old.Bottom) * -1 1048 | } 1049 | return moved 1050 | } 1051 | 1052 | ; ========================================== HELPER =========================================== 1053 | 1054 | ; Shorthand way of formatting something as 0x0 format Hex 1055 | FormatHex(val){ 1056 | return Format("{:#x}", val+0) 1057 | } 1058 | 1059 | ; Human readable hwnd, or padded number if not set 1060 | _FormatHwnd(hwnd := -1){ 1061 | if (hwnd = -1){ 1062 | hwnd := this._hwnd 1063 | } 1064 | if (!hwnd){ 1065 | return 0x000000 1066 | } else { 1067 | return hwnd 1068 | } 1069 | } 1070 | 1071 | ; Formats a String to a given length. 1072 | _SetStrLen(func, max := 25){ 1073 | if (StrLen(func) > max){ 1074 | func := Substr(func, 1, max) 1075 | } 1076 | return Format("{:-" max "s}",func) 1077 | } 1078 | 1079 | } -------------------------------------------------------------------------------- /CGui v2.ahk: -------------------------------------------------------------------------------- 1 | ; REQUIRES AHK v2 2 | ; DEPENDENCIES: 3 | ; _Struct(): https://raw.githubusercontent.com/HotKeyIt/_Struct/master/_Struct.ahk - docs: http://www.autohotkey.net/~HotKeyIt/AutoHotkey/_Struct.htm 4 | ; sizeof(): https://raw.githubusercontent.com/HotKeyIt/_Struct/master/sizeof.ahk - docs: http://www.autohotkey.net/~HotKeyIt/AutoHotkey/sizeof.htm 5 | ; WinStructs: https://github.com/ahkscript/WinStructs 6 | #SingleInstance force 7 | 8 | #include <_Struct> 9 | #include 10 | #include sample inihandler.ahk 11 | 12 | mc := new MyClass(0, "+Resize") 13 | 14 | ; Example class using CGui 15 | class MyClass extends CGui { 16 | __New(aParams*){ 17 | global BorderState 18 | 19 | base.__New(aParams*) 20 | this.Show("w300 h300 y0", "CGui Demo - " this._hwnd) 21 | 22 | this.FocusTest := new this._FocusTest(this, "+Border +Resize +Parent" this._hwnd) 23 | this.FocusTest.Show("w150 h150 x150 y150") 24 | 25 | this.VGTest := new this._VGTest(this, "-Border +Parent" this._hwnd) 26 | this.VGTest.Show("w170 h110 x0 y0", "") 27 | 28 | } 29 | 30 | class _VGTest extends CGui { 31 | __New(aParams*){ 32 | base.__New(aParams*) 33 | 34 | this.MyEdit1 := this.Gui("Add", "Edit", "w150") 35 | this.GuiControl("+g", this.MyEdit1, this.EditChanged) 36 | 37 | this.MyButton := this.Gui("Add", "Button", "w150", "v Copy v") 38 | this.GuiControl("+g", this.MyButton, this.ButtonPressed) 39 | 40 | this.Gui("Add", "Text", "w150 Center", "This box is Persistent") 41 | 42 | this.MyEdit2 := this.Gui("Add", "Edit", "w150") 43 | this.MyEdit2.MakePersistent("MyEdit2") 44 | } 45 | 46 | ButtonPressed(){ 47 | this.MyEdit2.value := this.MyEdit1.value 48 | } 49 | 50 | EditChanged(){ 51 | this.ToolTip(this.MyEdit1.value) 52 | } 53 | } 54 | 55 | class _FocusTest extends CGui { 56 | __New(aParams*){ 57 | base.__New(aParams*) 58 | 59 | Loop 8 { 60 | this.Gui("Add", "Edit", "w300", "Item " A_Index) 61 | } 62 | 63 | } 64 | } 65 | 66 | } 67 | 68 | Esc:: 69 | GuiClose: 70 | ExitApp 71 | 72 | ; Wraps All Gui commands - Guis and GuiControls 73 | class _CGui extends _CGuiBase { 74 | _type := "w" ; Window Type 75 | 76 | _ChildGuis := {} 77 | _ChildControls := {} 78 | 79 | ; ScrollInfo array - Declared as associative, but consider 0-based indexed. 0-based so SB_HORZ / SB_VERT map to correct elements. 80 | _ScrollInfos := {0: [], 1: []} 81 | 82 | ; ========================================== GUI COMMAND WRAPPERS ============================= 83 | ; Equivalent to Gui, New 84 | __New(parent := 0, options := 0, aParams*){ 85 | Static SB_HORZ := 0, SB_VERT := 1 86 | static WM_MOVE := 0x0003, WM_SIZE := 0x0005 87 | static WM_HSCROLL := 0x0114, WM_VSCROLL := 0x0115 88 | Static WM_MOUSEWHEEL := 0x020A, WM_MOUSEHWHEEL := 0x020E 89 | ;static WM_CLOSE := 0x0010 90 | 91 | this._parent := parent 92 | 93 | Gui, new, % "hwndhwnd " options 94 | this._hwnd := hwnd 95 | 96 | if (this._parent != 0){ 97 | this._parent._ChildGuis[this._hwnd] := this 98 | } 99 | this._MessageHandlerFunc := this._MessageHandler.Bind(this) 100 | 101 | ; Initialize page and range classes so that all values read 0 102 | this._RangeRECT := new this.RECT() 103 | this._PageRECT := new this.RECT() 104 | this._WindowRECT := new this.RECT() 105 | ;this._TestRECT := new this.RECT() 106 | 107 | ; Initialize scroll info array 108 | this._ScrollInfos := {0: this._DLL_GetScrollInfo(SB_HORZ), 1: this._DLL_GetScrollInfo(SB_VERT)} 109 | 110 | ;MsgBox % this._DLL_GetScrollInfo(SB_HORZ).Size() 111 | ; Register for ReSize messages 112 | this._RegisterMessage(WM_SIZE,this._OnSize) 113 | 114 | ; Register for scroll (drag of thumb) messages 115 | this._RegisterMessage(WM_HSCROLL,this._OnScroll) 116 | this._RegisterMessage(WM_VSCROLL,this._OnScroll) 117 | 118 | ; Register for move message. 119 | this._RegisterMessage(WM_MOVE,this._OnMove) 120 | 121 | ; Mouse Wheel 122 | this._RegisterMessage(WM_MOUSEWHEEL,this._OnWheel) 123 | this._RegisterMessage(WM_MOUSEHWHEEL,this._OnWheel) 124 | 125 | ; Close Gui - need method 126 | ;this._RegisterMessage(WM_CLOSE, this._OnExit) 127 | 128 | } 129 | 130 | __Delete(){ 131 | ;MsgBox % "GUI DELETE - " this._hwnd 132 | Gui, % this._hwnd ":Destroy" 133 | ;this._parent._GuiChildChangedRange() 134 | ; If top touches range top, left touches page left, right touches page right, or bottom touches page bottom... 135 | ; Removing this GuiControl should trigger a RANGE CHANGE. 136 | ; Same for Gui, Hide? 137 | } 138 | 139 | Destroy(){ 140 | this._ChildControls := "" 141 | 142 | for msg, value in _CGui 143 | if InStr(msg,"`r")=1 144 | value.Remove(this._hwnd) 145 | 146 | ;MsgBox % this._hwnd 147 | this._parent._ChildGuis.Remove(this._hwnd, "") 148 | this._parent._GuiChildChangedRange() 149 | ; Remove all child objects that could stop __Delete firing. 150 | for key in this { 151 | if (this[key]._parent = this){ 152 | this[key]._parent := "" 153 | } 154 | } 155 | } 156 | 157 | ; Simple patch to prefix Gui commands with HWND 158 | PrefixHwnd(cmd){ 159 | return this._hwnd ":" cmd 160 | } 161 | 162 | ; Equivalent to Gui, Show 163 | Show(options := "", title := ""){ 164 | Gui, % this.PrefixHwnd("Show"), % options, % title 165 | ;this._GuiSetWindowRECT() 166 | if (this._parent != 0){ 167 | ; Parent GUIs get a WM_MOVE message at start - emulate for children. 168 | this._OnMove() 169 | } 170 | } 171 | 172 | ; Wrapper for Gui commands 173 | Gui(cmd, aParams*){ 174 | if (cmd = "add"){ 175 | ; Create GuiControl 176 | obj := new this._CGuiControl(this, aParams*) 177 | return obj 178 | } else if (cmd = "new"){ 179 | obj := new _CGui(this, aParams*) 180 | return obj 181 | } 182 | } 183 | 184 | ; Wraps GuiControl to use hwnds and function binding etc 185 | GuiControl(cmd := "", ctrl := "", Param3 := ""){ 186 | m := SubStr(cmd,1,1) 187 | if (m = "+" || m = "-"){ 188 | ; Options 189 | o := SubStr(cmd,2,1) 190 | if (o = "g"){ 191 | ; Bind g-label to _glabel property 192 | fn := Param3.Bind(this) 193 | ctrl._glabel := fn 194 | return this 195 | } 196 | } else { 197 | GuiControl, % this._hwnd ":" cmd, % ctrl._hwnd, % Param3 198 | return this 199 | } 200 | } 201 | 202 | ; Wraps GuiControlGet 203 | GuiControlGet(cmd := "", ctrl := "", param4 := ""){ 204 | GuiControlGet, ret, % this._hwnd ":" cmd, % ctrl._hwnd, % Param4 205 | return ret 206 | } 207 | ; ========================================== DIMENSIONS ======================================= 208 | 209 | /* 210 | ; The PAGE (Size of window) of a Gui / GuiControl changed. For GuiControls, this is the size of the control 211 | _GuiPageGetRect(){ 212 | RECT := this._DLL_GetClientRect() 213 | return RECT 214 | } 215 | */ 216 | 217 | ; A Child of this window changed it's usage of this window's RANGE (in other words, it moved or changed size) 218 | ; old = the Child's old WindowRECT 219 | _GuiChildChangedRange(Child := 0, old := 0){ 220 | static opposites := {top: "bottom", left: "right", bottom: "top", right: "left"} 221 | shrank := 0 222 | 223 | if (child = 0){ 224 | ; Destroy 225 | Count := 0 226 | for childHwnd in this._ChildGuis { 227 | if (!Count){ 228 | this._RangeRECT := new this.RECT() 229 | this._RangeRECT.Union(this._ChildGuis[childHwnd]._WindowRECT) 230 | Count++ 231 | continue 232 | } 233 | this._RangeRECT.Union(this._ChildGuis[childHwnd]._WindowRECT) 234 | } 235 | this._GuiSetScrollbarSize() 236 | return 237 | } 238 | if (!this._RangeRECT.contains(Child._WindowRECT)){ 239 | ;if (this._RangeRECT.Union(Child._WindowRECT)){ 240 | this._RangeRECT.Union(Child._WindowRECT) 241 | this._GuiSetScrollbarSize() 242 | } else { 243 | ; Detect if child touching edge of range. 244 | ; set the new _WindowRECT and find out how much we moved, and in what direction. 245 | moved := this._GuiGetMoveAmount(old, Child._WindowRECT) 246 | for dir in opposites { 247 | ;if (dir = "down"){ 248 | ;ToolTip % "moved " this._SerializeRECT(moved) 249 | ;} 250 | if (moved[dir] > 0){ 251 | ;shrank := 1 252 | opp := opposites[dir] 253 | ;ToolTip % this.name "-" dir "`nOld: " old[opp] " = " this._RangeRECT[opp] " ?" 254 | if (old[opp] = this._RangeRECT[opp]){ 255 | shrank := 1 256 | break 257 | } 258 | } 259 | } 260 | if (shrank){ 261 | ; The child was touching an edge of our RANGE, and moved away from it ... 262 | ; ... Union WindowRECTs of all *other* children to see if this child is the only one needing that part of the range ... 263 | ; ... And if so, shrink our Range. 264 | 265 | Count := 0 266 | for childHwnd in this._ChildGuis { 267 | if (childHwnd = child._hwnd){ 268 | ;MsgBox % "Skipping " this._ChildGuis[childHwnd].name 269 | continue 270 | } 271 | if (!Count){ 272 | this._RangeRECT := new this.RECT() 273 | this._RangeRECT.Union(this._ChildGuis[childHwnd]._WindowRECT) 274 | ;MsgBox % "including " this._ChildGuis[childHwnd].name 275 | Count++ 276 | continue 277 | } 278 | ;MsgBox % "including " this._ChildGuis[childHwnd].name 279 | this._RangeRECT.Union(this._ChildGuis[childHwnd]._WindowRECT) 280 | Count++ 281 | } 282 | ;if (!this._RangeRECT.contains(old[childHwnd]._WindowRECT)){ 283 | if (!this._RangeRECT.contains(Child._WindowRECT)){ 284 | this._RangeRECT.Union(Child._WindowRECT) 285 | this._GuiSetScrollbarSize() 286 | } 287 | } 288 | /* 289 | if (shrank){ 290 | ToolTip % this.name " shrank`n" this._SerializeRECT(this._RangeRECT) "`nChild: " this._SerializeRECT(Child._WindowRECT) 291 | } else { 292 | ToolTip 293 | } 294 | */ 295 | } 296 | ;ToolTip % A_ThisFunc "`nOld: " this._SerializeRECT(oldrange) "`nNew: " this._SerializeRECT(this._RangeRECT) 297 | } 298 | 299 | /* 300 | ; Works out how big a window's caption / borders are and alters passed coords accordingly. 301 | ConvertCoords(coords,hwnd){ 302 | static WS_BORDER := 0x00800000, SM_CYCAPTION := 4 303 | VarSetCapacity(wi,60) 304 | DllCall("GetWindowInfo","PTR",hwnd,"PTR",&wi) 305 | ; Find size of frame (sizing handles - usually 3px) 306 | Frame := NumGet(&wi,48,"UInt") 307 | ; Does this window have a "caption" (Title) 308 | Caption := NumGet(&wi,36,"UInt") 309 | Caption := Caption & WS_BORDER 310 | if (Caption = WS_BORDER){ 311 | ; Yes - get size of caption 312 | TitleBar := DllCall("GetSystemMetrics","Int", SM_CYCAPTION) 313 | } else { 314 | ; No, window is -Border 315 | TitleBar := 0 316 | } 317 | ; Adjust coords 318 | coords := {x: coords.x, y: coords.y} 319 | ;ToolTip % coords.x 320 | coords.x -= Frame 321 | coords.y -= TitleBar + Frame 322 | return coords 323 | } 324 | */ 325 | 326 | ; ========================================== SCROLL BARS ====================================== 327 | 328 | ; Is the scrollbar at maximum? (ie all the way at the end). 329 | _IsScrollBarAtMaximum(bar){ 330 | end := this._ScrollInfos[bar].nPos + this._ScrollInfos[bar].nPage 331 | diff := ( this._ScrollInfos[bar].nMax - end ) * -1 332 | if (diff > 0){ 333 | return diff 334 | } else { 335 | return 0 336 | } 337 | } 338 | 339 | ; Set the POSITION component of a scrollbar 340 | _GuiSetScrollbarPos(nTrackPos, bar){ 341 | Static SB_HORZ := 0, SB_VERT := 1 342 | static SIF_POS := 0x4 343 | 344 | this._ScrollInfos[bar].fMask := SIF_POS 345 | this._ScrollInfos[bar].nPos := nTrackPos 346 | this._DLL_SetScrollInfo(bar, this._ScrollInfos[bar]) 347 | } 348 | 349 | ; Set the SIZE component(s) of a scrollbar - PAGE and RANGE 350 | ; bars = 0 = SB_HORZ 351 | ; bars = 1 = SB_VERT 352 | ; bars = 2 (or omit bars) = both bars 353 | _GuiSetScrollbarSize(bars := 2, PageRECT := 0, RangeRECT := 0, mode := "b"){ 354 | Static SB_HORZ := 0, SB_VERT := 1 355 | static SIF_DISABLENOSCROLL := 0x8 356 | static SIF_RANGE := 0x1, SIF_PAGE := 0x2, SIF_POS := 0x4, SIF_ALL := 0x17 357 | ; Index Min / Max property names of a RECT by SB_HORZ = 0, SB_VERT = 1 358 | static RECTProperties := { 0: {min: "Left", max: "Right"}, 1: {min: "Top", max: "Bottom"} } 359 | 360 | ; Determine what part of the scrollbars we wish to set. 361 | if (mode = "p"){ 362 | ; Set PAGE 363 | mask := SIF_PAGE 364 | } else if (mode = "r"){ 365 | ; Set RANGE 366 | mask := SIF_RANGE 367 | } else { 368 | ; Default to SET PAGE + RANGE 369 | mask := SIF_PAGE | SIF_RANGE 370 | } 371 | ;mask |= SIF_DISABLENOSCROLL ; If the scroll bar's new parameters make the scroll bar unnecessary, disable the scroll bar instead of removing it 372 | ;mask := SIF_ALL 373 | 374 | ; If no RECTs passed, use class properties 375 | if (PageRECT = 0){ 376 | PageRECT := this._PageRECT 377 | } 378 | if (RangeRECT = 0){ 379 | RangeRECT := this._RangeRECT 380 | } 381 | 382 | ; Alter scroll bars due to client size 383 | Loop 2 { 384 | bar := A_Index - 1 ; SB_HORZ = 0, SB_VERT = 1 385 | if ( ( bar=0 && (bars = 0 || bars = 2) ) || ( bar=1 && bars > 0 ) ){ 386 | ; If this scroll bar was specified ... 387 | ; ... Adjust this window's ScrollBar Struct as appropriate, ... 388 | this._ScrollInfos[bar].fMask := mask 389 | if (mask & SIF_RANGE){ 390 | ; Range bits set 391 | this._ScrollInfos[bar].nMin := RangeRECT[RECTProperties[bar].min] 392 | this._ScrollInfos[bar].nMax := RangeRECT[RECTProperties[bar].max] 393 | } 394 | 395 | if (mask & SIF_PAGE){ 396 | ; Page bits set 397 | this._ScrollInfos[bar].nPage := PageRECT[RECTProperties[bar].max] 398 | } 399 | ; ... Then update the Scrollbar. 400 | this._DLL_SetScrollInfo(bar, this._ScrollInfos[bar]) 401 | } 402 | 403 | ; If a vertical scrollbar is showing, and you are scrolled all the way to the bottom of the page... 404 | ; ... If you grab the bottom edge of the window and size up, the contents must scroll downwards. 405 | ; I call this a Size-Scroll. 406 | if (this._ScrollInfos[bar].nPage <= this._ScrollInfos[bar].nMax){ 407 | ; Page (Size of window) is less than Max (Size of contents) - scrollbars will be showing. 408 | diff := this._IsScrollBarAtMaximum(bar) 409 | 410 | if (diff > 0){ 411 | ; diff is positive, Size-Scroll required 412 | ; Set up vars for call 413 | if (bar) { 414 | h := 0 415 | v := diff 416 | } else { 417 | h := diff 418 | v := 0 419 | } 420 | ; Size-Scroll the contents. 421 | this._DLL_ScrollWindows(h,v) 422 | if (bar){ 423 | this._ScrollInfos[bar].nPos -= v 424 | } else { 425 | this._ScrollInfos[bar].nPos -= h 426 | } 427 | } 428 | } 429 | } 430 | } 431 | 432 | ; Sets cbSize, returns blank scrollinfo 433 | _BlankScrollInfo(){ 434 | return new _Struct(WinStructs.SCROLLINFO,{cbSize:sizeof(WinStructs.SCROLLINFO)}) 435 | } 436 | 437 | ; ========================================== DLL CALLS ======================================== 438 | 439 | ; ACCEPTS x, y 440 | ; Returns a POINT 441 | _DLL_ScreenToClient(hwnd, x, y){ 442 | ; https://msdn.microsoft.com/en-gb/library/windows/desktop/dd162952(v=vs.85).aspx 443 | lpPoint := new _Struct(WinStructs.POINT, {x: x, y: y}) 444 | r := DllCall("User32.dll\ScreenToClient", "Ptr", hwnd, "Ptr", lpPoint[], "Uint") 445 | return lpPoint 446 | } 447 | 448 | _DLL_MapWindowPoints(hwndFrom, hwndTo, ByRef lpPoints, cPoints := 2){ 449 | ; https://msdn.microsoft.com/en-gb/library/windows/desktop/dd145046(v=vs.85).aspx 450 | ;lpPoints := new _Struct(WinStructs.RECT) 451 | r := DllCall("User32.dll\MapWindowPoints", "Ptr", hwndFrom, "Ptr", hwndTo, "Ptr", lpPoints[], "Uint", cPoints, "Uint") 452 | return lpPoints 453 | } 454 | 455 | ; Wraps ScrollWindow() DLL Call. 456 | _DLL_ScrollWindows(XAmount, YAmount, hwnd := 0){ 457 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb787591%28v=vs.85%29.aspx 458 | if (!hwnd){ 459 | hwnd := this._hwnd 460 | } 461 | ;tooltip % "Scrolling " hwnd 462 | return DllCall("User32.dll\ScrollWindow", "Ptr", hwnd, "Int", XAmount, "Int", YAmount, "Ptr", 0, "Ptr", 0) 463 | } 464 | 465 | ; Wraps ScrollWindow() DLL Call. 466 | _DLL_ScrollWindow(bar, Amount, hwnd := 0){ 467 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb787591%28v=vs.85%29.aspx 468 | if (!hwnd){ 469 | hwnd := this._hwnd 470 | } 471 | if (bar){ 472 | XAmount := 0 473 | YAmount := Amount 474 | } else { 475 | XAmount := Amount 476 | YAmount := 0 477 | } 478 | ;tooltip % "Scrolling " hwnd 479 | return DllCall("User32.dll\ScrollWindow", "Ptr", hwnd, "Int", XAmount, "Int", YAmount, "Ptr", 0, "Ptr", 0) 480 | } 481 | 482 | /* 483 | ; Wraps GetClientRect() Dll call, returns RECT class (Not Structure! Class!) 484 | _DLL_GetClientRect(hwnd := 0){ 485 | if (hwnd = 0){ 486 | hwnd := this._hwnd 487 | } 488 | RECT := new this.RECT() 489 | DllCall("User32.dll\GetClientRect", "Ptr", hwnd, "Ptr", RECT[]) 490 | return RECT 491 | } 492 | */ 493 | 494 | ; Wraps SetScrollInfo() Dll call. 495 | ; Returns Dll Call return value 496 | _DLL_SetScrollInfo(fnBar, ByRef lpsi, fRedraw := 1, hwnd := 0){ 497 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb787595%28v=vs.85%29.aspx 498 | if (hwnd = 0){ 499 | ; Normal use - operate on youurself. Passed hwnd = inspect another window 500 | hwnd := this._hwnd 501 | } 502 | return DllCall("User32.dll\SetScrollInfo", "Ptr", hwnd, "Int", fnBar, "Ptr", lpsi[], "UInt", fRedraw, "UInt") 503 | } 504 | 505 | ;_DLL_GetScrollInfo(fnBar, ByRef lpsi, hwnd := 0){ 506 | ; returns a SCROLLINFO structure 507 | _DLL_GetScrollInfo(fnBar, hwnd := 0){ 508 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/bb787583%28v=vs.85%29.aspx 509 | static SIF_ALL := 0x17 510 | if (hwnd = 0){ 511 | ; Normal use - operate on youurself. Passed hwnd = inspect another window 512 | hwnd := this._hwnd 513 | } 514 | lpsi := this._BlankScrollInfo() 515 | lpsi.fMask := SIF_ALL 516 | r := DllCall("User32.dll\GetScrollInfo", "Ptr", hwnd, "Int", fnBar, "Ptr", lpsi[], "UInt") 517 | return lpsi 518 | ;Return r 519 | } 520 | 521 | _DLL_GetParent(hwnd := 0){ 522 | if (hwnd = 0){ 523 | hwnd := this._hwnd 524 | } 525 | return DllCall("GetParent", "Uint", hwnd, "Uint") 526 | } 527 | 528 | ; ========================================== MESSAGES ========================================= 529 | 530 | ; All messages route through here. Only one message of each kind will be registered, to avoid noise and make debugging easier. 531 | _MessageHandler(wParam, lParam, msg, hwnd){ 532 | ; Call the callback associated with this Message and HWND 533 | ;(_CGui._MessageArray[msg][hwnd]).(wParam, lParam, msg, hwnd) 534 | if _CGui["`r" msg].HasKey(hwnd+0) 535 | %_CGui["`r" msg][hwnd+0]%(wParam, lParam, msg, hwnd) 536 | } 537 | 538 | ; Register a message with the Message handler. 539 | _RegisterMessage(msg, callback){ 540 | /* 541 | msg += 0 542 | newmessage := 0 543 | if (!ObjHasKey(_CGui, "_MessageArray")){ 544 | _Cgui._MessageArray := {} 545 | ;_Cgui._MessageArray := [] 546 | } 547 | if (!ObjHasKey(_CGui._MessageArray, msg)){ 548 | _CGui._MessageArray[msg] := {} 549 | ;_CGui._MessageArray[msg] := [] 550 | newmessage := 1 551 | } 552 | */ 553 | ; Add the callback to _MessageArray, so that _MessageHandler can look it up and route to it. 554 | ; Store Array on _CGui, so any class can call it's own .RegisterMessage property. 555 | fn := callback.Bind(this) 556 | 557 | _CGui["`r" msg,this._hwnd] := fn 558 | 559 | ; Only subscribe to message if this message has not already been subscribed to. 560 | ; if (newmessage){ 561 | ; fn := this._MessageHandler.Bind(this) 562 | OnMessage(msg, this._MessageHandlerFunc) 563 | ; } 564 | } 565 | 566 | ; A scrollbar was dragged 567 | _OnScroll(wParam, lParam, msg, hwnd){ 568 | ;SoundBeep 569 | ; Handles: 570 | ; WM_VSCROLL https://msdn.microsoft.com/en-gb/library/windows/desktop/bb787577(v=vs.85).aspx 571 | ; WM_HSCROLL https://msdn.microsoft.com/en-gb/library/windows/desktop/bb787575(v=vs.85).aspx 572 | Critical 573 | static WM_HSCROLL := 0x0114, WM_VSCROLL := 0x0115 574 | Static SB_HORZ := 0, SB_VERT := 1 575 | static SB_LINEUP := 0x0, SB_LINEDOWN := 0x1, SB_PAGEUP := 0x2, SB_PAGEDOWN := 0x3, SB_THUMBPOSITION := 0x4, SB_THUMBTRACK := 0x5, SB_TOP := 0x6, SB_BOTTOM := 0x7, SB_ENDSCROLL := 0x8 576 | 577 | if (msg = WM_HSCROLL || msg = WM_VSCROLL){ 578 | bar := msg - 0x114 579 | } else { 580 | SoundBeep 581 | return 582 | } 583 | ScrollInfo := this._DLL_GetScrollInfo(bar) 584 | ;OutputDebug, % "SI: " ScrollInfo.nTrackPos ", Bar: " bar 585 | 586 | if (wParam = SB_LINEUP || wParam = SB_LINEDOWN || wParam = SB_PAGEUP || wParam = SB_PAGEDOWN){ 587 | ; Line scrolling (Arrows at end of scrollbar clicked) or Page scrolling (Area between handle and arrows clicked) 588 | ; Is an unimplemented flag 589 | ; wParam is direction 590 | ; msg is horiz / vert 591 | if (wParam = SB_PAGEUP || wParam = SB_PAGEDOWN){ 592 | wParam -= 2 593 | line := ScrollInfo.nPage 594 | } else { 595 | line := 20 596 | } 597 | amt := 0 598 | max := ScrollInfo.nMax - ScrollInfo.nPage 599 | if (wParam){ 600 | ; down 601 | amt := ScrollInfo.nPos + line 602 | if (amt > max){ 603 | amt := max 604 | } 605 | } else { 606 | ; up 607 | amt := ScrollInfo.nPos - line 608 | if (amt < ScrollInfo.nMin){ 609 | amt := ScrollInfo.nMin 610 | } 611 | } 612 | newpos := amt 613 | amt := ScrollInfo.nPos - amt 614 | if (amt){ 615 | ; IMPORTANT! Update scrollbar pos BEFORE scrolling window. 616 | ; Scrolling window trips _OnMove for children, and scrollbar pos is used by them to determine coordinates. 617 | this._GuiSetScrollbarPos(newpos, bar) 618 | this._DLL_ScrollWindow(bar, amt) 619 | } 620 | /* 621 | } else if (wParam = SB_THUMBTRACK){ 622 | ; "The user is dragging the scroll box. This message is sent repeatedly until the user releases the mouse button" 623 | ; This is bundled in with the drags, as same code seems good. 624 | } else if (wParam = SB_THUMBPOSITION || wParam = SB_ENDSCROLL){ 625 | ; This is bundled in with the drags, as same code seems good. 626 | this._GuiSetScrollbarPos(ScrollInfo.nTrackPos, bar) 627 | } else if (wParam = SB_TOP || wParam = SB_BOTTOM) { 628 | ; "Scrolls to the upper left" / "Scrolls to the lower right" 629 | ; Not entirely sure what these are for, disable for now 630 | SoundBeep, 100, 100 631 | */ 632 | } else if (wParam = SB_ENDSCROLL){ 633 | ; Seem to not need to implement this. 634 | ; Routing it through to the main drag block breaks page scrolling (arrow buttons or wheel) 635 | } else { 636 | ; Drag of scrollbar 637 | ; Handles SB_THUMBTRACK, SB_THUMBPOSITION, SB_ENDSCROLL Flags (Indicated by wParam has set LOWORD, Highest value is 0x8 which is SB_ENDSCROLL) ... 638 | ; These Flags generally only get set once each per drag. 639 | ; ... Also handles drag of scrollbar (wParam has set HIWORD = "current position of the scroll box"), so wParam will be very big. 640 | ; This HIWORD "Flag" gets set lots of times per drag. 641 | 642 | ; Set the scrollbar pos BEFORE scrolling the window. 643 | ; Scrolling the window will cause WM_MOVE to trigger for children... 644 | ; ... And the position of the window needs to match the postion of the scrollbars at that point in time. 645 | pos := (ScrollInfo.nTrackPos - this._ScrollInfos[bar].nPos) * -1 646 | this._GuiSetScrollbarPos(ScrollInfo.nTrackPos, bar) 647 | 648 | if (bar){ 649 | ; Vertical Bar 650 | h := 0 651 | ;v := (ScrollInfo.nTrackPos - this._ScrollInfos[bar].nPos) * -1 652 | v := pos 653 | } else { 654 | ; Horiz Bar 655 | ;h := (ScrollInfo.nTrackPos - this._ScrollInfos[bar].nPos) * -1 656 | h := pos 657 | v := 0 658 | } 659 | ;OutputDebug, % "[ " this._FormatHwnd() " ] " this._SetStrLen(A_ThisFunc) " Scrolling window by (x,y) " h ", " v " - new Pos: " this._ScrollInfos[bar].nPos 660 | ;ToolTip % ScrollInfo.nPos "," ScrollInfo.nPage "," ScrollInfo.nMax 661 | this._DLL_ScrollWindows(h, v) 662 | } 663 | } 664 | 665 | ; Adjust this._PageRECT when Gui Size Changes (ie it was Resized) 666 | ; Handles WM_SIZE message 667 | ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms632646%28v=vs.85%29.aspx 668 | _OnSize(wParam, lParam, msg, hwnd){ 669 | ; ToDo: Need to check if hwnd matches this._hwnd ? 670 | static SIZE_RESTORED := 0, SIZE_MINIMIZED := 1, SIZE_MAXIMIZED := 2, SIZE_MAXSHOW := 3, SIZE_MAXHIDE := 4 671 | 672 | if (wParam = SIZE_RESTORED || wParam = SIZE_MAXIMIZED){ 673 | old := this._WindowRECT.clone() 674 | w := lParam & 0xffff 675 | h := lParam >> 16 676 | if (w != this._PageRECT.Right || h != this._PageRECT.Bottom){ 677 | ; Gui Size Changed - update PAGERECT 678 | this._PageRECT.Right := w 679 | this._PageRECT.Bottom := h 680 | } 681 | this._GuiSetWindowRECT() 682 | ; Adjust Scrollbars if required 683 | this._GuiSetScrollbarSize() 684 | ;this._parent._GuiSetScrollbarSize() 685 | if (this._parent != 0){ 686 | this._parent._GuiChildChangedRange(this, old) 687 | } 688 | } 689 | } 690 | 691 | ; Called when a GUI Moves. 692 | ; If the GUI moves outside it's parent's RECT, enlarge the parent's RANGE 693 | ; ToDo: Needs work? buggy? 694 | ; OnMove seems to be called for a window when you scroll a window containing it. 695 | _OnMove(wParam := 0, lParam := 0, msg := 0, hwnd := 0){ 696 | Critical 697 | old := this._WindowRECT.clone() 698 | this._GuiSetWindowRECT(wParam, lParam, msg, hwnd) 699 | ;ToolTip % A_ThisFunc "`nOld: " this._SerializeRECT(old) "`nNew: " this._SerializeRECT(this._WindowRECT) 700 | ;if (!this._WindowRECT.Equals(old)){ 701 | ;if (!this._parent._RangeRECT.contains(this._WindowRECT)){ 702 | if (this._parent != 0){ 703 | this._parent._GuiChildChangedRange(this, old) 704 | } 705 | ;} 706 | return 707 | } 708 | 709 | ; Handles mouse wheel messages 710 | _OnWheel(wParam, lParam, msg, hwnd){ 711 | Static MK_SHIFT := 0x0004 712 | Static SB_LINEMINUS := 0, SB_LINEPLUS := 1 713 | Static WM_MOUSEWHEEL := 0x020A, WM_MOUSEHWHEEL := 0x020E 714 | Static WM_HSCROLL := 0x0114, WM_VSCROLL := 0x0115 715 | Static SB_HORZ := 0, SB_VERT := 1 716 | Critical 717 | 718 | MSG := (Msg = WM_MOUSEWHEEL ? WM_VSCROLL : WM_HSCROLL) 719 | bar := msg - 0x114 720 | has_scrollbars := 0 721 | MouseGetPos,,,,hcurrent,2 722 | if (hcurrent != ""){ 723 | has_scrollbars := this._HasScrollbar(bar, hcurrent) 724 | } 725 | if (has_scrollbars = 0){ 726 | ; No Sub-item found under cursor, get which main parent gui is under the cursor. 727 | MouseGetPos,,,hcurrent 728 | has_scrollbars := this._HasScrollbar(bar, hcurrent) 729 | } 730 | ; Drill down through Hwnds until one is found with scrollbars showing. 731 | while (!has_scrollbars){ 732 | hcurrent := this._DLL_GetParent(hcurrent) 733 | has_scrollbars := this._HasScrollbar(bar, hcurrent) 734 | if (hcurrent = 0){ 735 | ; No parent found - end 736 | break 737 | } 738 | } 739 | ; No candidates found - route scroll to main window 740 | if (!has_scrollbars){ 741 | ;MsgBox % this.FormatHex(hwnd) 742 | hcurrent := hwnd 743 | return 744 | } 745 | 746 | if (!ObjHasKey(_CGui["`r" msg], hcurrent)){ 747 | return 748 | } 749 | 750 | SB := ((wParam >> 16) > 0x7FFF) || (wParam < 0) ? SB_LINEPLUS : SB_LINEMINUS 751 | 752 | ;(_CGui._MessageArray[msg][hcurrent]).(sb, 0, msg, hcurrent) 753 | %_CGui["`r" msg][hcurrent+0]%(sb, 0, msg, hcurrent) 754 | ;return 0 755 | } 756 | 757 | _OnExit(wParam, lParam, msg, hwnd){ 758 | ; Need to find close message 759 | MsgBox EXIT 760 | } 761 | 762 | 763 | ; ========================================== MISC ============================================= 764 | 765 | ; Converts a rect to a debugging string 766 | _SerializeRECT(RECT) { 767 | return "T: " RECT.Top "`tL: " RECT.Left "`tB: " RECT.Bottom "`tR: " RECT.Right 768 | } 769 | 770 | _HasScrollbar(bar, hwnd){ 771 | sb := this._DLL_GetScrollInfo(bar, hwnd) 772 | if (sb.nPage && (sb.nPage <= sb.nMax)){ 773 | return 1 774 | } else { 775 | return 0 776 | } 777 | } 778 | 779 | BoolToSgn(bool){ 780 | if (bool){ 781 | return "+" 782 | } else { 783 | return "-" 784 | } 785 | } 786 | 787 | 788 | ToolTip(Text, duration := 500){ 789 | fn := this.ToolTipTimer.bind(this) 790 | this._TooltipActive := fn 791 | SetTimer, % fn, % "-" duration 792 | ToolTip % Text 793 | } 794 | 795 | ToolTipTimer(){ 796 | ToolTip 797 | } 798 | 799 | ; ========================================== CLASSES ========================================== 800 | 801 | ; Wraps GuiControls into an Object 802 | class _CGuiControl extends _CGuiBase { 803 | _type := "c" ; Control Type 804 | _glabel := 0 805 | ; Equivalent to Gui, Add 806 | __New(parent, ctrltype, options := "", text := ""){ 807 | this._parent := parent 808 | this._ctrltype := ctrltype 809 | Gui, % this._parent.PrefixHwnd("Add"), % ctrltype, % "hwndhwnd " options, % text 810 | ;this._parent.Gui("add", ctrltype, "hwndhwnd " options 811 | this._hwnd := hwnd 812 | 813 | ; Hook into OnChange event 814 | fn := this._OnChange.bind(this) 815 | GuiControl % "+g", % this._hwnd, % fn 816 | 817 | ; Add self to parent's list of GuiControls 818 | this._parent._ChildControls[this._hwnd] := this 819 | 820 | ; Set up RECTs for scrollbars 821 | this._WindowRECT := new this.RECT() 822 | this._GuiSetWindowRECT() 823 | 824 | ; Tell parent to adjust scrollbars 825 | this._parent._GuiChildChangedRange(this, this._WindowRECT) 826 | 827 | } 828 | 829 | __Delete(){ 830 | ;MsgBox % "CONTROL DELETE - " this._hwnd 831 | ;Gui, % this._parent.PrefixHwnd("Add"), % ctrltype, % "hwndhwnd " options, % text 832 | DllCall("DestroyWindow", "UInt", this._hwnd) 833 | 834 | ; If top touches range top, left touches page left, right touches page right, or bottom touches page bottom... 835 | ; Removing this GuiControl should trigger a RANGE CHANGE. 836 | ; Same for Hiding a GuiControl? 837 | } 838 | 839 | __Get(aParam){ 840 | if (aParam = "value"){ 841 | return this._parent.GuiControlGet(,this) 842 | } 843 | } 844 | 845 | __Set(aParam, aValue){ 846 | if (aParam = "value"){ 847 | return this._parent.GuiControl(,this, aValue) 848 | } 849 | } 850 | 851 | ; Removes a GUIControl. 852 | ; Does NOT remove the last reference to the Control on the parent, call Destroy, then unset manually to fire __Delete() 853 | ; eg this.childcontrol.Destroy() 854 | ; this.childcontrol := "" 855 | Destroy(){ 856 | this._parent._ChildControls.Remove(this._hwnd, "") 857 | this._parent._GuiChildChangedRange() 858 | } 859 | 860 | _OnChange(){ 861 | ; Provide hook into change event 862 | this.OnChange() 863 | 864 | ; Call bound function if present 865 | if (ObjHasKey(this,"_glabel") && this._glabel != 0){ 866 | %this._glabel%() 867 | } 868 | } 869 | 870 | ; Override to hook into change event independently of g-labels 871 | OnChange(){ 872 | return 873 | } 874 | } 875 | 876 | } 877 | 878 | ; ================================================================================================= 879 | ; ================================================================================================= 880 | 881 | ; A base class, purely for inheriting. 882 | class _CGuiBase { 883 | ; ========================================== CLASSES ========================================== 884 | 885 | ; RECT class. Wraps _Struct to provide functionality similar to C 886 | ; https://msdn.microsoft.com/en-us/library/system.windows.rect(v=vs.110).aspx 887 | class RECT { 888 | __New(RECT := 0){ 889 | ; Initialize RECT 890 | if (RECT = 0){ 891 | RECT := {Top: 0, Bottom: 0, Left: 0, Right: 0} 892 | } 893 | ; Create Structure 894 | this.RECT := new _Struct(WinStructs.RECT, RECT) 895 | } 896 | 897 | __Get(aParam := ""){ 898 | static keys := {Top: 1, Left: 1, Bottom: 1, Right: 1} 899 | if (aParam = ""){ 900 | ; Blank param passed via [] or [""] - pass back RECT Structure 901 | return this.RECT[""] 902 | } 903 | if (ObjHasKey(keys, aParam)){ 904 | ; Top / Left / Bottom / Right property requested - return property from Structure 905 | return this.RECT[aParam] 906 | } 907 | } 908 | 909 | __Set(aParam := "", aValue := ""){ 910 | static keys := {Top: 1, Left: 1, Bottom: 1, Right: 1} 911 | 912 | if (aParam = ""){ 913 | ; Blank param passed via [""] - pass back RECT Structure 914 | return this.RECT[] := aValue 915 | } 916 | 917 | if (ObjHasKey(keys, aParam)){ 918 | ; Top / Left / Bottom / Right property specified - Set property of Structure 919 | this.RECT[aParam] := aValue 920 | } 921 | } 922 | ; Syntax Sugar 923 | 924 | ; Does this RECT contain the passed rect ? 925 | Contains(RECT){ 926 | ;tooltip % A_ThisFunc "`n" this.RECT.Bottom ">=" RECT.Bottom " ?" 927 | return (this.RECT.Top <= RECT.Top && this.RECT.Left <= RECT.Left && this.RECT.Bottom >= RECT.Bottom && this.RECT.Right >= RECT.Right) 928 | } 929 | 930 | ; Is this RECT equal to the passed RECT? 931 | Equals(RECT){ 932 | return (this.RECT.Top = RECT.Top && this.RECT.Left = RECT.Left && this.RECT.Bottom = RECT.Bottom && this.RECT.Right = RECT.Right) 933 | } 934 | 935 | ; Expands the current RECT to include the new RECT 936 | ; Returns TRUE if it the RECT grew. 937 | Union(RECT){ 938 | Expanded := 0 939 | if (RECT.Top < this.RECT.Top){ 940 | this.RECT.Top := RECT.Top 941 | Expanded := 1 942 | } 943 | if (RECT.Left < this.RECT.Left){ 944 | this.RECT.Left := RECT.Left 945 | Expanded := 1 946 | } 947 | if (RECT.Right > this.RECT.Right){ 948 | this.RECT.Right := RECT.Right 949 | Expanded := 1 950 | } 951 | if (RECT.Bottom > this.RECT.Bottom){ 952 | this.RECT.Bottom := RECT.Bottom 953 | Expanded := 1 954 | } 955 | return Expanded 956 | } 957 | } 958 | 959 | ; ========================================== POSITION ========================================= 960 | 961 | ; Sets the Window RECT. 962 | _GuiSetWindowRECT(wParam := 0, lParam := 0, msg := 0, hwnd := 0){ 963 | Static SB_HORZ := 0, SB_VERT := 1 964 | if (this._type = "w"){ 965 | ; WinGetPos is relative to the SCREEN 966 | frame := DllCall("GetParent", "Uint", this._hwnd) 967 | ;MsgBox % "frame - " this.FormatHex(frame) ", parent - " this._parent._hwnd 968 | WinGetPos, PosX, PosY, Width, Height, % "ahk_id " this._hwnd 969 | ;WinGetPos, PosX, PosY, Width, Height, % "ahk_id " frame 970 | if (this._parent = 0){ 971 | Bottom := PosY + height 972 | Right := PosX + Width 973 | } else { 974 | ; The x and y coords do not change when the window scrolls? 975 | ; Base code off these instead? 976 | ; x/y is coord of child RANGE relative to window RANGE. 977 | ;x := lParam & 0xffff 978 | ;y := lParam >> 16 979 | /* 980 | ; Method flawed - wraps round to 65535 when you move off the top/left edge. 981 | ; Adjust to compensate for child border size 982 | if (lParam = 0){ 983 | ; called without params (ie not from a message) - work out x and y coord 984 | POINT := new _Struct(WinStructs.POINT) 985 | POINT.x := 0 986 | POINT.y := 0 987 | ; Find 0,0 of Child Page relative to Parent's Range 988 | this._DLL_MapWindowPoints(this._hwnd, this._parent._hwnd, POINT, 1) 989 | } else { 990 | POINT := {x: lParam & 0xffff, y: lParam >> 16} 991 | } 992 | ;POINT := this.ConvertCoords(POINT, this._hwnd) 993 | */ 994 | 995 | RECT := new _Struct(WinStructs.RECT) 996 | DllCall("GetWindowRect", "uint", this._hwnd, "Ptr", RECT[]) 997 | POINT := this._DLL_ScreenToClient(this._parent._hwnd, RECT.Left, RECT.Top) 998 | ScrollInfo := this._parent._DLL_GetScrollInfo(SB_VERT) 999 | 1000 | x_offset := this._parent._ScrollInfos[SB_HORZ].nPos 1001 | y_offset := this._parent._ScrollInfos[SB_VERT].nPos 1002 | PosX := POINT.x + x_offset 1003 | PosY := POINT.y + y_offset 1004 | 1005 | ; Offset for scrollbar position 1006 | x_offset := this._parent._ScrollInfos[SB_HORZ].nPos 1007 | y_offset := this._parent._ScrollInfos[SB_VERT].nPos 1008 | 1009 | PosX := POINT.x + x_offset 1010 | PosY := POINT.y + y_offset 1011 | Right := (PosX + Width) 1012 | Bottom := (PosY + height) 1013 | ;ToolTip % "Pos: " PosX "," PosY 1014 | ;ToolTip % this.name "`n" this._SerializeRECT(this._WindowRECT) " - " y_offset 1015 | } 1016 | } else { 1017 | GuiControlGet, Pos, % this._parent._hwnd ":Pos", % this._hwnd 1018 | Right := PosX + PosW 1019 | Bottom := PosY + PosH 1020 | } 1021 | 1022 | this._WindowRECT.Left := PosX 1023 | this._WindowRECT.Top := PosY 1024 | this._WindowRECT.Right := Right 1025 | this._WindowRECT.Bottom := Bottom 1026 | ;ToolTip % this.name "`n" this._SerializeRECT(this._WindowRECT) " - " y_offset 1027 | ;this._TestRECT.Left := PosX 1028 | ;this._TestRECT.Top := PosY 1029 | ;this._TestRECT.Right := Right 1030 | ;this._TestRECT.Bottom := Bottom 1031 | ;ToolTip % A_ThisFunc "`n" this._SerializeRECT(this._TestRECT) " - " y_offset 1032 | 1033 | } 1034 | 1035 | ; Returns a RECT indicating the amount the window moved 1036 | _GuiGetMoveAmount(old, new){ 1037 | ;ToolTip % "O: " this._SerializeRECT(old) "`nN: " this._SerializeRECT(new) 1038 | moved := new this.RECT() 1039 | if ( (new.Right - new.Left) = (old.Right - old.Left) && (new.Bottom - new.Top) = (old.Bottom - old.Top) ){ 1040 | ; Moved 1041 | moved.Left := (new.Left - old.Left) * -1 ; invert so positive means "we moved left" 1042 | moved.Top := (new.Top - old.Top) * -1 1043 | moved.Right := new.Right - old.Right 1044 | moved.Bottom := new.Bottom - old.Bottom 1045 | } else { 1046 | ; resized 1047 | /* 1048 | moved.Left := (new.Left - old.Left) 1049 | moved.Top := (new.Top - old.Top) 1050 | moved.Right := (new.Right - old.Right) * -1 1051 | moved.Bottom := (new.Bottom - old.Bottom) * -1 1052 | */ 1053 | ; Swap values, as code calling this is based around Move rather than size, and checks opposite edges. 1054 | moved.Right := (new.Left - old.Left) 1055 | moved.Bottom := (new.Top - old.Top) 1056 | moved.Left := (new.Right - old.Right) * -1 1057 | moved.Top := (new.Bottom - old.Bottom) * -1 1058 | } 1059 | return moved 1060 | } 1061 | 1062 | ; ========================================== HELPER =========================================== 1063 | 1064 | ; Shorthand way of formatting something as 0x0 format Hex 1065 | FormatHex(val){ 1066 | return Format("{:#x}", val+0) 1067 | } 1068 | 1069 | ; Human readable hwnd, or padded number if not set 1070 | _FormatHwnd(hwnd := -1){ 1071 | if (hwnd = -1){ 1072 | hwnd := this._hwnd 1073 | } 1074 | if (!hwnd){ 1075 | return 0x000000 1076 | } else { 1077 | return hwnd 1078 | } 1079 | } 1080 | 1081 | ; Formats a String to a given length. 1082 | _SetStrLen(func, max := 25){ 1083 | if (StrLen(func) > max){ 1084 | func := Substr(func, 1, max) 1085 | } 1086 | return Format("{:-" max "s}",func) 1087 | } 1088 | 1089 | } --------------------------------------------------------------------------------