[Class] ControlMonitor - Monitor changes to controls

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
RazorHalo
Posts: 45
Joined: 21 Dec 2015, 21:23

[Class] ControlMonitor - Monitor changes to controls

18 Aug 2020, 23:04

I was looking for a way to always execute some code whenever a control is updated or changed by a user, but didn't want to have all my controlsset to the same gLable as I needed them to all have their own, and didn't want to keep track of always Gosub-ing around like crazy. I came up with this class that monitors most controls that use the WM_COMMAND when they are updated.

Controls Supported: EditBox, Radio, Checkbox, DropDownList, ComboBox, EditBox, ListBox, Push Button
Static Controls (Text and Picture) that have SS_NOTIFY Style set

See the example for usage.

Class_ControlMonitor.ahk

Code: Select all

; =======================================================================================================================
; Class: ControlMonitor
; 		Monitors controls for changes and triggers a Label or Function when modified.
;		Uses WM_COMMAND notifications instead of gLabels
;		Supported controls: EditBox, Radio, Checkbox, DropDownList, ComboBox, EditBox, ListBox, Push Button
;							Static Controls (Text and Picture) that have SS_NOTIFY Style set
;	 Tested on:		Win 10 (x64)
;		Author:		RazorHalo
;	Change log:		1.0 (August 18, 2020) - Initial release.
;		
; =======================================================================================================================
Class ControlMonitor {
	; ===================================================================================================================
	; Constructor / Destructor
	; ===================================================================================================================
	__New() {
		; Bind the Check() method to WM_COMMAND notifications
		this.fn := ObjBindMethod(this, "Check")
		OnMessage(0x111, this.fn)
		this.Ctrl := {}
		this.State := 1
	}
	
	; ===================================================================================================================
	; Destroy			Frees the bound method and unregisters it from OnMessage
	; ===================================================================================================================
	Destroy() {
		OnMessage(0x111, this.fn, 0)
		this.fn := ""
	}
	
	; ===================================================================================================================
	; On				Turns on monitoring of controls
	; ===================================================================================================================
	On() {
		this.State := 1
	}
	
	; ===================================================================================================================
	; Off				Turns off monitoring of controls
	; ===================================================================================================================
	Off() {
		this.State := 0
	}

	; ===================================================================================================================
	; Add				Adds controls to the Monitor, saving its current value
	;    Parameters:	hControl - passed as either a single control hWnd or an array of control hWnds
	;					Group (Optional) - Specify a string to identify a group of controls, these can then be reset
	;					together.   Required when adding radio controls that are part of the same button group
	; Return values:	None
	; ===================================================================================================================
	Add(hControl, Group := "") {
		If (IsObject(hControl)) {
			For Each, hWnd in hControl {
				this.Ctrl[hWnd] := {}
				GuiControlGet, Value,, % hWnd
				this.Ctrl[hWnd].Value := Value
				
				If (Group) {
					this.Ctrl[hWnd].Group := Group
				}
			}
		} Else {
			this.Ctrl[hControl] := {}
			GuiControlGet, Value,, % hControl
			this.Ctrl[hControl] := Value
			
			If (Group) {
				this.Ctrl[hControl].Group := Group
			}
		}
	}
	
	; ===================================================================================================================
	; Remove			Removes controls from the Monitor
	;    Parameters:	hControl - passed as either a single control hWnd or an array of control hWnds
	;					Group (Optional) - Removes all controls from the specified group
	; Return values:	None
	; ===================================================================================================================
	Remove(hControl, Group := "") {
		If (IsObject(hControl))
			For Each, hWnd in hControl {
				If (Group && this.Ctrl[hWnd].Group != Group)
					Continue
				Else
					this.Ctrl.Delete(hWnd)
			}
		this.Ctrl.Delete(hControl)
	}
	
	; ===================================================================================================================
	; Reset				Resets the controls in the monitor, saving their current values to be the new 'unchanged' value
	;    Parameters:	hControl - passed as either a single control hWnd or an array of control hWnds
	;								** OMIT to reset all controls in the monitor
	;					Group (Optional) - Resets all controls in the specified group
	; Return values:	None
	; ===================================================================================================================
	Reset(hControl := "", Group := "") {	
		If !(hControl) {
			For hWnd in this.Ctrl {
				If (Group && this.Ctrl[hWnd].Group != Group)
					Continue
	
				GuiControlGet, Value,, % hWnd
				this.Ctrl[hWnd].Value := Value
			}
		} Else If (IsObject(hControl)) {
			For Each, hWnd in hControl {
				GuiControlGet, Value,, % hWnd
				this.Ctrl[hWnd].Value := Value
			}
		} Else {
			GuiControlGet, Value,, % hControl
			this.Ctrl[hControl].Value := Value
		}
	}
	
	; ===================================================================================================================
	; Target			Sets the target label or function to execute when monitored control is modified
	;    Parameters:	Target - Name of a label or function
	; Return values:	On success  - False
	;					On failure  - True
	;		Remarks:	If the target is a function it will pass the controls hWnd as the first parameter
	; ===================================================================================================================
	Target(Target) {
		If IsLabel(Target) {
			this.Target := Target
			Return False
		} Else If IsFunc(Target) {
			this.Target := Func(Target)
			Return False
		}
		
		MsgBox, 16, Error, % Target " is not a valid label or function"
		Return True
	}
	
	; ===================================================================================================================
	; Check				Checks if the value of a control has changed
	;    Parameters:	
	; Return values:	On success  - False
	;					On failure  - True
	;		Remarks:	Called by function bound to WM_COMMAND OnMessage
	; ===================================================================================================================
	Check(wParam, hCtrl, msg, hWnd) {
		Modified := 0
		
		NC := (wParam >> 16) ; Get notification code
		
		If (this.State && this.Ctrl.HasKey(hCtrl)
						&& (NC = 0x300		;EN_CHANGE
					    ||  NC = 0			;STN_CLICKED
					    ||  NC = 0x0			;BN_CLICKED
					    ||  NC = 1			;CBN_SELCHANGE, LBN_SELCHANGE
					    ||  NC = 5)) {		;CBN_EDITCHANGE
	
			; Make sure click is finished if any functions are using WM_LBUTTONUP
			While GetKeyState("LButton","P")
				Sleep 50

			; Compare the value
			GuiControlGet, Value,, % hCtrl

			If (this.Ctrl[hCtrl].Value != Value) {
				Modified := 1

			}
			
			; Execute target if control is modified
			If (Modified) {
				If (this.Ctrl[hCtrl].Group)
					this.Reset("", this.Ctrl[hCtrl].Group)
				Else
					this.Reset(hCtrl) ; Reset controls compare value to the new value
				
				If IsLabel(this.target)
					GoSub % this.Target
				Else If IsFunc(this.Target)
					this.Target.Call(hCtrl)
			}
		}
	}
}
Example:

