|
Steuerelemente, die dynamische Sammlungen anderer Elemente enthalten, wie etwa eine ListBox, reagieren auf jede Änderung der Sammlung mit einer Neudarstellung. Sollen viele Elemente unmittelbar nacheinander hinzugefügt, geändert oder entfernt werden (etwa in einer Schleife), flackert die Darstellung des Steuerelements nicht nur unschön, sondern die Neudarstellung kostet unnötig viel Zeit. Im Prinzip würde es genügen, nach der letzten Aktion eine Aktualisierung der Darstellung vorzunehmen. Es müsste eine Möglichkeit geben, die Aktualisierung eines Steuerelements bzw. eines Fensters vorübergehend zu unterbinden - es "einzufrieren".
Tatsächlich bietet Windows zwei Möglichkeiten an. Zum einen die API-Funktion LockWindowUpdate und zum anderen die Nachricht WM_SETREDRAW, die über die API-Funktion SendMessage an ein Fenster gesendet werden kann - näheres dazu finden Sie im Artikel "Flackerfreiheit und mehr Rasanz".
Während Sie per LockWindowUpdate immer nur ein einziges Fenster einfrieren und eingefroren halten können, erlaubt Ihnen die Nachricht WM_SETREDRAW, jedes beliebige Fenster (die meisten Steuerelemente sind ja Fenster) zu jedem beliebigen Zeitpunkt beliebig lange einzufrieren. Falls Sie jedoch von dieser Möglichkeit intensiveren Gebrauch machen sollten, besteht die Gefahr, die Übersicht darüber zu verlieren, welches Fenster nun gerade eingefroren ist und welches nicht. Ebenso haben Sie wenig Kontrolle über die gegebenenfalls von verschiedenen Stellen in Ihrem Code aus erfolgten Einfrierungen.
Das Modul modLockedWindows hilft Ihnen, die Übersicht zu behalten. Es merkt sich in einer Collection, welche Fenster eingefroren sind. Darüber hinaus merkt es sich sogar, wie oft die Anweisung zum Einfrieren eines Fensters erfolgt ist, und hebt die Einfrierung erst wieder auf, wenn die gleiche Anzahl zur Aufhebung erfolgt ist.
Dazu werden Zähler für jedes Fenster in einer Collection abgelegt. Als Schlüssel dient das explizit in einen String konvertierte Fenster-Handle. Ein Einfrieren erfolgt nun über einen Aufruf der Prozedur LockWindow mit dem betreffenden Fenster-Handle als Parameter.
Private Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, _
ByVal wParam As Long, lParam As Long) As Long
Private Const WM_SETREDRAW = &HB
Private mLockedWindows As Collection
Public Sub LockWindow(ByVal hWnd As Long)
Dim nLockCount As Long
If mLockedWindows Is Nothing Then
Set mLockedWindows = New Collection
End If
On Error Resume Next
nLockCount = mLockedWindows(CStr(hWnd))
If Err.Number Then
nLockCount = 1
Else
mLockedWindows.Remove CStr(hWnd)
nLockCount = nLockCount + 1
End If
mLockedWindows.Add nLockCount, CStr(hWnd)
SendMessage hWnd, WM_SETREDRAW, 0, 0
End Sub
Da ein in eine Collection aufgenommener Wert nicht verändert werden kann, muss der Zähler vorübergehend aus der Collection entfernt, dann erhöht und anschließend wieder eingefügt werden. Falls der Zähler zu einem Fenster-Handle noch nicht in der Collection enthalten gewesen ist, wird er mit dem Wert 1 erstmals aufgenommen. Falls die Collection noch nicht instanziert gewesen sein sollte, wird sie frisch instanziert.
Zum "Auftauen" wird in der Prozedur UnlockWindow der Zähler zum betreffenden Fenster ermittelt und um 1 erniedrigt. Verbleibt der Zählerwert größer
wird der neue Zählerwert wieder in die Collection eingefügt, falls nicht im optionalen Parameter Forced der Wert True übergeben wurde. Ist dagegen der Wert des Zählers auf 0 zurück gegangen, oder wurde in Forced True übergeben, wird er nicht wieder eingefügt, während die Sperrung des Fensters aufgehoben wird. Sollte die Collection danach leer sein, wird sie gelöscht.
Public Enum LockWindowErrorConstants
lwErrNoLockedWindow = 30001
lwErrWindowNotLocked = 30002
End Enum
Public Sub UnlockWindow(ByVal hWnd As Long, _
Optional ByVal Forced As Boolean)
Dim nLockCount As Long
If mLockedWindows Is Nothing Then
Err.Raise lwErrNoLockedWindow, "UnlockWindow"
Else
On Error Resume Next
nLockCount = mLockedWindows(CStr(hWnd))
If Err.Number Then
Err.Raise lwErrWindowNotLocked, "UnlockWindow"
Else
mLockedWindows.Remove CStr(hWnd)
nLockCount = nLockCount - 1
If Not Forced Then
If nLockCount > 0 Then
mLockedWindows.Add nLockCount, CStr(hWnd)
End If
End If
If nLockCount = 0 Then
SendMessage hWnd, WM_SETREDRAW, 1, 0
End If
End If
If mLockedWindows.Count = 0 Then
Set mLockedWindows = Nothing
End If
End If
End Sub
Auf ähnliche Weise können Sie über die Funktion IsWindowLocked abfragen, ob ein Fenster aktuelle eingefroren ist.
Public Function IsWindowLocked(ByVal hWnd As Long) As Boolean
Dim nLockCount As Long
If mLockedWindows Is Nothing Then
Err.Raise lwErrNoLockedWindow, "IsWindowLocked"
Else
On Error Resume Next
nLockCount = mLockedWindows(CStr(hWnd))
If Err.Number = 0 Then
IsWindowLocked = True
End If
End If
End Function
Mit der Prozedur UnlockAllWindows tauen Sie schließlich alle eingefrorenen Fenster auf einen Schlag wieder auf.
Public Sub UnlockAllWindows()
Dim l As Long
If Not (mLockedWindows Is Nothing) Then
For l = 1 To mLockedWindows.Count
SendMessage CLng(mLockedWindows(l)), WM_SETREDRAW, 1, 0
Next 'l
Set mLockedWindows = Nothing
End If
End Sub
|