Scrollable GUI from given position

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Chappier
Posts: 44
Joined: 21 Aug 2021, 21:58

Scrollable GUI from given position

Post by Chappier » 21 Aug 2021, 22:24

Would like to ask if someone have any idea in how to scroll the GUI starting from a given Y position.

Example:
Spoiler

I mean, keep from the yellow lines up static/unscrollable, and scroll starting from the red lines.

Code: Select all

SetBatchLines, -1

OnMessage(0x115, "OnScroll") ; WM_VSCROLL
;OnMessage(0x020A, "WM_MOUSEWHEEL")

; 0x00200000 = WS_VSCROLL
Gui, -DPIScale +Resize +0x00200000
Gui, Color, 0
Gui, Font, s20, Consolas

Gui, Add, Text, xm ym cYellow  , =====================
Gui, Add, Text, xm y+10 cYellow, KEEP THIS LINE STATIC
Gui, Add, Text, xm y+10 cYellow, =====================

Gui, Font, s12, Consolas

Loop, 50
   Gui, Add, Text, xm y+10 w150 cRed , %A_Index%
Gui, Show, w400 h300, Debug
Return



GuiSize:
   UpdateScrollBars(A_Gui, A_GuiWidth, A_GuiHeight)
Return



;---------------------------------------------------------------------------
; OnScroll and UpdateScrollBars by Lexikos made x64/x32 compatible by jeeswg.
; https://www.autohotkey.com/boards/viewtopic.php?p=237412#p237412

OnScroll(wParam, lParam, msg, hwnd) {
  Static  SCROLL_STEP=30

  bar := msg=0x115 ; SB_HORZ=0, SB_VERT=1

  VarSetCapacity(si, 28, 0)
  NumPut(28, si, 0, "uint") ; cbSize
  NumPut(0x17, si, 4, "uint") ; fMask
  if !DllCall("GetScrollInfo", "ptr", hwnd, "int", bar, "ptr", &si)
    return

  VarSetCapacity(rect, 16)
  DllCall("GetClientRect", "ptr", hwnd, "ptr", &rect)

  new_pos := NumGet(si, 20, "int") ; nPos

  action := wParam & 0xFFFF
  if action = 0 ; SB_LINEUP
    new_pos -= SCROLL_STEP
  else if action = 1 ; SB_LINEDOWN
    new_pos += SCROLL_STEP
  else if action = 2 ; SB_PAGEUP
    new_pos -= NumGet(rect, 12, "int") - SCROLL_STEP
  else if action = 3 ; SB_PAGEDOWN
    new_pos += NumGet(rect, 12, "int") - SCROLL_STEP
  else if (action = 5 || action = 4) ; SB_THUMBTRACK || SB_THUMBPOSITION
    new_pos := wParam>>16
  else if action = 6 ; SB_TOP
    new_pos := NumGet(si, 8, "int") ; nMin
  else if action = 7 ; SB_BOTTOM
    new_pos := NumGet(si, 12, "int") ; nMax
  else
    return

  min := NumGet(si, 8, "int") ; nMin
  max := NumGet(si, 12, "int") - NumGet(si, 16, "uint") ; nMax-nPage
  new_pos := new_pos > max ? max : new_pos
  new_pos := new_pos < min ? min : new_pos

  old_pos := NumGet(si, 20, "int") ; nPos

  x := y := 0
  if bar = 0 ; SB_HORZ
    x := old_pos-new_pos
  else
    y := old_pos-new_pos
  ; Scroll contents of window and invalidate uncovered area.
  DllCall("ScrollWindow", "ptr", hwnd, "int", x, "int", y, "ptr", 0, "ptr", 0)

  ; Update scroll bar.
  NumPut(new_pos, si, 20, "int") ; nPos
  DllCall("SetScrollInfo", "ptr", hwnd, "int", bar, "ptr", &si, "int", 1)
}



