Eine in sich geschlossene, getroffene Klasse, um die Benutzerpräsenz auf verschiedene Weise zu überwachen
Dies ist eine einfache Klasse, die 3 Ereignisse überwacht: den Monitorstatus (aus, auf oder verdoppelt), die "Benutzer präsentierte" Nachricht des Systems und, wenn Sie sich auf einem Laptop befinden, den Deckelstatus (offen, schließen). Dies geschieht in einer völlig selbsthaltigen Klasse- die Komplikation entsteht damit, weil diese Ereignisse in Form einer WM_POWERBROADCAST -Nachricht gesendet werden, um ein Fenster zu empfangen, um es zu empfangen. Aber ich wollte die Klasse nicht auf grafische Apps beschränken und nur ein Formular HWND unterklassen, obwohl die Demo -App aus Gründen der Vollständigkeit zeigt, wie es auch so geht. Die Klasse erstellt also ihr eigenes, benutzerdefiniertes verstecktes Fenster in einem separaten Thread, um zu vermeiden, dass die Nachrichtenschleife die Nachrichtenschleife des Formulars blockiert, falls vorhanden.
Die Klasse nutzt die parametrisierten Konstruktoren von TwinBasic, um die verfügbaren Argumente direkt im neuen Schlüsselwort anzugeben. Hier sind dies optionale Argumente, für die Ereignisse zu erhöhen und die Hintern zu registrieren zu registrieren ( App.hInstance 99% der Zeit; Wenn Sie es weglassen, wird GetModuleHandleW() verwendet, was den gleichen Wert wie App.hInstance zurückgibt, ohne eine Abhängigkeit von Gewinnern zu erstellen). Dann kommen wir zu einer der großen Freuden von TB: CreateThread treadhread ohne aufwändige Hacks neccessary, so cool wie in VB6. Der ThreadProc selbst ruft einfach den Rest des Codes auf.
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 und CreateWindowEx werden in nur einer normalen Routine verwendet, um ein verstecktes Fenster zu erstellen, wobei die WndProc dank der unterstützenden AddressOf von TB hier eine Funktion in der Klasse ist. Der letzte wichtige Teil von Setup ist, dass wir uns für die gewünschten Ereignisse registrieren müssen. Sie sind alle als PBT_POWERSETTINGCHANGE -Nachrichten geliefert. Wir müssen als Klassenvariable für jeden ein Griff behalten, aber die Registrierung ist unkompliziert. TBSHELLLIB liefert die API und Richtlinien:
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
Wie bereits erwähnt, werden sie von UMSG WM_POWERBROADCAST mit WPARAM PBT_POWERSETTINGCHANGE geliefert. Wir wissen von msdn das lparam dann auf eine POWERBROADCAST_SETTING udt. Aber wir stoßen auf ein Problem:
typedef struct {
GUID PowerSetting;
DWORD DataLength;
UCHAR Data[1];
} POWERBROADCAST_SETTING, *PPOWERBROADCAST_SETTING;
Das ist ein Array im C-Stil, kein SAFEARRAY ; Die Daten folgen unmittelbar im Speicher, wobei Arrays der variablen Länge in TB (derzeit) nur ein SAFEARRAY mit einer völlig anderen Struktur sein können. Die Lösung, die TBShelllib verwendet, ist etwas klobig.
[ 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
Die Idee ist, den festen Teil zu kopieren und dann die Länge zu verwenden, um den variablen Teil zu reduzieren und eine separate Kopie in VAGTR (Daten (0)) durchzuführen. Diese Klasse nimmt jedoch eine Abkürzung; Da wir nur mit einem einzigen 4-Byte-DWORD für die Eigenschaften arbeiten, an denen wir interessiert sind, sieht es so aus:
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()
Wir kopieren den festen Teil, damit wir die Richtlinie überprüfen können, für welche Ereignisse es diesmal ist. Anschließend kopieren Sie jedoch nur 4 Bytes, beginnend mit dem Versatz von Data (eine Richtlinie sind 16 Bytes sowie die 4 Byte lang, = 20) direkt zu einer Variablen, die die Aufwand möglicher Werte darstellt. Alles außer der letzten Zeile wird von TBShelllib bereitgestellt, einschließlich der generischen PointerAdd , die sicher eine nicht signierte Ergänzung durchführt. Dann prüfen wir nur, ob der Anrufer die Veranstaltung will, und erheben sie. Überraschenderweise funktionierte der Raufel von Raissevent von Out Out Thread wie dieser ohne besonderes Handling und ist noch nicht abgestürzt. Ich habe die Klasse mehr als 6 Stunden lang gelassen, wobei viele Ereignisse angehoben wurden.
Notiz
Sie erhalten den aktuellen Status, wenn Sie sich zum ersten Mal für Ereignisse registrieren. Daher wird es "Monitor an" senden, obwohl der Monitor nicht nur eingeschaltet wurde.
Die Klasse bietet eine Destroy() -Methode, um die Überwachung auszuschalten und das versteckte Fenster zu beseitigen. Es ist erforderlich, dass Sie dies anrufen, bevor Sie versuchen, es auf Nothing zu setzen, wenn Ihre App weiter ausgeführt werden soll (wenn Sie beenden, können Sie das System alles zerstören lassen). Der Thread befindet sich in seiner Nachrichtenschleife, sodass er nicht beendet wird, dass die Nachrichtenschleife beendet. Daher muss das Fenster zerstört werden. In einem anderen Thread können Sie DestroyWindow auf dem Fenster nicht anrufen. Senden Sie also stattdessen WM_CLOSE mit PostMessage , und das Fenster zerstört sich selbst und unregistriert die Ereignisse:
Case WM_CLOSE
DestroyWindow m_hWnd
Case WM_DESTROY
UnregisterEvents
PostQuitMessage 0
Außerdem geben wir dem Thread ein paar Sekunden Zeit, um herunterzufahren, und registrieren dann unsere benutzerdefinierte Fensterklasse. Danach ist alles aufgeräumt und die Klasse selbst ist bereit, zerstört zu werden.
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
Und das ist alles, was es gibt! Es ist alles ziemlich unkompliziert, aber es lohnt sich, aufzuschreiben, da viele neu im ganzen "Easy Multithreading" -Ding sind.