W tym odcinku zajmiemy się esencją programowania z użyciem MUI w szczególności, a programowania obiektowego w ogólności. Mowa bowiem będzie o notyfikacjach. Notyfikacje są tym, co czyni program zbiorem komunikujących się ze sobą obiektów, a nie zestawem procedur. Czym więc jest notyfikacja? Jest ona metodą obiektu, która służy do komunikowania się z innymi obiektami. Konkretnie w MUI jest to metoda MUIM_Notify w klasie MUIC_Notify. Jeden rzut oka na drzewo klas MUI uświadomi nam, że klasa MUIC_Notify jest nadrzędna dla wszystkich innych klas. To oznacza, że każda klasa dziedziczy metodę notyfikacji, a co za tym idzie każdy obiekt MUI może się komunikować z każdym innym. Na czym konkretnie ta komunikacja polega? Mamy tu dwa obiekty, jeden jest obiektem, który notyfikację wysyła (nadajnikiem), a drugi ją odbiera. Wysłanie informacji następuje w chwili, gdy wybrany atrybut nadajnika przyjmie określoną wartość, a polega ono na wykonaniu na obiekcie odbierającym (odbiorniku) dowolnej jego metody. Jak widać jest to mechanizm bardzo ogólny. Z jednej strony daje on ogromną swobodę programiście, z drugiej jednak może być na początku trudny do zrozumienia. Dlatego przeanalizujmy przykład z poprzedniego odcinka:
DoMethod (win, MUIM_Notify, MUIA_Window_CloseRequest, MUIV_EveryTime, app, 2, MUIM_Application_Return_ID, MUIV_Application_ReturnID_Quit);
Jak widać ustawienie notyfikacji polega na wykonaniu metody (stąd funkcja DoMethod ()). Jest to metoda MUIM_Notify. Metoda ta jest zawsze wykonywana na obiekcie - nadajniku. W tym przypadku nadajnikiem informacji jest okno. Pierwsze dwa parametry są już jasne. Trzecim parametrem jest atrybut nadajnika, którego zmiana spowoduje wysłanie informacji. W tym przypadku jest to atrybut MUIA_Window_CloseRequest, który jest ustawiany po naciśnięciu na gadżet zamknięcia okna. Tu ważna uwaga - notyfikacja może być ustawiana jedynie na te atrybuty które są możliwe do odczytania, a więc mają literę "G" w autodocach. Czwartym parametrem jest wartość atrybutu wywołująca notyfikację. Specjalna stała MUIV_EveryTime oznacza wywołanie notyfikacji przy każdej zmianie wartości atrybutu. Piąty parametr to obiekt docelowy (odbiornik), w tym przypadku jest to aplikacja. Tajemnicza dwójka oznacza ilość pozostałych parametrów funkcji (maksymalnie 7). Jak napisałem wcześniej, notyfikacja polega na wykonaniu jakiejś metody na odbiorniku, a różne metody miewają różne ilości parametrów. Trzeba więc tę ilość określić, aby MUI mogło zarezerwować na nie pamięć. Później następuje identyfikator metody do wywołania i jej parametry.
Dla lepszej ilustracji jeszcze jeden przykład. Wyobraźmy sobie, że mamy suwak (MUIC_Slider) i pole tekstowe (MUIC_Text). Chcemy teraz, aby za każdym razem gdy suwak będzie na pozycji "13", w gadżecie pojawił się tekst "Pechowa liczba!". Odpowiednia notyfikacja będzie wyglądała następująco:
DoMethod (suwak, MUIM_Notify, MUIA_Slider_Level, 13, pole_tekstowe, 3, MUIM_Set, MUIA_Text_Contents, "Pechowa liczba!");
Zauważmy jedną rzecz - notyfikację ustawiamy tylko raz, na początku programu. Cała reszta dzieje się już zupełnie automatycznie, bez naszego (jako programistów) udziału. Czyż to nie wielkie ułatwienie? Przy okazji pojawia nam się tu bardzo użyteczna metoda MUIM_Set. Służy ona do ustawienia wartości podanego atrybutu w obiekcie. "Jak to?" zakrzyknie uważny czytelnik "przecież do tego służy metoda OM_SET!". Tak, ale jak pamiętamy, wymaga ona podania listy tagów, podczas gdy MUIM_Set ustawia tylko jeden atrybut, który podaje się bezpośrednio. Dzięki temu jest bardzo wygodna w użyciu właśnie w notyfikacjach.
Powyższy przykład notyfikacji ma jednak jedną wadę. Po ustawieniu suwakiem pozycji "13" napis pojawia się w polu tekstowym, ale nie znika gdy przesuniemy suwak dalej. Niestety nie ma sposobu, aby zrobić notyfikację "na wszystko oprócz 13". Nieco dalej powiem jak sobie z tym poradzić.
Bywa tak, że chcemy na odbiorniku wykonać metodę z parametrem równym wartości atrybutu, który notyfikację wyzwolił. Na przykład wartość wpisana do gadżetu MUIC_String powinna się pojawić w polu tekstowym. Wtedy możemy w miejsce parametru wykonywanej na odbiorniku metody umieścić specjalną stałą MUIV_TriggerValue. MUI wykonując notyfikację umieści tam wartość parametru. Oto przykładowa notyfikacja:
DoMethod (string, MUIM_Notify, MUIA_String_Acknowledge, MUIV_EveryTime, pole_tekstowe, 3, MUIM_Set, MUIA_Text_Contents, MUIV_TriggerValue);
W przypadku, gdy atrybut wyzwalający notyfikację przyjmuje jedynie wartości logiczne (TRUE lub FALSE), możemy przesłać jego zanegowaną wartość korzystając ze stałej MUIV_NotTriggerValue w taki sam sposób jak wyżej. Często znajduje to zastosowanie w notyfikowaniu różnego rodzaju przycisków i checkmarków.
Bardzo często najwygodniej byłoby wywołać notyfikacją jakąś procedurę, bo wykonanie metody na obiekcie jakoś nie wystarcza. Powróćmy do przykładu z pechową trzynastką. Wskazane byłoby, żeby napis pojawiał się tylko wtedy, gdy suwak znajduje się na pozycji "13", a znikał po przesunięciu suwaka na inną liczbę. Trzeba więc napisać procedurę która sprawdzi pozycję suwaka i ustawi odpowiednio napis w polu tekstowym. Są dwie drogi do osiągnięcia tego celu. Po pierwsze można napisać podklasę MUIC_Text z nowym atrybutem np. TXTA_MożeTrzynaście, a samo sprawdzenie odbywałoby się w metodzie OM_SET klasy. Jest to metoda najlepsza, ale omówię ją później przy rozważaniach na temat budowania własnych klas MUI. Drugim, nieco prostszym sposobem jest wywołanie w notyfikacji hooka. Dlaczego nie zwykłej procedury? Hook jest niezależny od języka programowania, więc można go wywoływać z poziomu każdego języka (patrz jeden z pierwszych odcinków). W MUI istnieje metoda MUIM_CallHook, którą można hooka wywołać. Rozwiązanie naszego problemu będzie więc następujące:
#define reg(a) __asm(#a) /* tylko dla kompilatora EGCS */ long TestTrzynastki (struct Hook *hook reg(a0), Object *obj reg(a2), long *x reg(a1)) { if (*x == 13) SetAttrs (obj, MUIA_Text_Contents, "Pechowa liczba!", TAG_END); else SetAttrs (obj, MUIA_Text_Contents, "", TAG_END); return 0; } struct Hook h_TestTrzynastki = {NULL, NULL, (HOOKFUNC)TestTrzynastki, NULL, NULL}; DoMethod (suwak, MUIM_Notify, MUIA_Slider_Level, MUIV_EveryTime, pole_tekstowe, 3, MUIM_CallHook, &h_TestTrzynastki, MUIV_TriggerValue);
Co się tutaj dzieje? Przy każdej zmianie pozycji suwaka zostanie na obiekcie tekstowym wykonana metoda MUIM_CallHook. Metoda ta wywoła hooka określonego strukturą h_TestTrzynastki. W rejestrach procesora zostaną umieszczone odpowiednie wartości. W rejestrze a0 znajdzie się adres struktury Hook. W rejestrze a2 adres obiektu, na którym jest wykonywana metoda (w naszym przypadku będzie to adres pola tekstowego). W rejestrze a1 natomiast znajdzie się adres tablicy wszystkich parametrów (maksymalnie pięciu) podanych w metodzie MUIM_CallHook (oprócz adresu hooka oczywiście). W naszym przykładzie stoi tam MUIV_TriggerValue, a więc a1 będzie wskaźnikiem na zmienną zawierającą poziom suwaka. W hooku odczytujemy tą zmienną, sprawdzamy jej wartość i ustawiamy odpowiedni napis w polu tekstowym.
W typowym przypadku notyfikacja po ustawieniu istnieje do końca działania programu. Czasem jednak trzeba notyfikację wyłączyć w czasie działania programu. Pozwala nam na to metoda MUIM_KillNotify. Wykonujemy ją na nadajniku notyfikacji. Z poprzedniego przykładu:
DoMethod (suwak, MUIM_KillNotify, MUIA_SliderLevel);
Notyfikacja zostanie usunięta, a więc pole tekstowe przestanie reagować na zmiany położenia suwaka. Pewien problem może powstać, gdy ustawimy kilka notyfikacji na ten sam atrybut jednego obiektu (choć być może na różne wartości tego atrybutu). W tym przypadku notyfikacje są usuwane w kolejności odwrotnej, niż były tworzone.
Przy radosnym tworzeniu notyfikacji często może nam się przydarzyć ich wzajemne zapętlenie. Weźmy przykładowo układ złożony z dwóch suwaków. Chcemy, aby poruszenie jednym suwakiem powodowało odpowiednią zmianę położenia drugiego suwaka i odwrotnie. Nic prostszego, załatwiają nam to dwie notyfikacje:
DoMethod (suwak1, MUIM_Notify, MUIA_Slider_Level, MUIV_EveryTime, suwak2, 3, MUIM_Set, MUIA_Slider_Level, MUIV_TriggerValue); DoMethod (suwak2, MUIM_Notify, MUIA_Slider_Level, MUIV_EveryTime, suwak1, 3, MUIM_Set, MUIA_Slider_Level, MUIV_TriggerValue);
Istnieje tu jednak pewne niebezpieczeństwo. Gdy np. ruszymy suwakiem 1, to notyfikacja powoduje zmianę położenia suwaka 2. Ale na tę zmianę mamy przecież ustawioną notyfikację! Spowoduje ona zmianę położenia suwaka 1 (co prawda nie fizyczną, bo wartość atrybutu będzie ta sama), co z kolei spowoduje... I mamy pętlę bez końca, program stanął w miejscu i zużywa 99% mocy procesora. Na szczęście autor MUI przewidział taką sytuację i wprowadził bardzo proste zabezpieczenie. Po prostu jeżeli ustawiany jest jakiś atrybut na taką samą wartość jaką posiada w tej chwili, to notyfikacje nie są wykonywane. Tak więc jeżeli poziomy suwaków wynoszą np. 17 i przestawimy suwak1 na 25, to wywoła on notyfikację na suwak2, ustawiając go również na 25. Następnie suwak2 wywoła notyfikację na suwak1 chcąc przestawić go na 25. Ale suwak 1 jest już na pozycji 25, w związku z czym kolejna notyfikacja już nie zostanie wykonana i pętla ulega przerwaniu.
Może się zdarzyć, że będziemy czasem chcieli zmienić jakiś atrybut na wartość inną od aktualnej, a mimo to nie wywoływać notyfikacji nań ustawionej. W takim przypadku trzeba posłużyć się metodą MUIM_NoNotifySet. Działa ona zupełnie tak samo, jak MUIM_Set, ale nie wywołuje notyfikacji. Podobne działanie ma atrybut MUIA_NoNotify, z tymże stosujemy go w funkcji SetAttrs. Załóżmy, że chcemy programowo przestawić suwak1 z ostatniego przykładu:
SetAttrs (suwak1, MUIA_Slider_Level, 25, TAG_END);
To oczywiście spowoduje przestawienie również suwaka2. Jeżeli jednak chcemy przestawić tylko suwak1:
SetAttrs (suwak1, MUIA_NoNotify, TRUE, MUIA_Slider_Level, 25, TAG_END);
MUIA_NoNotify ma charakter "jednorazowy", a więc kolejne zmiany atrybutu będą już przestawiać oba suwaki.
Jako przykład praktycznego zastosowania notyfikacji niech posłuży prościutka gra "Odwracanka". Mamy w niej planszę 3x3 pola. Każde pole może mieć dwa możliwe stany. Kliknięcie na pole powoduje zmianę jego stanu na przeciwny, ale zmianie ulegają również stany wszystkich pól w rzędzie i kolumnie wybranego. Wskaźniki na obiekty pól umieszczone są w tablicy. To pozwala na ustawianie notyfikacji w pętli, dla każdego obiektu MUI zapamięta właściwą wartość przekazywaną jako parametr hooka (a będącą po prostu numerem klikniętego pola).
for (i = 0; i < 9; i++) DoMethod (Pola[i], MUIM_Notify, MUIA_Selected, MUIV_EveryTime, Program, 3, MUIM_CallHook, &h_Odwroc, i);
W tablicy 'Zalezności' zapisane jest jakie pola podlegają zmianie po kliknięciu na jedno z nich. Funkcja OdwrocJeden() odwraca pole o podanym numerze:
void OdwrocJeden (long ktory) { long stan; GetAttr (MUIA_Selected, Pola[ktory], &stan); SetAttrs (Pola[ktory], MUIA_NoNotify, TRUE, MUIA_Selected, !stan, TAG_END); return; }
Warto zwrócić uwagę na fakt, że w tym wypadku niezbędne jest użycie atrybutu MUIA_NoNotify do przerwania nieskończonej pętli notyfikacji. Dlaczego? Tu opisany wcześniej mechanizm zabezpieczający jest bezradny, bo zawsze ustawiamy atrybut MUIA_Selected w stan przeciwny do aktualnego (negacja zmiennej 'stan'). Eksperymentatorzy mogą spróbować uruchomienia programu po usunięciu MUIA_NoNotify, ale ostrzegam, że najprawdopodobniej skończy się to zawieszeniem programu. I tym optymistycznym akcentem kończę. Dodatek - tutaj.