UpdateScrollBars(GuiNum, GuiWidth, GuiHeight) {
  Static SIF_RANGE=0x1, SIF_PAGE=0x2, SIF_DISABLENOSCROLL=0x8, SB_HORZ=0, SB_VERT=1
  Gui, %GuiNum%:Default
  Gui, +LastFound

  ; Calculate scrolling area.
  Left := Top := 9999
  Right := Bottom := 0
  WinGet, ControlList, ControlList
  Loop, Parse, ControlList, `n
  {
    GuiControlGet, c, Pos, %A_LoopField%
    if (cX < Left)
      Left := cX
    if (cY < Top)
      Top := cY
    if (cX + cW > Right)
      Right := cX + cW
    if (cY + cH > Bottom)
      Bottom := cY + cH
  }
  Left -= 0
  Top -= 0
  Right += 0
  Bottom += 0
  ScrollWidth := 0 ;Right-Left
  ScrollHeight := Bottom-Top

  ; Initialize SCROLLINFO.
  VarSetCapacity(si, 28, 0)
  NumPut(28, si, 0, "uint") ; cbSize
  NumPut(SIF_RANGE | SIF_PAGE, si, 4, "uint") ; fMask

  ; Update horizontal scroll bar.
  NumPut(ScrollWidth, si, 12, "int") ; nMax
  NumPut(GuiWidth, si, 16, "uint") ; nPage
  DllCall("SetScrollInfo", "ptr", WinExist(), "int", SB_HORZ, "ptr", &si, "int", 1)

  ; Update vertical scroll bar.
  ; NumPut(SIF_RANGE | SIF_PAGE | SIF_DISABLENOSCROLL, si, 4, "uint") ; fMask
  NumPut(ScrollHeight, si, 12, "int") ; nMax
  NumPut(GuiHeight, si, 16, "uint") ; nPage
  DllCall("SetScrollInfo", "ptr", WinExist(), "int", SB_VERT, "ptr", &si, "int", 1)

  if (Left < 0 && Right < GuiWidth)
    x := Abs(Left) > GuiWidth-Right ? GuiWidth-Right : Abs(Left)
  if (Top < 0 && Bottom < GuiHeight)
    y := Abs(Top) > GuiHeight-Bottom ? GuiHeight-Bottom : Abs(Top)
  if (x || y)
    DllCall("ScrollWindow", "ptr", WinExist(), "int", x, "int", y, "ptr", 0, "ptr", 0)
}

I was reading the Microsoft docs about ScrollWindow:
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-scrollwindow
Type: const RECT*

Pointer to the RECT structure specifying the portion of the client area to be scrolled. If this parameter is NULL, the entire client area is scrolled.

Then i tried to specify the rect starting from Y50:

Code: Select all

   VarSetCapacity(RECT, 16, 0)
   WinGetPos,,, W, H, Debug
   NumPut(0, RECT,  0, "Float")
   NumPut(50, RECT,  4, "Float")
   NumPut(W, RECT,  8, "Float")
   NumPut(H, RECT, 12, "Float")

   ; Scroll contents of window and invalidate uncovered area.
   DllCall("ScrollWindow", "ptr", hwnd, "int", x, "int", y, "ptr", &Rect, "ptr", 0)

   ; Update scroll bar.
   NumPut(new_pos, si, 20, "int") ; nPos
   DllCall("SetScrollInfo", "ptr", hwnd, "int", bar, "ptr", &si, "int", 1)

But now it does not scroll anything.

User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Scrollable GUI from given position

Post by kczx3 » 23 Aug 2021, 20:56

An easier way I’ve found is to just use child GUIs and scroll the lower child.

rogaty69
Posts: 4
Joined: 13 Mar 2019, 09:31

Re: Scrollable GUI from given position

Post by rogaty69 » 24 Jan 2022, 12:16

kczx3 wrote:
23 Aug 2021, 20:56
An easier way I’ve found is to just use child GUIs and scroll the lower child.
Yeah, it works, I'm, using it. But the problem is when you try to update some controls, i.e. checkboxes.
I have some on the child window and one on the parent. When the parent's window checkbox (All) is checked, checkbox (CheckA) on the child window should be too, but is not.
As long as I work inside parent window, everything works. I can get the status of the control from the child window using GuiControllGet, but I'm unable to change the control status.
Below is my code It is not a "real life" app, I only wanted to point to the problem:

Code: Select all

#SingleInstance force

FileInstall, No.ico, %temp%\No.ico, 1
FileInstall, Skip.ico, %temp%\Skip.ico, 1
FileInstall, Exit.ico, %temp%\Exit.ico, 1
FileInstall, Docs.ico, %temp%\Docs.ico, 1
FileInstall, Daily.ico, %temp%\Daily.ico, 1
FileInstall, Info.ico, %temp%\Info.ico, 1
FileInstall, File.ico, %temp%\File.ico, 1

Gui, +Resize
Gui, Color, White,, White
Gui, Add, CheckBox, vCheckAll gCheckAll, All (GUI1)
Gui, 2:+Scroll -Caption
Gui, 2:Color, White
Gui, 2:Add, CheckBox, vCheckA gCheckA Check3, CheckA (GUI2)
Gui, 2:Add, Picture, y+20 w32 h-1, OK.ico
Gui, 2:Add, Picture, w32 h-1, %temp%\No.ico
Gui, 2:Add, Picture, w32 h-1, %temp%\Skip.ico
Gui, 2:Add, Picture, w32 h-1, %temp%\Exit.ico
Gui, 2:Add, Picture, w32 h-1, %temp%\Docs.ico
Gui, 2:Add, Picture, w32 h-1, %temp%\Daily.ico
Gui, 2:Add, Picture, w32 h-1, %temp%\Info.ico
Gui, 2:Add, Picture, w32 h-1, %temp%\File.ico
Gui, Add, Gui, xm y+10 w400 h200 aw, 2
Gui, Add, Edit, xm wp y+6 aya aw ah1 w150, Test3
Gui, Add, Checkbox, y+10 vCheckB gCheckB, CheckB (GUI1)
Gui, Add, GroupBox, +VScroll w300 h100 R5
Gui, GroupBox:Add, Edit,, Test
Gui, GroupBox:Add, Edit,, Test
Gui, GroupBox:Add, Edit,, Test
Gui, GroupBox:Add, Edit,, Test
Gui, GroupBox:Add, Edit,, Test


Gui, Add, Picture, w32 h-1, Skip.ico
Gui, Show
return

CheckA:
return

CheckB:
Gui, 2:Submit, NoHide
GuiControlGet, StateB,, CheckB
MsgBox CheckA: %CheckA%`nCheckB: %StateB%
return

CheckAll:
Gui, 2:Submit, NoHide
GuiControlGet, InsState,, Child:CheckA
MsgBox %CheckA%
GuiControl,, CheckA, 1
return

GuiClose:
if FileExist(temp "\No.ico")
	FileDelete, %temp%\No.ico
if FileExist(temp "\Skip.ico")
	FileDelete, %temp%\Skip.ico
if FileExist(temp "\Exit.ico")
	FileDelete, %temp%\Exit.ico
if FileExist(temp "\Docs.ico")
	FileDelete, %temp%\Docs.ico
if FileExist(temp "\Daily.ico")
	FileDelete, %temp%\Daily.ico
if FileExist(temp "\Info.ico")
	FileDelete, %temp%\Info.ico
if FileExist(temp "\File.ico")
	FileDelete, %temp%\File.ico
ExitApp
So after I check the CheckB I'm getting the status, but GuiControl part does not work. So far I'm unable to find a solution.
Sorry for the mess on the form, I was just trying to put some icon on it to allow scrolling. I'm working on the much bigger app and so far this is the only problem I can't get over.


[Edit]
Just found a solution...
So instead of having:

Code: Select all

CheckAll:
Gui, 2:Submit, NoHide
GuiControlGet, InsState,, Child:CheckA
MsgBox %CheckA%
GuiControl,, CheckA, 1
return
I put:

Code: Select all

CheckAll:
Gui, 2:Default
GuiControl,, CheckA, 1
return

logan9
Posts: 33
Joined: 22 Feb 2022, 12:48

Re: Scrollable GUI from given position

Post by logan9 » 21 Apr 2022, 16:55

I was searching for the same thing, replying to this topic with hope someone else with more knowledge about could help somehow.
I would like to avoid creating child GUIs and bind them with setparent just to achieve this.

just me
Posts: 9453
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Scrollable GUI from given position

Post by just me » 22 Apr 2022, 03:02

You might try to use the ClipRect parameter of ScrollWindow.

Changes to the sample in the OP:

Code: Select all

Gui, Add, Text, xm y+10 cYellow vTX, ===================== ; changed
GuiControlGet, TX, Pos ; added
Global OffY := TXY + TXH ; added
...
  ; Scroll contents of window and invalidate uncovered area.
  NumPut(OffY, rect, 4, "Int") ; added
  DllCall("ScrollWindow", "ptr", hwnd, "int", x, "int", y, "ptr", 0, "ptr", &rect) ; changed

Chappier
Posts: 44
Joined: 21 Aug 2021, 21:58

Re: Scrollable GUI from given position

Post by Chappier » 22 Apr 2022, 09:17

@just me
Its scrolling from the given area, however its causing glitches:
2022-04-22_11-11-32.gif
2022-04-22_11-11-32.gif (38.82 KiB) Viewed 709 times

I also tried with ScrollWindowEx and it happens the same thing
I tried playing with the flags and in which value to use the rect, but I must be missing something:

Code: Select all

		;flags := SW_ERASE | SW_SCROLLCHILDREN | SW_INVALIDATE
		;flags := SW_SCROLLCHILDREN | SW_INVALIDATE | SW_SMOOTHSCROLL | (100 << 16)
		flags := SW_SCROLLCHILDREN
		
		/*
			prcScroll:
				Pointer to a RECT structure that specifies the portion of the client area to be scrolled. If this parameter is NULL, the entire client area is scrolled.

			prcClip:
				Pointer to a RECT structure that contains the coordinates of the clipping rectangle. Only device bits within the clipping rectangle are affected. Bits scrolled from the outside of the rectangle to the inside are painted; bits scrolled from the inside of the rectangle to the outside are not painted. This parameter may be NULL.

			hrgnUpdate:
				Handle to the region that is modified to hold the region invalidated by scrolling. This parameter may be NULL.

			prcUpdate:
				Pointer to a RECT structure that receives the boundaries of the rectangle invalidated by scrolling. This parameter may be NULL
		*/
		DllCall("User32.dll\ScrollWindowEx", "ptr", hwnd, "int", x, "int", y, "ptr"
		, 0, "ptr"   		 ; prcScroll
		, &Scroll_Rect, "ptr"   ; prcClip
		, 0, "ptr"        		 ; hrgnUpdate
		, 0, "Uint"       		 ; prcUpdate
		, flags)    


Updated code:

Code: Select all

SetBatchLines, -1

OnMessage(0x115, "OnScroll") ; WM_VSCROLL
OnMessage(0x020A, "WM_MOUSEWHEEL")


; 0x00200000 = WS_VSCROLL
Gui, +hWndhGui +0x00200000 -DPIScale +Resize
Gui, +E0x02000000 +E0x00080000
Gui, Color, 0
Gui, Font, s20, Consolas

Gui, Add, Text, xm ym cYellow  , =====================
Gui, Add, Text, xm y+10 cYellow, KEEP THIS LINE STATIC
Gui, Add, Text, xm y+10 cYellow, ===================== ; changed


Gui, Font, s12, Consolas

Loop, 50
   Gui, Add, Text, xm y+10 w150 cRed , %A_Index%
Gui, Show, w400 h400, Debug
Return



Esc::Exitapp
GuiSize:
   UpdateScrollBars(A_Gui, A_GuiWidth, A_GuiHeight)
Return



WM_MOUSEWHEEL(wParam, lParam, msg, hWnd) {
	Global hGui
   ; Scroll up
   If ((wParam >> 16) < 32768)
      OnScroll(0, 1, 0x115, hGui)
   Else ; Scroll down
      OnScroll(1, 1, 0x115, hGui)
}



; OnScroll and UpdateScrollBars by Lexikos made x64/x32 compatible by jeeswg.
; https://www.autohotkey.com/boards/viewtopic.php?p=237412#p237412

OnScroll(wParam, lParam, msg, hwnd) {
  
	Static  SCROLL_STEP=30

	bar := msg=0x115 ; SB_HORZ=0, SB_VERT=1

	VarSetCapacity(si, 28, 0)
	NumPut(28, si, 0, "uint") ; cbSize
	NumPut(0x17, si, 4, "uint") ; fMask
	if !DllCall("GetScrollInfo", "ptr", hwnd, "int", bar, "ptr", &si)
		return

	VarSetCapacity(rect, 16)
	DllCall("GetClientRect", "ptr", hwnd, "ptr", &rect)

	new_pos := NumGet(si, 20, "int") ; nPos

	action := wParam & 0xFFFF
	if action = 0 ; SB_LINEUP
		new_pos -= SCROLL_STEP
	else if action = 1 ; SB_LINEDOWN
		new_pos += SCROLL_STEP
	else if action = 2 ; SB_PAGEUP
		new_pos -= NumGet(rect, 12, "int") - SCROLL_STEP
	else if action = 3 ; SB_PAGEDOWN
		new_pos += NumGet(rect, 12, "int") - SCROLL_STEP
	else if (action = 5 || action = 4) ; SB_THUMBTRACK || SB_THUMBPOSITION
		new_pos := wParam>>16
	else if action = 6 ; SB_TOP
		new_pos := NumGet(si, 8, "int") ; nMin
	else if action = 7 ; SB_BOTTOM
		new_pos := NumGet(si, 12, "int") ; nMax
	else
		return

	min := NumGet(si, 8, "int") ; nMin
	max := NumGet(si, 12, "int") - NumGet(si, 16, "uint") ; nMax-nPage
	new_pos := new_pos > max ? max : new_pos
	new_pos := new_pos < min ? min : new_pos

	old_pos := NumGet(si, 20, "int") ; nPos

	x := y := 0
	if bar = 0 ; SB_HORZ
		x := old_pos-new_pos
	else
		y := old_pos-new_pos


	
	; ScrollWindowEx flags.
   static SW_ERASE = 0x4, SW_SCROLLCHILDREN = 0x0001, SW_INVALIDATE = 0X2, SW_SMOOTHSCROLL = 0x10



	DllCall("UpdateWindow", "ptr", hWnd)
	;NumPut(133, rect, 4, "Int") ; added

   VarSetCapacity(Scroll_Rect, 16)
   DllCall("GetClientRect", "ptr", hWnd, "ptr", &Scroll_Rect)
   NumPut(180,  Scroll_Rect,  4, "int")



	If (ScrollWindow:=1) {

		/*
		lpRect
			Pointer to the RECT structure specifying the portion of the client area to be scrolled. If this parameter is NULL, the entire client area is scrolled.

		lpClipRect:
			Pointer to the RECT structure containing the coordinates of the clipping rectangle. Only device bits within the clipping rectangle are affected. Bits scrolled from the outside of the rectangle to the inside are painted; bits scrolled from the inside of the rectangle to the outside are not painted.
		*/		
		DllCall("ScrollWindow", "ptr", hwnd, "int", x, "int", y, "ptr"
		, 0, "ptr" 		  	; lpRect
		, &Scroll_Rect) 	; lpClipRect

	}
	


	If (ScrollWindowEx) {
		
		;flags := SW_ERASE | SW_SCROLLCHILDREN | SW_INVALIDATE
		;flags := SW_SCROLLCHILDREN | SW_INVALIDATE | SW_SMOOTHSCROLL | (100 << 16)
		flags := SW_SCROLLCHILDREN
		
		/*
			prcScroll:
				Pointer to a RECT structure that specifies the portion of the client area to be scrolled. If this parameter is NULL, the entire client area is scrolled.

			prcClip:
				Pointer to a RECT structure that contains the coordinates of the clipping rectangle. Only device bits within the clipping rectangle are affected. Bits scrolled from the outside of the rectangle to the inside are painted; bits scrolled from the inside of the rectangle to the outside are not painted. This parameter may be NULL.

			hrgnUpdate:
				Handle to the region that is modified to hold the region invalidated by scrolling. This parameter may be NULL.

			prcUpdate:
				Pointer to a RECT structure that receives the boundaries of the rectangle invalidated by scrolling. This parameter may be NULL.
		*/
		DllCall("User32.dll\ScrollWindowEx", "ptr", hwnd, "int", x, "int", y, "ptr"
		, 0, "ptr"   		 ; prcScroll
		, &Scroll_Rect, "ptr"   ; prcClip
		, 0, "ptr"        		 ; hrgnUpdate
		, 0, "Uint"       		 ; prcUpdate
		, flags)           
	
	}



	; Update scroll bar.
	NumPut(new_pos, si, 20, "int") ; nPos
	DllCall("SetScrollInfo", "ptr", hwnd, "int", bar, "ptr", &si, "int", 1)

	DllCall("UpdateWindow", "ptr", hWnd)
       ;DllCall("User32.dll\InvalidateRect", "Ptr", hWnd, "Ptr", &rect, "Int", 1)

}



UpdateScrollBars(GuiNum, GuiWidth, GuiHeight) {
  Static SIF_RANGE=0x1, SIF_PAGE=0x2, SIF_DISABLENOSCROLL=0x8, SB_HORZ=0, SB_VERT=1
  Gui, %GuiNum%:Default
  Gui, +LastFound

  ; Calculate scrolling area.
  Left := Top := 9999
  Right := Bottom := 0
  WinGet, ControlList, ControlList
  Loop, Parse, ControlList, `n
  {
    GuiControlGet, c, Pos, %A_LoopField%
    if (cX < Left)
      Left := cX
    if (cY < Top)
      Top := cY
    if (cX + cW > Right)
      Right := cX + cW
    if (cY + cH > Bottom)
      Bottom := cY + cH
  }
  Left -= 0
  Top -= 0
  Right += 0
  Bottom += 0
  ScrollWidth := 0 ;Right-Left
  ScrollHeight := Bottom-Top

  ; Initialize SCROLLINFO.
  VarSetCapacity(si, 28, 0)
  NumPut(28, si, 0, "uint") ; cbSize
  NumPut(SIF_RANGE | SIF_PAGE, si, 4, "uint") ; fMask

  ; Update horizontal scroll bar.
  NumPut(ScrollWidth, si, 12, "int") ; nMax
  NumPut(GuiWidth, si, 16, "uint") ; nPage
  DllCall("SetScrollInfo", "ptr", WinExist(), "int", SB_HORZ, "ptr", &si, "int", 1)

  ; Update vertical scroll bar.
  ; NumPut(SIF_RANGE | SIF_PAGE | SIF_DISABLENOSCROLL, si, 4, "uint") ; fMask
  NumPut(ScrollHeight, si, 12, "int") ; nMax
  NumPut(GuiHeight, si, 16, "uint") ; nPage
  DllCall("SetScrollInfo", "ptr", WinExist(), "int", SB_VERT, "ptr", &si, "int", 1)

  if (Left < 0 && Right < GuiWidth)
    x := Abs(Left) > GuiWidth-Right ? GuiWidth-Right : Abs(Left)
  if (Top < 0 && Bottom < GuiHeight)
    y := Abs(Top) > GuiHeight-Bottom ? GuiHeight-Bottom : Abs(Top)
  if (x || y)
    DllCall("ScrollWindow", "ptr", WinExist(), "int", x, "int", y, "ptr", 0, "ptr", 0)
}

User avatar
lmstearn
Posts: 694
Joined: 11 Aug 2016, 02:32
Contact:

Re: Scrollable GUI from given position

Post by lmstearn » 23 Apr 2022, 09:30

Visual artifacts occur when dragging the scrollbar, not when clicking the scroll arrows. Object or window drag is often an event which impacts system resourcing due to the number of key-down messages dispatched, so using a timer will regulate the number of calls to OnScroll, ensuring the reliability of values in there. Example.
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH

Chappier
Posts: 44
Joined: 21 Aug 2021, 21:58

Re: Scrollable GUI from given position

Post by Chappier » 23 Apr 2022, 18:22

@lmstearn i had found your topic before and also test it, i see in your script you limit the scroll speed to 5sec

do you mean only allow scroll between a minimum time?

User avatar
lmstearn
Posts: 694
Joined: 11 Aug 2016, 02:32
Contact:

Re: Scrollable GUI from given position

Post by lmstearn » 24 Apr 2022, 03:08

Yes, the scrollbars update every 5 milliseconds, other values can be sampled according to script speed and system grunt. Seems to work all right on this machine. :)
:arrow: itros "ylbbub eht tuO kaerB" a ni kcuts m'I pleH

Post Reply

Return to “Ask for Help (v1)”