Visão Geral
Este guia demonstra como implementar barras de rolagem em interfaces gráficas utilizando AutoHotkey v2. A solução apresentada é uma adaptação modernizada de código originalmente desenvolvido para a versão 1, convertendo a sintaxe e aproveitando os recursos orientados a objetos do AHK v2.
Conceito Arquitetural
A estratégia adota uma estrutura hierárquica de jenelas: um container externo recebe as barras de rolagem, enquanto o conteúdo interativo reside em uma janela filha aninhada. Essa abordagem permite que o sistema operacional gerencie nativamente o comportamento de rolagem, mantendo a responsividade da interface.
Implementação da Classe
A classe RolavelGUI encapsula toda a lógica necessária. Segue a implementação completa compatível com AHK v2:
#Requires AutoHotkey v2.0
; ============================================================
; Classe: RolavelGUI
; Descrição: Container com capacidade de rolagem para GUIs
; Versão: 2.0.0 (adaptado para AHK v2)
; Base: Código original de "just me" (AHK v1)
; Licença: The Unlicense
; ============================================================
class RolavelGUI {
static ativos := {}
; Constantes de estilo de janela
static ESTILO_H := 0x100000, ESTILO_V := 0x200000
__Novo(HandleFilho, Largura, Altura, Opcoes := "", Barras := 3, Roda := 0) {
; Validação de parâmetros
Barras &= 3, Roda &= 7
if (!this._ValidarParametros(Barras, Roda))
return false
if !DllCall("User32.dll\IsWindow", "Ptr", HandleFilho, "UInt")
return false
; Configura janela filha
if !this._CalcularDimensoes(HandleFilho, &LarguraFilho, &AlturaFilho)
return false
guiFilho := GuiFromHwnd(HandleFilho)
guiFilho.Opt("-Caption -Resize")
guiFilho.Show(Format("w{} h{} Hide", LarguraFilho, AlturaFilho))
; Limites e métricas de rolagem
MaxH := LarguraFilho, MaxV := AlturaFilho
PassoH := Ceil(MaxH / 20), PassoV := Ceil(MaxV / 20)
; Ajusta dimensões do container
if (Largura = 0) || (Largura > MaxH)
Largura := MaxH
if (Altura = 0) || (Altura > MaxV)
Altura := MaxV
; Cria container com estilos apropriados
Estilos := (Barras & 1 ? " +" . this.ESTILO_H : "")
. (Barras & 2 ? " +" . this.ESTILO_V : "")
container := Gui(Opcoes . Estilos)
HwndContainer := container.Hwnd
container.Show(Format("w{} h{} Hide", Largura, Altura))
container.Opt(Format("+MaxSize{}x{}", MaxH, MaxV))
; Armazena referências
this.container := container
this.Hwnd := HwndContainer
this.HandleFilho := HandleFilho
this.Largura := Largura
this.Altura := Altura
this.UsarShift := this.TemH := this.TemV := false
; Configura barra horizontal
if (Barras & 1) {
this._ConfigurarBarra(0, MaxH, Largura + 1, 0x0114, Roda, 1)
this.MaxH := MaxH
this.PassoH := PassoH
this.PaginaH := Largura + 1
this.PosH := 0
this.TemH := true
}
; Configura barra vertical
if (Barras & 2) {
this._ConfigurarBarra(1, MaxV, Altura + 1, 0x0115, Roda, 2)
this.MaxV := MaxV
this.PassoV := PassoV
this.PaginaV := Altura + 1
this.PosV := 0
this.TemV := true
}
; Anexa filho ao container
guiFilho.Opt("+parent" . HwndContainer)
guiFilho.Show("x0 y0")
; Registra instância e configura redimensionamento
RolavelGUI.ativos.%HwndContainer% := this
this._AjustarDimensoes()
OnMessage(0x0005, (p*) => this._AoRedimensionar(p*))
}
_ValidarParametros(Barras, Roda) {
return ((Barras >= 1) && (Barras <= 3))
&& ((Roda >= 0) && (Roda <= 4))
}
_ConfigurarBarra(Tipo, Maximo, Pagina, MsgScroll, Roda, MascaraRoda) {
this._DefinirInfoScroll(Tipo, { Max: Maximo, Page: Pagina, Pos: 0 })
OnMessage(MsgScroll, (p*) => this._ProcessarScroll(p*))
if (Roda & 1) && (MascaraRoda & 1)
OnMessage(0x020E, (p*) => this._ProcessarRoda(p*))
else if (Roda & 4) {
OnMessage(0x020A, (p*) => this._ProcessarRoda(p*))
this.UsarShift := true
}
if (Roda & 6) && (MascaraRoda & 2)
OnMessage(0x020A, (p*) => this._ProcessarRoda(p*))
}
Mostrar(Titulo := "", OpcoesExibicao := "") {
OpcoesExibicao := RegExReplace(OpcoesExibicao, "i)\+?AutoSize")
this.container.Show(OpcoesExibicao . " w" . this.Largura . " h" . this.Altura)
WinSetTitle(Titulo, this.container)
return true
}
Destruir() {
if RolavelGUI.ativos.HasProp(this.Hwnd) {
GuiFromHwnd(this.Hwnd).Destroy()
RolavelGUI.ativos.DeleteProp(this.Hwnd)
return true
}
return false
}
__Delete() {
this.Destruir()
}
SincronizarFilho() {
RC := Buffer(16, 0)
DllCall("User32.dll\GetWindowRect", "Ptr", this.HandleFilho, "Ptr", RC.Ptr)
LarguraAntiga := NumGet(RC, 8, "Int") - NumGet(RC, 0, "Int")
AlturaAntiga := NumGet(RC, 12, "Int") - NumGet(RC, 4, "Int")
DllCall("User32.dll\ScreenToClient", "Ptr", this.Hwnd, "Ptr", RC.Ptr)
XAtual := XNovo := NumGet(RC, 0, "Int")
YAtual := YNovo := NumGet(RC, 4, "Int")
if !this._CalcularDimensoes(this.HandleFilho, &NovaLargura, &NovaAltura)
return false
guiFilho := GuiFromHwnd(this.HandleFilho)
guiFilho.Show(Format("x{} y{} w{} h{}", XAtual, YAtual, NovaLargura, NovaAltura))
this.container.Opt(Format("+MaxSize{}x{}", NovaLargura, NovaAltura))
if (NovaLargura < this.Largura) || (NovaAltura < this.Altura) {
guiFilho.Show("w" . NovaLargura . " h" . NovaAltura)
this.Largura := NovaLargura
this.Altura := NovaAltura
this._DefinirPagina(1, NovaLargura + 1)
this._DefinirPagina(2, NovaAltura + 1)
}
PassoH := Ceil(NovaLargura / 20)
PassoV := Ceil(NovaAltura / 20)
if this.TemH {
this._DefinirMaximo(1, NovaLargura)
this.PassoH := PassoH
if (XAtual + NovaLargura) < this.Largura {
XNovo += this.Largura - (XAtual + NovaLargura)
XNovo := Max(XNovo, 0)
this._DefinirInfoScroll(0, { Pos: XNovo * -1 })
this._ObterInfoScroll(0, &SI)
this.PosH := NumGet(SI, 20, "Int")
}
}
if this.TemV {
this._DefinirMaximo(2, NovaAltura)
this.PassoV := PassoV
if (YAtual + NovaAltura) < this.Altura {
YNovo += this.Altura - (YAtual + NovaAltura)
YNovo := Max(YNovo, 0)
this._DefinirInfoScroll(1, { Pos: YNovo * -1 })
this._ObterInfoScroll(1, &SI)
this.PosV := NumGet(SI, 20, "Int")
}
}
if (XAtual != XNovo) || (YAtual != YNovo)
DllCall("User32.dll\ScrollWindow", "Ptr", this.Hwnd,
"Int", XNovo - XAtual, "Int", YNovo - YAtual, "Ptr", 0, "Ptr", 0)
return true
}
_DefinirMaximo(Barra, Valor) {
Barra-- ; Converte 1/2 para 0/1
if (Barra != 0) && (Barra != 1)
return false
(Barra = 0) ? (this.MaxH := Valor) : (this.MaxV := Valor)
return this._DefinirInfoScroll(Barra, { Max: Valor })
}
_DefinirPagina(Barra, Valor) {
Barra--
if (Barra != 0) && (Barra != 1)
return false
(Barra = 0) ? (this.PaginaH := Valor) : (this.PaginaV := Valor)
return this._DefinirInfoScroll(Barra, { Page: Valor })
}
_CalcularDimensoes(Handle, &LarguraTotal, &AlturaTotal) {
EstadoAnterior := A_DetectHiddenWindows
DetectHiddenWindows(1)
Retangulo := Buffer(16, 0)
LarguraTotal := AlturaTotal := 0
HandleAtual := Handle
Comando := 5 ; GW_CHILD
Esquerda := Topo := Direita := Base := EsqOculto := TopoOculto := ""
while (HandleAtual := DllCall("GetWindow", "Ptr", HandleAtual, "UInt", Comando, "UPtr"))
&& (Comando := 2) {
WinGetPos(&X, &Y, &W, &H, HandleAtual)
PosDireita := X + W
PosBase := Y + H
Estilos := WinGetStyle(HandleAtual)
if (Estilos & 0x10000000) { ; WS_VISIBLE
if (Esquerda = "") || (X < Esquerda)
Esquerda := X
if (Topo = "") || (Y < Topo)
Topo := Y
if (Direita = "") || (PosDireita > Direita)
Direita := PosDireita
if (Base = "") || (PosBase > Base)
Base := PosBase
} else {
if (EsqOculto = "") || (X < EsqOculto)
EsqOculto := X
if (TopoOculto = "") || (Y < TopoOculto)
TopoOculto := Y
}
}
DetectHiddenWindows(EstadoAnterior)
; Converte coordenadas de tela para cliente
Ponto := Buffer(8, 0)
if (EsqOculto != "") {
NumPut("Int", EsqOculto, Ponto, 0)
DllCall("ScreenToClient", "Ptr", Handle, "Ptr", Ponto.Ptr)
EsqOculto := NumGet(Ponto, 0, "Int")
}
if (TopoOculto != "") {
NumPut("Int", TopoOculto, Ponto, 4)
DllCall("ScreenToClient", "Ptr", Handle, "Ptr", Ponto.Ptr)
TopoOculto := NumGet(Ponto, 4, "Int")
}
NumPut("Int", Esquerda, Retangulo, 0)
NumPut("Int", Topo, Retangulo, 4)
NumPut("Int", Direita, Retangulo, 8)
NumPut("Int", Base, Retangulo, 12)
DllCall("MapWindowPoints", "Ptr", 0, "Ptr", Handle, "Ptr", Retangulo.Ptr, "UInt", 2)
LarguraTotal := NumGet(Retangulo, 8, "Int")
+ (EsqOculto != "" ? EsqOculto : NumGet(Retangulo, 0, "Int"))
AlturaTotal := NumGet(Retangulo, 12, "Int")
+ (TopoOculto != "" ? TopoOculto : NumGet(Retangulo, 4, "Int"))
return true
}
_ObterInfoScroll(Barra, &Info) {
Info := Buffer(28, 0)
NumPut("UInt", 28, Info, 0)
NumPut("UInt", 0x17, Info, 4) ; SIF_ALL
return DllCall("User32.dll\GetScrollInfo", "Ptr", this.Hwnd,
"Int", Barra, "Ptr", Info.Ptr, "UInt")
}
_DefinirInfoScroll(Barra, Valores) {
static MASCARAS := { Max: 0x01, Page: 0x02, Pos: 0x04 }
static OFFSETS := { Max: 12, Page: 16, Pos: 20 }
Mascara := 0
Info := Buffer(28, 0)
NumPut("UInt", 28, Info, 0)
for Chave, Valor in Valores.OwnProps() {
if MASCARAS.HasProp(Chave) {
Mascara |= MASCARAS.%Chave%
NumPut("UInt", Valor, Info, OFFSETS.%Chave%)
}
}
if (Mascara) {
NumPut("UInt", Mascara | 0x08, Info, 4) ; SIF_DISABLENOSCROLL
return DllCall("User32.dll\SetScrollInfo", "Ptr", this.Hwnd,
"Int", Barra, "Ptr", Info.Ptr, "UInt", 1, "UInt")
}
return false
}
_ProcessarScroll(WP, LP, Msg, Hwnd) {
if !RolavelGUI.ativos.HasProp(Hwnd)
return
Instancia := RolavelGUI.ativos.%Hwnd%
if ((Msg = 0x0114) && Instancia.TemH) || ((Msg = 0x0115) && Instancia.TemV)
return Instancia._ExecutarScroll(WP, LP, Msg, Hwnd)
}
_ExecutarScroll(WP, LP, Msg, Hwnd) {
static ACoes := { 0: "LINEMINUS", 1: "LINEPLUS",
2: "PAGEMINUS", 3: "PAGEPLUS", 5: "THUMBTRACK" }
if (LP != 0)
return
Barra := (Msg = 0x0114) ? 0 : 1
Codigo := WP & 0xFFFF
Deslocamento := (Msg = 0x0114) ? this.PassoH : this.PassoV
if !this._ObterInfoScroll(Barra, &Info)
return
PosAtual := PosNova := NumGet(Info, 20, "Int")
TamanhoPagina := NumGet(Info, 16, "UInt")
switch Codigo {
case 0: PosNova := PosAtual - Deslocamento
case 1: PosNova := PosAtual + Deslocamento
case 2: PosNova := PosAtual - TamanhoPagina
case 3: PosNova := PosAtual + TamanhoPagina
case 5: PosNova := NumGet(Info, 24, "Int")
}
if (PosAtual = PosNova)
return 0
this._DefinirInfoScroll(Barra, { Pos: PosNova })
this._ObterInfoScroll(Barra, &Info)
PosNova := NumGet(Info, 20, "Int")
(Barra = 0) ? (this.PosH := PosNova) : (this.PosV := PosNova)
if (PosAtual != PosNova) {
DeslH := (Msg = 0x0114) ? PosAtual - PosNova : 0
DeslV := (Msg = 0x0115) ? PosAtual - PosNova : 0
DllCall("User32.dll\ScrollWindow", "Ptr", this.Hwnd,
"Int", DeslH, "Int", DeslV, "Ptr", 0, "Ptr", 0)
}
return 0
}
_AoRedimensionar(WP, LP, Msg, Hwnd) {
if ((WP = 0) || (WP = 2)) && RolavelGUI.ativos.HasProp(Hwnd) {
Largura := LP & 0xFFFF
Altura := (LP >> 16) & 0xFFFF
return RolavelGUI.ativos.%Hwnd%._AjustarDimensoes(Largura, Altura)
}
}
_AjustarDimensoes(Largura := 0, Altura := 0) {
if (Largura = 0) || (Altura = 0) {
RC := Buffer(16, 0)
DllCall("User32.dll\GetClientRect", "Ptr", this.Hwnd, "Ptr", RC.Ptr)
Largura := NumGet(RC, 8, "Int")
Altura := NumGet(RC, 12, "Int")
}
DeslH := DeslV := 0
if this.TemH && (Largura != this.Largura) {
this._DefinirInfoScroll(0, { Page: Largura + 1 })
this.Largura := Largura
this._ObterInfoScroll(0, &Info)
NovaPosH := NumGet(Info, 20, "Int")
DeslH := this.PosH - NovaPosH
this.PosH := NovaPosH
}
if this.TemV && (Altura != this.Altura) {
this._DefinirInfoScroll(1, { Page: Altura + 1 })
this.Altura := Altura
this._ObterInfoScroll(1, &Info)
NovaPosV := NumGet(Info, 20, "Int")
DeslV := this.PosV - NovaPosV
this.PosV := NovaPosV
}
if (DeslH) || (DeslV)
DllCall("User32.dll\ScrollWindow", "Ptr", this.Hwnd,
"Int", DeslH, "Int", DeslV, "Ptr", 0, "Ptr", 0)
return 0
}
_ProcessarRoda(WP, LP, Msg, Hwnd) {
HwndAtivo := WinActive("A") + 0
if (HwndAtivo != Hwnd) && RolavelGUI.ativos.HasProp(HwndAtivo) {
Instancia := RolavelGUI.ativos.%HwndAtivo%
SobreBarra := SendMessage(0x0084, 0, LP & 0xFFFFFFFF, , HwndAtivo)
if (SobreBarra = 6) && Instancia.TemH
return Instancia._ExecutarRoda(WP, LP, 0x020E, HwndAtivo)
if (SobreBarra = 7) && Instancia.TemV
return Instancia._ExecutarRoda(WP, LP, 0x020A, HwndAtivo)
}
if RolavelGUI.ativos.HasProp(Hwnd) {
Instancia := RolavelGUI.ativos.%Hwnd%
if ((Msg = 0x020E) && Instancia.TemH)
|| ((Msg = 0x020A) && (Instancia.TemV
|| (Instancia.TemH && Instancia.UsarShift && (WP & 0x0004))))
return Instancia._ExecutarRoda(WP, LP, Msg, Hwnd)
}
}
_ExecutarRoda(WP, LP, Msg, Hwnd) {
if (Msg = 0x020A) && this.UsarShift && (WP & 0x0004)
Msg := 0x020E
MsgScroll := (Msg = 0x020A) ? 0x0115 : 0x0114
Direcao := ((WP >> 16) > 0x7FFF) || (WP < 0) ? 1 : 0
return this._ExecutarScroll(Direcao, 0, MsgScroll, Hwnd)
}
}
Exemplos Práticos
Exemplo 1: Uso Básico
#Include RolavelGUI.ahk
Esc::ExitApp()
; Cria interface principal
janelaPrincipal := Gui("+Resize")
janelaPrincipal.BackColor := "f1ffb1"
janelaPrincipal.AddText("x0 y0", "Demonstração")
; Constrói conteúdo extenso
conteudo := Gui()
Loop 100 {
linha := A_Index
Loop 5 {
if A_Index = 1
conteudo.AddText("xm y+10 w21 h21 0x201", linha)
else
conteudo.AddText("xm y+10 w21 h21 yp 0x201", linha)
conteudo.AddEdit(" x+10 yp w55 hp r1", A_Index)
}
}
; Instancia container rolável (altura máxima de 100px)
rolavel := RolavelGUI(conteudo.Hwnd, 0, 100, "-Caption -Resize", 2, 2)
rolavel.Mostrar("Interface Rolável", "x0 y100 NA")
; Embede no container principal
rolavel.container.Opt("+Parent" . janelaPrincipal.Hwnd)
DllCall("SetParent", "Ptr", rolavel.container.Hwnd, "Ptr", janelaPrincipal.Hwnd)
janelaPrincipal.Show("w300 h400")
Exemplo 2: Sincronização Dinâmica
janelaDinamica := Gui()
janelaDinamica.AddButton("x0 y0 w80 h20", "Alterar Tamanho")
.OnEvent("Click", Atualizar)
Loop 10 {
linha := A_Index
Loop 5 {
if A_Index = 1
janelaDinamica.AddText("xm y+10 w22 0x201", linha)
else
janelaDinamica.AddText("x+10 w22 yp 0x201", linha)
janelaDinamica.AddEdit("x+10 yp w55 hp r1", A_Index)
}
}
rolavelDinamico := RolavelGUI(janelaDinamica.Hwnd, 330, 200, "Caption Resize", 3, 4)
rolavelDinamico.Mostrar("Rolagem Dinâmica", "x100 y500 NA")
Atualizar(*) {
rolavelDinamico.container.Move(,, 30)
rolavelDinamico.SincronizarFilho()
}
Exemplo 3: Controle Programático
painel := Gui()
painel.SetFont("s15")
painel.AddText("x0 y0 w200 h100")
Loop 60 {
painel.AddText("x0 yp+20 h20 Border Backgroundc8ffcb", A_Index)
}
painel.BackColor := "e5ffee"
rolavelManual := RolavelGUI(painel.Hwnd, 200, 200, "-Caption -Resize", 2, 2)
rolavelManual.SetLine(2, 20)
rolavelManual.Mostrar("Controle Manual", "x500 y500 NA")
referencia := rolavelManual.container
; Atalhos para navegação
a:: rolavelManual._ProcessarScroll(1, 0, 0x0115, referencia.Hwnd)
d:: rolavelManual._ProcessarScroll(0, 0, 0x0115, referencia.Hwnd)
q:: {
while rolavelManual.PosV > 1
SendMessage(0x0115, 2, 0, rolavelManual.container)
}
e:: {
while rolavelManual.MaxV - rolavelManual.PaginaV + 1 > rolavelManual.PosV
SendMessage(0x0115, 3, 0, rolavelManual.container)
}
Referência de Parâmetros
| Parâmetro | Descrição | Valores |
|---|---|---|
Barras |
Tipo de barras de rolagem | 1=horizontal, 2=vertical, 3=ambas |
Roda |
Comportamento da roda do mouse | 0=desativado, 1=horizontal, 2=vertical, 3=ambas, 4=vertical+Shift |
Largura/Altura |
Dimensões visíveis | 0=automático (baseado no conteúdo) |
Observações Importantes
- As dimensões especificadas nunca excedem o tamanho real do conteúdo
- Utilize
SincronizarFilho()após modificar controles dinamicamente - O modificador Ctrl permite rolagem mesmo com controles focados
- Para ocultar barras visualmente, utilize
WinSetRegion()no container