Code: Select all

; =======================================================================================================================
; Class_ControlMonitor Example - By RazorHalo
; =======================================================================================================================

#NoEnv
#SingleInstance Force
SetWorkingDir %A_ScriptDir%
SetBatchLines -1

#Include Class_ControlMonitor.ahk

MonitorRadioControls := 0

Gui Add, CheckBox, hWndhCheckBox vCheckBox x14 y6 w68 h23, CheckBox
Gui Add, ComboBox, hWndhComboBox vComboBox x160 y40 w121, ComboBox||Option 1|Option 2|Option 3
Gui Add, Edit, hWndhEditBox vEditBox x108 y8 w172 h21, EditBox
Gui Add, DropDownList, hWndhDropDownList vDropDownList x12 y39 w131, DropDownList||Option 1|Option 2|Option 3
Gui Add, ListBox, hWndhListBox vListBox x161 y76 w121 h108, ListBox||Red|Green|Blue|Black|White
Gui Add, Radio, hWndhRadioButton1 vRadioButton1 x31 y95 w91 h23, Radio Button 1
Gui Add, Radio, hWndhRadioButton2 vRadioButton2 x31 y122 w91 h24, Radio Button 2
Gui Add, Radio, hWndhRadioButton3 vRadioButton3 x31 y151 w91 h23, Radio Button 3
Gui Add, GroupBox, hWndhGroupBox vGroupBox x12 y72 w131 h113, NOT Monitored
Gui Add, Button, gMonitorRadioControls_TOGGLE x10 y194 w131 h23, Toggle Radio Monitor
Gui Font, s16 cRed
Gui Add, Text, hWndhTxtStatus x22 y253 w249 h24 +0x200 Center, None
Gui Font
Gui Font, s16 Underline
Gui Add, Text, x20 y223 w249 h24 +0x200 Center, Last Updated Control
Gui Font
Gui Add, Button, hWndhBtnMonitor_TOGGLE vBtnMonitor_TOGGLE gMonitor_TOGGLE x152 y194 w131 h23, Pause Monitor

Gui Show, w291 h289, Control Monitor

; Define some arrays of control hWnds
SomeControls := [hCheckBox, hComboBox, hEditBox, hDropDownList, hGroupBox]
RadioControls := [hRadioButton1, hRadioButton2, hRadioButton3]

; Create a new instance
Monitor := New ControlMonitor

Monitor.Add(SomeControls) 	; Add controls by passing an array of hWnds
Monitor.Add(hListBox)		; Add single control by passing a single hWnd

; Set the target to execute when a control changes
; In this case its the name of a function, but can also be a label to jump to
Monitor.Target("RefreshStatus")


Return

; Clean up and Exit
GuiEscape:
GuiClose:
	Monitor.Destroy() ; Needed to release the references to the WM_COMMAND message function
	Monitor := ""
	ExitApp

; Toggle monitoring of Radio Controls by adding and removing them from the monitor
MonitorRadioControls_TOGGLE:
	If (MonitorRadioControls := !MonitorRadioControls) {
		GuiControl ,, % hGroupBox, Monitoring
		Monitor.Add(RadioControls, "Radio") ; Radio controls need to be added as a group
	} Else {
		GuiControl ,, % hGroupBox, NOT Monitored
		Monitor.Remove(RadioControls)
	}
Return

; Pause and resume monitoring
Monitor_TOGGLE:
	If (Monitor.Status := ! Monitor.Status) {
		GuiControl ,, % hBtnMonitor_TOGGLE, Resume Monitor
		Monitor.Off()
	} Else {
		GuiControl ,, % hBtnMonitor_TOGGLE, Pause Monitor
		Monitor.On()
	}

Return

; Target function - just updates the gui as to what control was changed
RefreshStatus(hWnd) {
	Global hTxtStatus
	GuiControlGet, vLabel, Name, % hWnd
	GuiControl ,, % hTxtStatus, % vLabel
}

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: FlyingFree and 82 guests