Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

ProgressBar

[lekcja]
Czasem zdarza się, że nasz program ma do wykonania jakąś czasochłonną operację. Z zewnątrz objawia się to jego chwilowym "zawieszeniem się". Zwykle w takich sytuacjach user zaczyna się irytować i czasem może nawet zrezygnować z używania takiej aplikacji, a tego drugiego byśmy nie chcieli. Dość oczywistym rozwiązaniem problemu jest poinformowanie użytkownika o postępie pracy programu. Właśnie do takiego zadania została stworzona kontrolka ProgressBar. Pasek postępu jest na tyle czytelny, że powinien spacyfikować nawet takiego usera, który usilnie stara się nie czytać żadnych napisów w oknie programu:

ProgressBar (Windows 7)
ProgressBar (Windows 7)

Tworzenie ProgressBara

Tworzymy go tak, jak każdą inną kontrolkę. Małym utrudnieniem jest fakt, że jak wszystkie ciekawsze kontrolki, ProgressBar też należy do grupy Common Controls (Jeśli nie wiesz co to, zajrzyj tu: » Kurs WinAPI, C++ » KontrolkiToolbar, cz. 1 lekcja. Musimy ją zainicjalizować z pomocą funkcji InitCommonControlsEx:
C/C++
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof( INITCOMMONCONTROLSEX );
icc.dwICC = ICC_BAR_CLASSES; // toolbary, statusbary, tooltipy i oczywiście progressbary
InitCommonControlsEx( & icc );
Teraz można już brać się za tworzenie ProgressBara. Jako nazwę klasy wpiszemy stałą PROGRESS_CLASS:
C/C++
HWND hProgressBar = CreateWindowEx( 0, PROGRESS_CLASS, NULL, WS_CHILD | WS_VISIBLE,
10, 10, 200, 15, hwnd,( HMENU ) 200, hInstance, NULL );
Jeśli chcemy słuchać zaleceń Microsoftu, długość paska powinna wynosić pomiędzy 160 a 355 pikseli.

Tak utworzony ProgressBar wygląda beznadziejnie - jest podzielony na prostokątne segmenty, zamiast przesuwać się płynnie. Aby temu zaradzić można użyć dodatkowych stylów:

StylDziałanieWersja CommCtrl.lib
PBS_SMOOTHPasek jest jednolity, bez podziału na segmenty4.70
PBS_SMOOTHREVERSEJak wyżej, ale można go cofać, zachowując płynną animację6.0
PBS_VERTICALPasek jest pionowy, wypełnia się od dołu4.70
PBS_MARQUEEPasek pulsuje, nie podając żadnego konkretnego wskazania6.0

Wersja 6.0 biblioteki CommCtrl.lib jest obecna w systemie Windows XP i nowszych, więc właściwie na wszystkich używanych systemach. Jeśli jednak program ma działać na starszych komputerach, nie należy używać tych stylów.

Ustawianie zakresu wartości

Zanim zaczniemy używać ProgressBar, powinniśmy ustawić zakres liczb na jakich będzie operował. Ustawiamy zarówno wartość maksymalną, przy której pasek będzie cały zamalowany, jak również wartość minimalną, przy której będzie pusty. Wynika z tego, że może on pracować w dowolnym zakresie liczb, nie tylko od zera.

Aby ustawić zakres pozycji paska, wysyłamy komunikat PBM_SETRANGE. Żeby nie było zbyt prosto, ani intuicyjnie, zarówno minimalną jak i maksymalną wartość przekazujemy za pomocą parametru lParam. Pomoże nam w tym makro MAKELONG, które scala dwie liczby 16-bitowe do jednej 32-bitowej. Wynika z tego, że możemy przekazać liczby od 0 do 65535. Nie wolno nam użyć wParam, więc zostawimy go sam na sam z zerem:
C/C++
SendMessage( hProgressBar, PBM_SETRANGE, 0,( LPARAM ) MAKELONG( 0, 123 ) );
Funkcja zwraca lParam w przypadku powodzenia lub 0 jeśli się nie uda.

Warto jeszcze wiedzieć, że gdybyśmy przypadkiem chcieli ustawiać wartości procentowo, nie musimy zmieniać zakresu, który domyślnie jest ustawiony od 0 do 100.

Zmiana pozycji ProgressBara

Pasek postępu, który nie pokazuje postępu nie jest zbyt przydatny. Aby ustawić nową pozycję możemy użyć dwóch różnych komunikatów. Pierwszy z nich - PBM_SETPOS - ustawia kontrolce całkiem nową wartość, którą przekazujemy w parametrze wParam:
C/C++
SendMessage( hProgressBar, PBM_SETPOS,( WPARAM ) 17, 0 ); // ustawia wartość na 17
Wartość zwracana to poprzednia pozycja ProgressBara.

Drugi komunikat - PBM_DELTAPOS - dodaje do obecnej pozycji liczbę przekazaną w parametrze wParam:
C/C++
SendMessage( hProgressBar, PBM_DELTAPOS,( WPARAM ) 5, 0 ); // zwiększa wartość o 5
Ten komunikat również zwraca poprzednią pozycję.

Przy zmianie pozycji ProgressBara należy zawsze pilnować, żeby nie wykroczyć poza zakres, ustawiony komunikatem PBM_SETRANGE.

Praca krokowa

Jeśli wiemy, że nasz ProgressBar będzie za każdym razem przesuwał się o tą samą wielkość, możemy jeszcze trochę zautomatyzować ten proces, by się za bardzo nie zmęczyć. Na początku przyda się wysłać komunikat PBM_SETSTEP, który ustala wielkość tą, o jaki za każdym razem będzie przyrastała pozycja paska postępu. Wielkość kroku przekazujemy w parametrze wParam, mniej więcej w ten sposób:
C/C++
SendMessage( hProgressBar, PBM_SETSTEP,( WPARAM ) 13, 0 );
Jeśli wpiszemy 0, domyślnie krok ustawi się na 10.

Teraz, aby powiększyć PrograssBar o tę wielkość wystarczy wysłać komunikat PBM_SETSTEP:
C/C++
SendMessage( hProgressBar, PBM_STEPIT, 0, 0 );

ProgressBar ze stylem PBS_MARQUEE

Styl PBS_MARQUEE jest przeznaczony do sytuacji, kiedy nie wiemy ile pracy zastało do wykonania programowi. Jak to możliwe, żeby na pasku postępu pokazać, że nie znamy czasu do końca? Właśnie tak:

ProgressBar ze stylem PBS_MARQUEE (Windows 7)
ProgressBar ze stylem PBS_MARQUEE (Windows 7)

Taki ProgressBar ma wyłącznie informować, że program pracuje, a nie zawiesił się. Po uruchomieniu paska samoczynnie przesuwa się on jednostajnie w prawą stronę. My możemy jedynie zmienić prędkość tego ruchu.

Wszystko, co musimy zrobić, żeby nasz pasek postępu działał tak, jakbyśmy sobie życzyli, to zapoznanie się z komunikatem PBM_SETMARQUEE. Parametr lParam mówi kontrolce, czy ma być aktywna. Wpisujemy do niego TRUE lub FALSE. Jeśli wybraliśmy true możemy w parametrze lParam podać prędkość przesuwania się zielonego obszaru w milisekundach na klatkę. Najlepiej jednak nic nie ruszać, bo domyślna wartość (30) jest optymalna:

C/C++
// Uruchamiamy pasek z domyślną prędkością
SendMessage( hProgressBar, PBM_SETMARQUEE, TRUE, 0 );
// Dezaktywujemy pasek
SendMessage( hProgressBar, PBM_SETMARQUEE, FALSE, 0 );

ProgressBar na pasku zadań

Skoro nauczyliśmy się tworzyć pasek postępu w oknie programu, możemy pójść jeszcze krok dalej i umiescić taki również na pasku zadań. Rozwiązanie takie ma wiele zalet. Po pierwsze widać go, kiedy okno programu jest zminimalizowane, po drugie można na nim pokazać też pauzę lub błąd programu, a po trzecie i najważniejsze pokazujemy w ten sposób jak bardzo jesteśmy pro ;-)

