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

Akceleratory

[lekcja] Rozdział 17. Tworzenie statycznych jak również zmiennych skrótów klawiszowych do elementów menu.
Akceleratory to inaczej skróty klawiszowe do elementów menu. Jak być może już zauważyłeś, w pliku *.rc menu możemy sobie dopisać jakiś skrót, używając sekwencji ucieczki \t (wstawiającej tabulację), np.:
C/C++
MENUITEM "&Nowy\tCtrl+N", 100
Tyle tylko, że ten skrót nie będzie sobie ot tak po prostu działał. Tutaj właśnie potrzebujemy akceleratorów.

Edycja pliku zasobów

Podobnie jak samo menu, tabele akceleratorów umieszcza się w zasobach (*.rc). Możemy to zrobić ręcznie albo skorzystać z odpowiedniego programu, np. ResEdit). Wygląda to mniej więcej tak:
C/C++
IDR_ACCELERATORS ACCELERATORS
{
    VK_F2, ID_ACC_F2, VIRTKEY
}
Ta deklaracja ma podobną formę, jak wszystkie inne rodzaje zasobów (najpierw identyfikator, u nas IDR_ACCELERATORS (chyba nie muszę dodawać, że identyfikator należy wcześniej zdefiniować za pomocą #define, najlepiej w pliku nagłówkowym), potem typ zasobu, czyli ACCELERATORS, a następnie nawiasy klamrowe oznaczające blok z konkretnymi akceleratorami. Pojedynczy akcelerator składa się z: klawisza, identyfikatora oraz dodatkowych flag, które za chwilę omówimy (tutaj jest tylko VIRTKEY).
Klawisz, z którym skojarzony jest akcelerator, może być podany w formie kodu ASCII albo jako kod wirtualnego klawisza. Kod ASCII jest najczęściej używany dla literek, natomiast specjalne klawisze (np. F2) podajemy jako kody VK, czyli tak jak powyżej. Gdybyśmy chcieli dodać jeszcze akcelerator dla klawisza N, tabela wyglądałaby następująco:
C/C++
IDR_ACCELERATORS ACCELERATORS
{
    VK_F2, ID_ACC_F2, VIRTKEY
    "n", ID_ACC_N, ASCII
}
Dodatkowo możemy zdefiniować modyfikatory Shift, Alt i Ctrl. W przypadku kodów ASCII dodanie klawisza Shift jest bardzo proste, wystarczy umieścić w deklaracji akceleratora dużą literę zamiast małej. Można też zrobić następująco:
C/C++
IDR_ACCELERATORS ACCELERATORS
{
    VK_F2, ID_ACC_F2, SHIFT, VIRTKEY
    "n", ID_ACC_N, CONTROL, VIRTKEY
}
Pierwszy akcelerator z powyższego przykładu zareaguje na kombinację Shift+F2, a drugi – Ctrl+N.

Zauważ, że ten drugi akcelerator ma flagę VIRTKEY zamiast ASCII. Dodanie modyfikatora CONTROL lub SHIFT (ale nie ALT!) wymaga bowiem VIRTKEY. Kod klawisza "n" na szczęście jest i tak rozpoznawany prawidłowo.

Ładowanie i użycie akceleratorów

Skoro wiemy już, jak tworzyć tabelę akceleratorów, możemy spróbować użyć jej w programie. Najpierw trzeba oczywiście załadować tabelę z zasobów:
C/C++
HACCEL hAccel = LoadAccelerators( hInstance, MAKEINTRESOURCE( IDR_ACCELERATORS ) );
if( !hAccel )
{
    MessageBox( hWnd, "Nie można załadować akceleratorów.", NULL, MB_ICONEXCLAMATION );
}
Kolejna sprawa to wywołanie funkcji TranslateAccelerator, co musimy zrobić w pętli komunikatów. Właśnie ta funkcja zajmuje się sprawdzaniem, czy wciśnięta kombinacja klawiszy figuruje w tabeli akceleratorów i (jeśli jest) jaki identyfikator jest jej przypisany. Szukamy więc naszej pętli, która prawdopodobnie wygląda jakoś tak:
C/C++
MSG msg;
while( GetMessage( & msg, NULL, 0, 0 ) )
{
    TranslateMessage( & msg );
    DispatchMessage( & msg );
}
Funkcja TranslateAccelerator dostaje uchwyt naszego głównego okna, uchwyt tabeli akceleratorów, który dostaliśmy od funkcji LoadAccelerators oraz wskaźnik na komunikat (u nas &msg), po czym zwraca true jeśli komunikat pochodzi od akceleratora. Wówczas nie powinniśmy już dalej przetwarzać komunikatu. W przeciwnym wypadku wołamy normalnie TranslateMessage i DispatchMessage. Tak więc teraz nasza pętla będzie następująca:
C/C++
MSG msg;
while( GetMessage( & msg, NULL, 0, 0 ) )
{
    if( !TranslateAccelerator( hWnd, hAccel, & msg ) )
    {
        TranslateMessage( & msg );
        DispatchMessage( & msg );
    }
}
Akceleratory powinny już działać, co jednak nie zmienia nic w naszym programie, ponieważ ich działanie sprowadza się tylko do wysłania komunikatu WM_COMMAND, którego nie obsługujemy. Możemy to jednak zrobić:
C/C++
case WM_COMMAND:
{
    if( LOWORD( wParam ) == ID_ACC_F2 )
    {
        MessageBox( hWnd, "Wcisnąłeś F2.", "Wow.", MB_ICONINFORMATION );
    }
}
break;
Nie ma tu nic nadzwyczajnego, wygląda to niemal identycznie, jak obsługa poleceń menu... no właśnie, przecież akceleratory miały nam służyć do ułatwienia obsługi menu, a tymczasem po prostu obsłużyliśmy sobie klawisz. Gdy używamy akceleratorów, prościej jest wprowadzać kombinacje klawiszy, ale nie w tym rzecz (a raczej nie tylko w tym). Chodzi głównie o to, żeby wprowadzanie każdego nowego klawisza nie wymagało pisania dodatkowego kodu, tylko dodania tego klawisza do zasobów, czyli zwykle poklikania w edytorze.

Jeśli więc zależy nam tylko na dodaniu skrótu klawiszowego do określonej pozycji w menu, to nie dajemy akceleratorowi nowego identyfikatora, tylko przypisujemy mu ten sam identyfikator, co owej pozycji menu, np.:
C/C++
IDR_ACCELERATORS ACCELERATORS
{
    VK_F2, ID_MENU_FILE_NEW, VIRTKEY
}
Jeśli już wcześniej obsłużyliśmy kliknięcie ID_MENU_FILE_NEW w menu, to taki akcelerator zadziała automatycznie – nie będziemy musieli nic dopisywać do obsługi WM_COMMAND. W razie jednak gdybyś kiedyś zapragnął sprawdzić, czy dany komunikat WM_COMMAND pochodzi od akceleratora, czy od menu, wystarczy zbadać wyrażenie HIWORD(wParam); jeśli jest równe 0, to komunikat przyszedł od menu, natomiast akceleratory wpisują tam 1.

Tworzenie akceleratorów w run-time

Akceleratory można tworzyć w zasobach, ale można je tworzyć również już podczas działania programu. Zazwyczaj użytkownik uznaje nagłe zmiany działania kombinacji klawiszy w programie za głupi kawał, ale zapewne istnieją też bardziej konstruktywne zastosowania tej techniki ;-). Żeby daleko nie szukać, wyobraźmy sobie dialog, w którym użytkownik może sobie sam zdefiniować skróty klawiszowe. Aby taki zrobić, musimy zrezygnować z wstawiania akceleratorów do zasobów i stworzyć je właśnie w fazie ''run-time''.

Aby stworzyć nową tabelę akceleratorów zamiast ładować ją z zasobów, używamy funkcji CreateAcceleratorTable. Jej głównym parametrem jest wskaźnik na strukturę ACCEL. Jest to bardzo prosta strukturka:
C/C++
struct ACCEL
{
    BYTE fVirt;
    WORD key;
    WORD cmd;
};
Ponieważ widzieliśmy już, jak wyglądają deklaracje akceleratorów w pliku *.rc, więc domyślamy się, jak wypełnić tę strukturę. Pole key ma zawierać kod klawisza (ASCII lub VK), cmd – identyfikator akceleratora (czyli kod polecenia w menu), zaś fVirt to kombinacja następujących flag:
  • FALT
  • FCONTROL
  • SHIFT
  • VIRTKEY
Znaczenia pierwszych trzech domyślamy się z łatwością, natomiast FVIRTKEY jest odpowiednikiem VIRTKEY w pliku zasobów; tę ostatnią flagę dajemy wtedy, gdy podany kod klawisza to VK. Natomiast jeśli podajemy kod ASCII, po prostu pomijamy flagę FVIRTKEY.

Zwykle tworzymy więcej niż jeden akcelerator w tabeli, więc wygodnie jest wpakować strukturę ACCEL do tablicy:
C/C++
ACCEL acc[ 2 ];
acc[ 0 ].key = VK_F2;
acc[ 0 ].cmd = ID_MENU_FILE_NEW;
acc[ 0 ].fVirt = FVIRTKEY;
acc[ 1 ].key = "a";
acc[ 1 ].cmd = ID_MENU_FILE_APPEND:
acc[ 1 ].fVirt = 0;
Teraz możemy już wywołać CreateAcceleratorTable, podając adres powyższej tablicy, a także liczbę elementów w tablicy:
C/C++
HACCEL hAccel = CreateAcceleratorTable( acc, 2 );
Z otrzymanym uchwytem postępujemy podobnie, jak w przypadku akceleratorów ładowanych z zasobów, czyli wołamy TranslateAccelerator. Tym razem jednak musimy skasować tabelę akceleratorów, gdy przestanie już być używana:
C/C++
DestroyAcceleratorTable( hAccel );
Poprzedni dokument Następny dokument
Menu Rejestr