Uma classe encadeada e independente para monitorar a presença do usuário de várias maneiras
Esta é uma classe simples que monitora 3 eventos: o estado do monitor (off, on ou diminuiu), a mensagem 'Usuário presente' do sistema e, se você estiver em um laptop, o status da tampa (aberto, fechar). Isso é feito em uma classe totalmente independente- a complicação disso surge porque esses eventos são enviados na forma de uma mensagem WM_POWERBROADCAST , rentando uma janela para recebê-la. Mas eu não queria limitar a classe a aplicativos gráficos e apenas subclasse um formulário, embora por uma questão de completude o aplicativo de demonstração mostre como fazê -lo dessa maneira também. Portanto, a classe cria sua própria janela oculta totalmente personalizada, em um encadeamento separado para evitar o loop de mensagem bloqueando o loop de mensagem do formulário, se presente.
A classe aproveita os construtores parametrizes do TwinBasic para especificar os argumentos disponíveis diretamente na nova palavra -chave; Aqui estão os argumentos opcionais para quais eventos levantarem e o Hinstance a se registrar em ( App.hInstance 99% das vezes; se você omitir, ele usará GetModuleHandleW() , que retorna o mesmo valor que App.hInstance sem criar uma dependência de formas winnativas). Então chegamos a um dos grandes prazeres da TB: chamando CreateThread sem nenhum hacks elaborado neco, tão legal quanto no VB6. O próprio ThreadProc simplesmente chama o restante do código.
Sub New(Optional ByVal dwNotifyMask As CPMonEventNotify = CPMEN_ALL, Optional ByVal hInst As LongPtr)
m_hInst = If(hInst = 0, GetModuleHandleW(), hInst)
If dwNotifyMask = CPMEN_ERROR Then Exit Sub
m_Mask = dwNotifyMask
tConfig.hInst = m_hInst
tConfig.Mask = m_Mask
m_hThread = CreateThread(ByVal 0, 0, AddressOf CPMonProc, tConfig, 0, m_idThread)
End Sub
RegisterClassEx e CreateWindowEx são usados apenas em uma rotina normal para criar uma janela oculta, com o WNDPROC sendo uma função dentro da classe, graças ao AddressOf de suporte da TB aqui. A última parte importante da configuração é que precisamos nos registrar para os eventos que queremos; Todos eles são entregues como mensagens PBT_POWERSETTINGCHANGE . Precisamos manter uma alça para cada uma como uma variável de classe, mas o registro é direto; tbshelllib fornece a API e GUIDS:
Private Function RegisterEvents() As Boolean
m_hEventM = RegisterPowerSettingNotification(m_hWnd, GUID_SESSION_DISPLAY_STATUS, DEVICE_NOTIFY_WINDOW_HANDLE)
m_hEventP = RegisterPowerSettingNotification(m_hWnd, GUID_SESSION_USER_PRESENCE, DEVICE_NOTIFY_WINDOW_HANDLE)
m_hEventL = RegisterPowerSettingNotification(m_hWnd, GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE)
If m_hEventM Then Return True
End Function
Como já mencionado, eles são entregues pelo UMSG WM_POWERBROADCAST com wparam PBT_POWERSETTINGCHANGE ; Sabemos que, no MSDN, o lparam aponta para um POWERBROADCAST_SETTING udt. Mas nós encontramos um problema:
typedef struct {
GUID PowerSetting;
DWORD DataLength;
UCHAR Data[1];
} POWERBROADCAST_SETTING, *PPOWERBROADCAST_SETTING;
Essa é uma matriz no estilo C, não um SAFEARRAY ; Os dados seguem imediatamente na memória, onde as matrizes de comprimento variável na TB (atualmente) podem ser apenas um SAFEARRAY com uma estrutura totalmente diferente. A solução que tbshelllib usa é um pouco desajeitada;
[ Description ("WARNING: You can't use this directly due to the SAFEARRAY. To receive, fill the first 20 bytes, then the data in the array. To send, create a byte buffer excluding the safearray member.") ]
Public Type POWERBROADCAST_SETTING
PowerSetting As UUID
DataLength As Long
Data() As Byte
End Type
A idéia é copiar a peça fixa e, em seguida, usar o comprimento para redim a peça variável e fazer uma cópia separada no Varptr (dados (0)). Esta aula exige um atalho; Como estamos trabalhando apenas com um único DWORD de 4 bytes para as propriedades em que estamos interessados, é assim:
Case WM_POWERBROADCAST
If wParam = PBT_POWERSETTINGCHANGE Then
Dim pSetting As POWERBROADCAST_SETTING
CopyMemory pSetting, ByVal lParam, 20
If IsEqualGUID(pSetting.PowerSetting, GUID_SESSION_DISPLAY_STATUS) Then
Dim pState As MONITOR_DISPLAY_STATE
CopyMemory pState, ByVal PointerAdd(lParam, 20), 4
Select Case pState
Case PowerMonitorOff
If (m_Mask And CPMEN_MONITOROFF) Then RaiseEvent MonitorOff()
Copiamos a peça fixa para que possamos verificar o GUID para qual evento é desta vez, mas basta copiar 4 bytes começando a partir do deslocamento de Data (um GUID é de 16 bytes, mais o 4 byte de comprimento, = 20) diretamente para uma variável que representa a enumeração de possíveis valores. Tudo além da última linha é fornecido pelo Tbshelllib, incluindo o PointerAdd genérico, que executa com segurança uma adição não assinada. Então, apenas verificamos se o chamador deseja o evento e o levantamos. Surpreendentemente, ligar para RaiseEvent de fora de tópicos como esse funcionou sem manuseio especial e ainda não caiu, e deixei a aula correndo por trechos de mais de 6 horas, com muitos eventos sendo criados.
Observação
Você receberá o status atual quando se registrar pela primeira vez para eventos. Portanto, ele enviará 'Monitor em', mesmo que o monitor não tenha sido ativado.
A classe fornece um método Destroy() para desativar o monitoramento e se livrar da janela oculta. É necessário que você chame isso antes de tentar defini -lo para Nothing se o seu aplicativo planeja continuar funcionando (se sair, você pode deixar o sistema destruir tudo). O thread está em seu loop de mensagem, para que não saia das saídas do loop da mensagem; portanto, para fazer isso, a janela precisa ser destruída. Você não pode chamar DestroyWindow na janela em um tópico diferente, então o que você faz é enviar WM_CLOSE com PostMessage , e a janela se destrói, e não registra os eventos:
Case WM_CLOSE
DestroyWindow m_hWnd
Case WM_DESTROY
UnregisterEvents
PostQuitMessage 0
Além disso, damos ao tópico alguns segundos para desligar e não registre nossa classe de janela personalizada. Depois disso, tudo está limpo e a aula em si está pronta para ser destruída.
Public Sub Destroy()
If m_hWnd Then PostMessageW(m_hWnd, WM_CLOSE, 0, ByVal 0)
Dim lRet As WaitForObjOutcomes = WaitForSingleObject(m_hThread, 5000)
Debug.Print "Wait outcome=" & lRet
Dim hr As Long = UnregisterClassW(StrPtr(wndClass), m_hInst)
Debug.Print "Unregister hr=" & hr & ", lastErr=" & Err.LastDllError
End Sub
E isso é tudo o que há para isso! É tudo bem direto, mas vale a pena escrever, já que muitos são novos em toda a coisa de 'Easy Multithreading'.