TBPF_NORMAL (Windows 7)
TBPF_NORMAL (Windows 7)

Zanim jednak zdecydujemy, że warto umieszczać w programie taki bajer, powinniśmy wiedzieć, że działa on tylko w systemie Windows 7 lub nowszym, więc przed jego utworzeniem będziemy musieli programowo sprawdzić wersję systemu. Kolejnym utrudnieniem jest całkiem łatwy do przewidzenie fakt, że panowie z Micro$oftu zrobili wszystko, aby żadna z potrzebnych funkcji nawet nie przypominała tych, których używamy do tworzenia zwykłego ProgressBara. Są też inne "smaczki", ale dla chcącego nic trudnego, więc zamiast narzekać bierzmy się do roboty.

Sprawdzanie wersji systemu

Żeby sprawdzić wersję wersję Łindołsa posłużymy się strukturą OSVERSIONINFO. Jej składniki są następujące:

PoleZnaczenie
dwOSVersionInfoSizeRozmiar struktury w bajtach. Należy tu wpisać sizeof (OSVERSIONINFO).
dwMajorVersionGłówny numer wesji.
dwMinorVersionPomniejszy numer wersji.
dwBuildNumbeNumer budowy systemu. Mało przydatny.
dwPlatformIdNumer platformy. Według dokumentacji może przybrać tylko jedną wartość :-\
szCSDVersionŁańcuch znaków z pełnym numerem wersji. (Wymaga Service Pack 3.)

Windows 7 wcale nie ma numeru 7 tylko 6.1, więc będziemy musieli sprawdzić główny jak i poboczny numer wersji. Oczywiście musimy przed całą operacją standardowo wyzerować strukturę i określić jej rozmiar.

