Paski postępu w MS Access
MS Access w trakcie naprzemiennego uruchamiania Accessa 2007 i Accessa 2010 prezentuje paski nowszego typu
Nowsze paski postępu MS Access 2007 i 2010
Funkcja SysCmd obiektu Application umożliwia wyświetlenie wskaźnika zaawansowania operacji w dolnej części okna MS Access gdzie znajduje się pasek stanu, który służy do wyświetlania różnych informacji na temat danych w formularzu. Na przykład może zawierać opis pola, komunikaty o błędach, ostrzeżenia lub wspomniany wcześniej pasek postępu.
Private Sub testProgressBar() Dim i As Long Const cForTo As Long = 5000 'pokaż pasek stanu Application.CommandBars("Status Bar").Visible = True ' zainicjuj pasek postępu Application.SysCmd acSysCmdInitMeter, "MS Access 2016", cForTo For i = 0 To cForTo ' aktualizuj pasek postępu Application.SysCmd acSysCmdUpdateMeter, i DoEvents: DoEvents: DoEvents Next ' usuń pasek postępu Application.SysCmd acSysCmdRemoveMeter End Sub
Po wywołaniu procedury testProgressBar() w różnych wersjach MS Access, możemy zobaczyć jak zmieniał się pasek postępu i jego położenie w kolejnych odsłonach MS Access.
Wygląd paska postępu w różnych wersjach MS Access
Własny pasek postępu.
Minimalistyczny sposób utworzenie własnego paska postępu polega stopniowym zwiększaniu szerokości pojedynczej „kolorowej” etykiety w miarę zaawansowania wykonania operacji. Np. wykonanie 1% zadania powoduje zwiększenie szerokości 0,01*Maksymalna_szerokość_etykiety. Możemy w trakcie wykonywania zmieniać tekst etykiety na: „Wykonano x% operacji”, lub „Wykonano x z zaplanowanych Y operacji. Jeżeli zdecydujemy się na takie rozwiązanie, to etykietą musimy podzielić na początkową, (o stałej szerokości część) opisową i zwiększać rozmiar etykiety proporcjonalnie do zaawansowania wykonania operacji.
Ponieważ pasek postępu może nam być potrzebny wielokrotnie, to by nie powielać kodu w każdym formularzu wykorzystującym pasek postępu spróbujmy utworzyć moduł klasy, za pomocą którego będziemy mogli sterować podstawowymi właściwościami i metodami formularza spełniającego rolę paska postępu.
Moduł klasy
Moduł klasy jest specjalnym typem modułu standardowego. Jest on definicją obiektu i pozwala na tworzenie nowych egzemplarzy obiektów opartych na kodzie tego moduły klasy. Wszystkie właściwości, metody i zdarzenia, dostępne są w module klasy w formie procedur oznaczanych słowem kluczowym Public. Wszystkie procedury i funkcje który nie powinny być dostępne na zewnątrz modułu, powinien być oznaczony słowem kluczowym Private.
Wstawić moduł klasy
Aby wstawić do naszego projektu moduł klasy należy w oknie edytora VBA wybrać z menu pozycję „Insert” i następnie kliknąć na podmenu „Class Module”,
lub po kliknięciu prawym przyciskiem myszy w oknie Eksplorator projektu „Project” wybrać z menu podręcznego pozycję „Insert”, a potem pozycję „Class Module”.
Następnie w oknie właściwości nowo wstawionego modułu klasy „Properties Class1” nadajemy właściwości Name wartość clsProgBar. Od tej pory będziemy się mogli odwoływać do naszej klasy za pomocą jej nazwy clsProgBar.
Projekt modalnego formularza tworzącego pasek postępu.
Formularz zawiera 3 formanty typu Label, etykieta lblProgBack będąca tłem nad którą znajduje się właściwy pasek postępu lblProg. Poniżej znajduje się etykieta opisowa lblProgDscr. Ramkę wokół etykiet tworzy formant typu Rectangle o nazwie rctProgBorder.
Budowa i niektóre właściwości formularza tworzącego pasek postępu.
Option Compare Database Option Explicit ' odwołania do formularza formantów paska postępu Private m_frm As Access.Form Private m_rctProg As Access.Rectangle Private m_lblBack As Access.Label Private m_lblProg As Access.Label Private m_lblDscr As Access.Label ' tekst informacyjny etykiety opisowej Private m_sPrefix As String 'ilość przewidywanych operacji Private m_lMaxCount As Long ' przypisz do zmiennych odwołania do formantów tworzących pasek postępu Private Sub Class_Initialize() Set m_frm = New Form_frmProgressBar DoCmd.Restore With m_frm Set m_rctProg = .Controls("rctProgBorder") Set m_lblBack = .Controls("lblProgBack") Set m_lblProg = .Controls("lblProg") Set m_lblDscr = .Controls("lblProgDscr") End With ' kolorowy pasek postępu m_rctProg.BackColor = 1 ' przezroczyste etykiety opisowe m_lblBack.BackColor = 0 m_lblDscr.BackColor = 0 ' tekst na początku etykiety opisowej m_sPrefix = "Operacja: " End Sub Private Sub Class_Terminate() ' zniszcz zmienne obiektowe Set m_lblDscr = Nothing Set m_lblProg = Nothing Set m_lblBack = Nothing Set m_rctProg = Nothing ' zamknij formularz paska, bo kolejne ' wywołania powodują przesunięcie paska DoCmd.Close acForm, m_frm.Name Set m_frm = Nothing End Sub ' przesuwa i zmienia rozmiar formularza i formantów tworzących pasek postępu Public Sub pgMoveSize( _ Optional ByVal lLeft As Long = -1, _ Optional ByVal lTop As Long = -1, _ Optional ByVal lWidth As Long = -1) ' zmień rozmiar formularza i dostosuj ' wielkość formantów paska postępu If lWidth > 0 Then lWidth = CLng(lWidth) m_frm.InsideWidth = lWidth - 25 m_rctProg.Width = lWidth - 100 m_lblBack.Width = lWidth - 200 m_lblDscr.Width = lWidth - 200 End If ' ustaw fokus na formularzu paska postępu DoCmd.SelectObject acForm, m_frm.Name, False ' przesuń formularz paska postępu If (lLeft > 0) And (lTop > 0) Then DoCmd.MoveSize lLeft, lTop ElseIf (lLeft > 0) Then DoCmd.MoveSize lLeft ElseIf (lTop > 0) Then DoCmd.MoveSize , lTop End If End Sub ' aktualizacja paska postępu w/m ilości wykonanych operacji Public Sub pgUpdateProgBar(ByVal lOperation As Long) Dim lPercent As Long Dim lWidthProgBar As Long ' oblicz % wykonania lPercent = (lOperation / m_lMaxCount) * 100 With m_frm ' ustaw teksty "przenikających się" etykiet m_lblProg.Caption = "Wykonano " & lPercent & "% zadania" m_lblBack.Caption = "Wykonano " & lPercent & "% zadania" ' oblicz szerokość paska postępu lWidthProgBar = m_lblBack.Width * (lPercent / 100) ' dla ostatnich operacji zmniejsz maksymalną szerokość ' paska postępu o 60 twipów (jest za długi) If lWidthProgBar > m_lblBack.Width - 60 Then m_lblProg.Width = m_lblBack.Width - 60 Else m_lblProg.Width = lWidthProgBar End If ' ustaw sformatowany tekst paska postępu m_lblDscr.Caption = prpPrefix & _ Format$(CStr(lOperation), "### ### ### ###") & _ " z " & Format$(m_lMaxCount, "### ### ### ###") ' Czasami występuje nieprzyjemne migotanie paska postępu. ' Możemy na czas przemalowania formularza zablokować odświeżanie ' okien kosztem o wiele dłuższego czasu działania paska postępu 'On Error Resume Next ' Application.Echo False DoEvents ' Application.Echo True 'On Error GoTo 0 End With End Sub ' maksymalna ilość operacji Public Property Let prpMaxCount(prpMaxCount As Long) If prpMaxCount = 0 Then prpMaxCount = 1 m_lMaxCount = Abs(prpMaxCount) End Property Public Property Get prpMaxCount() As Long prpMaxCount = m_lMaxCount End Property ' szerokość formularza paska postępu Public Property Get prpProgBarWidth() As Long prpProgBarWidth = m_frm.InsideWidth End Property ' tekst początkowy na pasku postępu Public Property Let prpPrefix(sPrefix As String) m_sPrefix = sPrefix End Property Public Property Get prpPrefix() As String prpPrefix = m_sPrefix End Property ' kolor tekstu tła paska postępu Public Property Let prpDetailColorBack(lCol As Long) 'm_frm.Section(acDetail).BackStyle = 1 m_frm.Section(acDetail).BackColor = lCol End Property ' kolor tekstu tła paska postępu Public Property Let prpBackColorBack(lCol As Long) m_lblBack.BackStyle = 1 m_lblBack.BackColor = lCol End Property ' kolor tekstu tła paska postępu Public Property Let prpBackColorText(lCol As Long) m_lblBack.ForeColor = lCol End Property ' kolor paska postępu Public Property Let prpProgColorBack(lCol As Long) m_lblProg.BackColor = lCol m_lblProg.BorderColor = lCol End Property ' kolor tekstu paska postępu Public Property Let prpProgColorText(lCol As Long) m_lblProg.ForeColor = lCol End Property ' kolor tekstu opisu operacji Public Property Let prpDscrColorText(lCol As Long) m_lblDscr.ForeColor = lCol End Property ' tekstu opisu operacji Public Property Let prpDscrColorBack(lCol As Long) m_lblDscr.BackStyle = 1 m_lblDscr.BackColor = lCol End Property ' wysokość okna formularza paska postępu Public Property Get prpProgBarHeight() As Long prpProgBarHeight = m_frm.WindowHeight End Property
Poniżej przykładowe wywołanie paska postępu z wykorzystaniem nowo utworzonej klasy clsProgressBar.
Private Sub SimpleProgressBar() Dim clsPgBar As clsProgressBar Dim i As Long Set clsPgBar = New clsProgressBar With clsPgBar ' ustaw domyślny rozmiar i położenie paska .pgMoveSize ' przekaż do klasy clsPgBar ilość powtórzeń pętli .prpMaxCount = 10000 ' wykonaj prostą pętlę 10 000 razy For i = 0 To 10000 ' tutaj wykonuj swoje instrukcje ! ' ... ' wydłuż czas działania pętli DoEvents: DoEvents: DoEvents .pgUpdateProgBar i Next End With Set clsPgBar = Nothing End Sub
Całkowity czas wykonania procedury SimpleProgBar() wraz z aktualizacją paska postępu w każdym kroku
pętli For ... Next (10 000 kroków pętli) zajmuje ok. 15 sekund .
Bez aktualizacji paska postępu czas wykonania procedury SimpleProgBar() wynosi ok. 8 sek.
ale na ekranie nic sensownego nie zobaczymy, poza paskiem po pierwszej aktualizacji (1%).
Wizualnie tekst górnego paska postępu pokazującego procentowe wykonanie ulega zmianie co 1%,
a dolny opisowy pasek zmienia się z każdym krokiem pętli. Prawdopodobnie dlatego
bardzo nieprzyjemnym efektem ubocznym jest intensywne „migotanie” elementów paska postępu.
Aby zminimalizować nieprzyjemne „migotanie” okien paska, możemy zastosować metodę
Echo,
obiektu
Application, by wyłączyć odświeżanie ekranu.
Application.Echo False DoEvents Application.Echo True
Włączanie i wyłączanie odświeżania ekranu spowodowało, że „migotanie” okien paska jest prawie niezauważalne,
Ale „coś za coś”. Czas wykonania procedury SimpleProgBar() wydłużył się dość znacznie:
- 32-bit Access 2007: z 13 sek. do 240 sek.
- 64-bit Access 2010: z 12 sek. do 180 sek.
- 64-bit Access 2016: z 10 sek. do 60 sek.
Podstawowe pytanie:
Po co aktualizować pasek postępu po wykonaniu każdej operacji (tut. 10 000 razy), kiedy wizualnie widoczna jest zmiana po wykonaniu 1% operacji. Jeżeli tak, to spróbujmy aktualizować pasek co 1%. Praktycznie (wizualnie), bez względu na ilość operacji, tekst paska postępu będzie się zmieniał tylko 100 razy (tak jak zmienia się procentowe zaawansowanie przebiegu operacji).
' aktualizuj co lModUpdate wywołań
lModUpdate = CLng(cRepeat * cPercentUpdate)
If i Mod lModUpdate = 0 Then
.pgUpdateProgBar i
End If
Jeśli tak, to aktualizujmy pasek postępu co 1% wykonanych operacji:
Private Sub ProgressBar() Dim clsPgBar As clsProgressBar Dim lModUpdate As Long Dim i As Long Const cRepeat As Long = 10000 Const cPercentUpdate As Single = 0.01 ' co ile operacji ma być aktualizowany pasek postępu lModUpdate = CLng(cRepeat * cPercentUpdate) Set clsPgBar = New clsProgressBar With clsPgBar ' ustaw domyślny rozmiar i położenie paska .pgMoveSize ' przekaż do klasy clsPgBar ilość powtórzeń pętli .prpMaxCount = cRepeat ' wykonaj prostą pętlę 10 000 razy For i = 0 To cRepeat ' tutaj wykonuj swoje instrukcje ! ' ... ' wydłuż czas działania pętli DoEvents: DoEvents: DoEvents ' aktualizuj co lModUpdate wywołań If i Mod lModUpdate = 0 Then .pgUpdateProgBar i End If Next End With Set clsPgBar = Nothing End Sub
Teraz jest o wiele lepiej. Pasek postępu prawie przestał „migotać”, a włączanie i wyłączanie odświeżania ekranu nie powoduje zbyt dużego wydłużenia czasu działania pętli For ... Next. Poniżej orientacyjny czas wykonywania procedury SimpleProgBar()
- 32-bit Access 2007: z 4,4 sek. do 6,3 sek.
- 64-bit Access 2010: z 3,2 sek. do 4,8 sek.
- 64-bit Access 2016: z 2,9 sek. do 3,5 sek.
W tym przypadku w wywołaniu procedury ProgressBar() pasek postępu jest aktualizowany 100 razy (zamiast 10 000 razy), co 1% kroków pętli. Czas wykonywania procedury wynosi ok. ~5 sekundy, co jest zbliżonym wynikiem do wywołania procedury bez aktualizacji paska postępu. Dodatkową korzyścią jest brak tzw. „migotania” okien paska postępu.
Wygląd paska postępu w trakcie pracy.
Kolorowy pasek postępu
Jeżeli nie podoba nam się szary wygląd paska postępu, możemy wykorzystać metody i właściwości klasy clsProgressBar by zmienić kolorystykę paska postępu. Przykładowe kolory zostały wybrane z użyciem zdefiniowanych stałych i nie mają nic wspólnego z estetyką ☺
#If VBA7 Then Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) #Else Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) #End If Private Sub ProgressBarColor() Dim clsPgBar As clsProgressBar ' referencyjna zmienna obiektowa do klasy Dim lModUpdate As Long ' numer operacji aktualizowania paska postępu Dim i As Long ' licznik Const cSleep As Long = 10 ' wydłużenie czasu działania pętli w milisekundach Const cRepeat As Long = 100 ' ilość powtórzeń Const cPercentUpdate As Single = 0.01 ' częstotliwość odświeżania paska Set clsPgBar = New clsProgressBar With clsPgBar ' przekaż do klasy clsPgBar ilość powtórzeń pętli .prpMaxCount = cRepeat ' przesuń pasek postępu pod formularzem i wycentruj go w poziomie .pgMoveSize Me.WindowLeft + (Me.InsideWidth - .prpProgBarWidth) \ 1, _ Me.WindowTop + Me.WindowHeight + 50, 9400 ' zmień kolor etykiety tła i tekstu .prpBackColorText = vbRed .prpBackColorBack = vbGreen ' kolor tła paska postępu i tekstu paska .prpProgColorText = vbYellow .prpProgColorBack = vbBlue ' zmień kolor tła etykiety opisowej i tekstu .prpDscrColorText = vbBlue .prpDscrColorBack = vbCyan ' zmień kolor tła sekcji Szczegóły .prpDetailColorBack = RGB(255, 0, 0) ' operacja aktualizowania paska postępu lModUpdate = CLng(cRepeat * cPercentUpdate) ' wykonaj prostą pętlę cRepeat razy For i = 0 To cRepeat ' tutaj wykonuj swoje instrukcje ! ' ... ' wydłuż czas działania pętli Call Sleep(cSleep) ' aktualizuj co lModUpdate wywołań If i Mod lModUpdate = 0 Then .pgUpdateProgBar i End If Next End With Set clsPgBar = Nothing End Sub
Zmiana kolorystyki paska postępu i jego szerokości ☺
Przesuwany pasek postępu (góra, środek, dół) formularza wywołującego.
#If VBA7 Then Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) #Else Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) #End If ' miejsca ustawienia paska postępu Private Const m_TOP As Long = 1 Private Const m_MIDDLE As Long = 2 Private Const m_BOTTOM As Long = 3 Private Sub ProgressBarMoved(clsProgBar As clsProgressBar, lPosition As Long) Dim lModUpdate As Long ' numer operacji aktualizowania paska postępu Dim lAddLeft As Long ' Dim lAddTop As Long ' poprawki na rozmiar paska postępu Dim lAddWidth As Long ' Dim i As Long ' licznik Const cSleep As Long = 10 ' wydłużenie czasu działania pętli w milisekundach Const cRepeat As Long = 100 ' ilość powtórzeń Const cPercentUpdate As Single = 0.01 ' częstotliwość odświeżania paska With clsProgBar ' ustaw ilość powtórzeń pętli .prpMaxCount = cRepeat .prpPrefix = "Operacja:" ' poprawki na wymiary i położenie paska postępu ' gdyż wersje MS Access powyżej 2010 mają jednopikselowe obramowanie If Eval(Application.Version) > 14 Then lAddLeft = 15 lAddTop = 25 lAddWidth = 0 Else lAddLeft = 65 lAddTop = 50 lAddWidth = 50 End If ' sprawdź, gdzie ma być wyświetlony pasek postępu Select Case lPosition Case m_TOP ' przesuń pasek postępu na górę formularza i zmień jego szerokość .pgMoveSize Me.WindowLeft + lAddLeft, _ Me.WindowTop + lAddTop + 0, _ Me.InsideWidth + lAddWidth Case m_MIDDLE ' przesuń pasek postępu na środek wysokości formularza i zmień jego szerokość .pgMoveSize Me.WindowLeft + lAddLeft, _ Me.WindowTop + (Me.WindowHeight - .prpProgBarHeight) / 2 + lAddTop, _ Me.InsideWidth + lAddWidth Case m_BOTTOM ' przesuń pasek postępu na dół formularza i zmień jego szerokość .pgMoveSize Me.WindowLeft + lAddLeft, _ Me.WindowTop + (Me.WindowHeight - .prpProgBarHeight) - lAddTop, _ Me.InsideWidth + lAddWidth Case Else ' bez zmiany położenia .pgMoveSize End Select ' oblicz co ile wykonanych operacji ma być aktualizowany pasek postępu lModUpdate = CLng(cRepeat * cPercentUpdate) ' wykonaj prostą pętlę cRepeat razy For i = 0 To cRepeat ' tutaj wykonuj swoje instrukcje ! ' ... ' wydłuż czas działania pętli Call Sleep(cSleep) ' aktualizuj co lModUpdate wywołań If i Mod lModUpdate = 0 Then .pgUpdateProgBar i End If Next End With End Sub
Pasek postępu ustawiony na środku wysokości formularza wywołującego pasek postępu.
'przykładowe wywołanie Private Sub btnProgressBar_Click() Dim clsPgBar As clsProgressBar ' pasek postępu na górze formularza Set clsPgBar = New clsProgressBar Call ProgressBarMoved(clsPgBar, m_TOP) Set clsPgBar = Nothing ' pasek postępu na środku wysokości formularza Set clsPgBar = New clsProgressBar Call ProgressBarMoved(clsPgBar, m_MIDDLE) Set clsPgBar = Nothing ' kolorowy pasek postępu na dole formularza Set clsPgBar = New clsProgressBar ' pokoloruj pasek dolny pasek postępu '==================================== With clsPgBar ' zmień kolor etykiety tła i tekstu .prpBackColorText = vbRed .prpBackColorBack = vbBlue ' zmień kolor tła paska postępu i tekstu paska .prpProgColorText = vbYellow .prpProgColorBack = vbGreen ' zmień kolor tła etykiety opisowej i tekstu .prpDscrColorText = vbMagenta .prpDscrColorBack = vbCyan ' zmień kolor tła sekcji Szczegóły .prpDetailColorBack = RGB(125, 0, 125) End With Call ProgressBarMoved(clsPgBar, m_BOTTOM) Set clsPgBar = Nothing ' mały pasek postępu poniżej dolnej krawędzi formularza Call ProgressBar ' kolorowy pasek postępu poniżej dolnej krawędzi formularza Call ProgressBarColor End Sub