Dane pobierzemy za pomocą funkcji GetVersionEx, podając jej wskaźnik do naszej struktury:
C/C++
OSVERSIONINFO osvi;
ZeroMemory( & osvi, sizeof( OSVERSIONINFO ) );
osvi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );

GetVersionEx( & osvi );
bool b7LubNowszy =(( osvi.dwMajorVersion[ h1 ] 6 ) &&( osvi.dwMinorVersion >= 1 ) ) ||( osvi.dwMajorVersion >= 7 );
Teraz pozostaje przed każdą operacją na pasku zadań sprawdzać, czy zmienna b7LubNowszy ma wartość true.

Oczekiwanie na utworzenie przycisku na pasku zadań

Nie jest tajemnicą, że programiści, którzy próbują modyfikować nieistniejące obiekty, nigdy nie żyją długo i szczęśliwie. Dlatego przed jakimikolwiek machinacjami z paskiem zadań trzeba się upewnić, że program utworzył już na nim przycisk.
Żeby tego dokonać należy odebrać odpowiedni komunikat. Doświadczenie z produktami Microsoftu podpowiada jednak, że to nie może być takie proste. I rzeczywiście, musimy bowiem sami zarejestrować ten komunikat. Używamy do tego funkcji RegisterWindowMessage z parametrem "TaskbarButtonCreated". Wywołujemy ją podczas obsługi WM_CREATE. Funkcja zwróci nam numer wiadomości, który musimy odebrać. Dopiero wtedy możemy wziąć się do roboty.
C/C++
LRESULT CALLBACK WindowProcedure( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    static DWORD g_wmTBC =( DWORD ) - 1;
   
    if( message[ / h1 ] g_wmTBC )
    {
        // Tu możemy wreszcie utworzyć ProgressBar
    } else
    {
        switch( message )
        {
        case WM_CREATE:
            g_wmTBC = RegisterWindowMessage( "TaskbarButtonCreated" );
            break;
           
            // Obsługa pozostałych komunikatów
        }
    }
Musieliśmy uciec się do użycia intrukcji if, ponieważ switch przyjmuje tylko stałe wartości.

Interfejs ITaskbarList3

Interfejs ten służy do najróżniejszych modyfikacji paska zadań. Pobieramy wskaźnik do niego za pomocą funkcji CoCreateInstance. Jej wywołanie jest dosyć skomplikowane, więc przejdźmy od razu do przykładu, który najlepiej po prostu sobie skopiować:
C/C++
#include <Shobjidl.h> // dla ITaskbarList3
//...
ITaskbarList3 * ptl;
if( CoCreateInstance( CLSID_TaskbarList, NULL, CLSCTX_ALL, IID_ITaskbarList3,( LPVOID * ) & ptl ) != S_OK )
     MessageBox( 0, "I nici z bajerów.", 0, 0 );

Sprawdziliśmy od razu wartość zwracaną, która równa się S_OK jeśli wszystko jest OK ;-)

Obsługa ProgressBara na pasku zadań

Po tym wszystkim powinniśmy się spodziewać czegoś strasznie skomplikowanego na deser. Czasem jednak nawet wrednych Panów z Microsoftu stać na odrobinę litości, dlatego musimy zapoznać się tylko z dwiema prostymi funkcjami.

Na początku powinniśmy ustawić styl wyświetlania naszego paska postępu, a mamy ich do wyboru całkiem sporo:

StałaZnaczenieWygląd
TBPF_NOPROGRESSBrak paska
TBPF_NOPROGRESS (Windows 7)
TBPF_NOPROGRESS (Windows 7)
TBPF_INDETERMINATEStan nieokreślony
TBPF_INDETERMINATE (Windows 7)
TBPF_INDETERMINATE (Windows 7)
TBPF_NORMALNormalny wygląd
TBPF_NORMAL (Windows 7)
TBPF_NORMAL (Windows 7)
TBPF_ERRORBłąd
TBPF_ERROR (Windows 7)
TBPF_ERROR (Windows 7)
TBPF_PAUSEDSpauzowany
TBPF_PAUSED (Windows 7)
TBPF_PAUSED (Windows 7)

Styl ustawimy, wywołując funkcję składową SetProgressState dla pobranego wcześniej wskaźnika:
C/C++
ptl->SetProgressState( hOknoProgramu, TBPF_ERROR );
Ustawienie wartości paska jest równie łatwe i sprowadza się do użycia funkcji SetProgressValue. Podajemy w niej zarówno aktualne położenie paska, jak i maksymalną wartość. Nie możemy za to ustawić minimalnej wartości, która zawsze wynosi 0. Ustawmy sobie pasek na 2/3 długości:
C/C++
ptl->SetProgressValue( hOknoProgramu, 2, 3 );
Poprzedni dokument Następny dokument
Kolory kontrolek Zakładki (TabCtrl)