Difference between revisions of "Programowanie sterowane zdarzeniami, notyfikacje"

From MorphOS Library

(Event Driven Programming: Translated (except the diagram for now).)
(Polish version of flowchart, minor fixes.)
 
(10 intermediate revisions by the same user not shown)
Line 9: Line 9:
  
  
[[File:mzone_eventdriven_1.png|center]]
+
[[File:mzone_eventdriven_1_pl.png|center]]
  
  
 
<center>''Rys. 1. Przebieg wykonania programu sterowanego zdarzeniami.''</center>
 
<center>''Rys. 1. Przebieg wykonania programu sterowanego zdarzeniami.''</center>
  
==Notifications in MUI==
 
  
There are two approaches to input event decoding in an event driven program: centralized and decentralized. Centralized decoding is programmed as a big conditional statement (a ''switch'' statement usually, or a long cascade of ''if'' statements) inside the main loop of the fig. 1. flowchart. Depending on the decoded event, subroutines performing requested actions are called. Decentralized input event decoding is a more modern idea. In this case, the GUI toolkit receives and handles incoming input events internally and maps them to attribute changes of GUI objects (for example, clicking with the mouse on a button changes its attribute to "pressed"). Then an application programmer can assign actions to attribute changes of chosen objects. This is done by creating '''notifications''' on objects.
+
==Notyfikacje w MUI==
  
MUI uses decentralized input event decoding. All input events are mapped to attribute changes of different objects. These are visible GUI gadgets (controls) usually, but some events may be mapped to attributes of a window object, or an application object (the last one has no visible representation). After creating the complete object tree, but before entering the main loop, the program sets up notifications, assigning actions to attribute changes. Notifications can also be created and deleted dynamically at any time.
+
Do odbierania i przetwarzania zdarzeń generowanych przez użytkownika można podejść na dwa sposoby. Jeden z nich można nazwać podejściem scentralizowanym, drugi &ndash; zdecentralizowanym. Dekodowanie zdarzeń w wersji scentralizowanej składa się z rozbudowanego wyrażenia warunkowego (najczęściej konstrukcja ''switch'', ale bywa też długa kaskada ''if''-ów) znajdującego się w głównej pętli algorytmu z rys. 1. Po zidentyfikowaniu każdego zdarzenia, wykonywana jest odpowiadająca mu procedura. Zdecentralizowana obsługa zdarzeń, to nowsza idea, która rozpowszechniła się wraz z programowaniem obiektowym. W tym przypadku biblioteka tworząca GUI sama odbiera i przetwarza zdarzenia, a następnie mapuje je na zmiany atrybutów obiektów GUI (na przykład kliknięcie na przycisk myszą, zmienia jego atrybut na "wciśnięty"). Następnie autor programu może przypisać akcje wykonywane przez program do określonych zmian atrybutów wybranych obiektów. Robi się to poprzez ustawianie '''notyfikacji''' na obiektach.
  
A notification connects two objects together. The source object triggers the action after one of its attributes changes. The assigned action (method) is then performed on the target object. The notification is set up by calling the ''MUIM_Notify()'' method on the source object. Arguments of the method can be divided into the source part and the target part. The general form of the ''MUIM_Notify()'' call is shown below:
+
MUI odbiera i rozpoznaje zdarzenia w sposób zdecentralizowany. Wszystke zdarzenia są tłumaczone na zmiany atrybutów różnych obiektów interfejsu. Zazwyczaj są to po prostu gadżety w oknie programu, ale niektóre zdarzenia mogą być mapowane na atrybuty obiektu okna lub obiektu aplikacji (ten ostatni nie ma widzialnej reprezentacji na ekranie). Po stworzeniu kompletnego drzewa obiektów, ale przed wejściem do głównej pętli, program ustawia notyfikacje, przypisując akcje do zmian atrybutów. Notyfikacje mogą być również tworzone i usuwane dynamicznie, jeżeli zachodzi taka potrzeba.
  
DoMethod(source, '''MUIM_Notify''', attribute, value, target, param_count, action, /* parameters */);
+
Notyfikacja łączy ze sobą dwa obiekty. Obiekt źródłowy wyzwala zdefiniowaną akcję po zmianie jednego z jego atrybutów. Akcja ta (jest to zawsze metoda) wykonywana jest na obiekcie docelowym. Notyfikację ustawia się wykonując metodę ''MUIM_Notify()'' na obiekcie źródłowym. Argumenty tej metody można podzielić na dwie grupy: grupę źródła i grupę celu. Ogólna postać wywołania ''MUIM_Notify()'' wygląda następująco:
  
The first four arguments form the '''source''' part, the rest is the '''target''' part. The complete call can be "translated" to a human language in the following way:
+
DoMethod(zrodlo, '''MUIM_Notify''', atrybut, wartosc, cel, ilosc_parametrow, akcja, /* parametry */);
  
 +
Pierwsze cztery argumenty tworzą grupę '''źródła''', pozostałe grupę '''celu'''. Całe wywołanie można "przetłumaczyć" na ludzki język następująco:
  
<big><center>When the '''source''' object changes its '''attribute''' to the '''value''',<br>perform '''action''' method on the '''target''' object with '''parameters'''.</center></big>
+
<big><center>Kiedy '''źródło''' zmieni swój '''atrybut''' na określoną '''wartość''',<br>wykonaj metodę '''akcja''' na obiekcie '''cel''' z '''parametrami'''.</center></big>
  
  
There is one argument not explained with the above sentence, namely ''param_count''. This is just the number of parameters following this argument. The minimum number of parameters is 1 (the action method identifier), there is no upper limit other than using common sense.
+
Pozostał nam jeden nieobjaśniony argument nazwany ''ilość_parametrów''. To po prostu ilość parametrów ''MUIM_Notify()'', jakie występują po tym argumencie. Minimalna ilość parametrów to 1 (identyfikator metody do wykonania), górnym limitem jest jedynie zdrowy rozsądek.
  
A notification is triggered when an attribute is set to a specified value. It is often useful to have a notification on '''any''' attribute change. A special value ''MUIV_EveryTime'' should be used as a triggering value in this case.
+
Notyfikacja zostaje wyzwolona, gdy atrybut wyzwalający zostaje ustawiony na wskazaną wartość. Często chcemy wyzwolić notyfikację przy '''każdej''' zmianie atrybutu, niezależnie od wartości. Jako wartość wyzwalającą należy wtedy podać predefiniowaną stałą ''MUIV_EveryTime''.
  
The target action of a notification can be any method. There are a few methods designed specifically to be used in notifications:
+
Akcja wykonywana na obiekcie docelowym może być dowolną metodą tego obiektu. MUI posiada kilka metod specjalnie zaprojektowanych pod kątem użycia ich w notyfikacjach. Są to metody klasy ''Notify'', po której dziedziczą wszystkie klasy MUI:
  
'''''MUIM_Set()''''' is another method for setting an attribute. It is used when a notification has to set an attribute on the target object. ''OM_SET()'' is not suitable for using in notifications because it takes a [[Taglists|taglist]] containing attributes to be set. This taglist cannot be built from arguments and must be defined separately. ''MUIM_Set()'' sets a single attribute to a specified value. They are passed to the method directly as two separate arguments. The example below opens a window when a button is pressed:
+
'''''MUIM_Set()''''' to inny sposób na ustawienie atrybutu obiektu, używany w sytuacji gdy notyfikacja powinna ustawić atrybut obiektu docelowego. Metoda ''OM_SET()'' się tu nie nadaje, ponieważ jako argumentu wymaga [[Taglists|taglisty]] zawierającej atrybuty do ustawienia. Taglista ta nie może być dynamicznie zbudowana z argumentów i w związku z tym musi być zdefiniowana oddzielnie. ''MUIM_Set()'' natomiast ustawia jeden atrybut na podaną wartość. Atrybut i jego wartość są podawane bezpośrednio jako dwa oddzielne argumenty tej metody. Notyfikacja poniżej otwiera okno po naciśnięciu przycisku.
  
  DoMethod(button, MUIM_Notify, MUIA_Pressed, FALSE, window, 3, MUIM_Set, MUIA_Window_Open, TRUE);
+
  DoMethod(przycisk, MUIM_Notify, MUIA_Pressed, FALSE, okno, 3, MUIM_Set, MUIA_Window_Open, TRUE);
  
Those not familiar with MUI may ask why the triggering value is set as ''FALSE''. It is related to the default behaviour of button gadgets. The gadget triggers when the left mouse button is '''released''', so at the end of a click. The ''MUIA_Pressed'' attribute is set to ''TRUE'' when the mouse button is pushed down, and set to ''FALSE'' when the mouse button is released. Now it should be obvious why the notification is set to trigger at a ''MUIA_Pressed'' change to ''FALSE''.
+
Osoby dopiero poznające MUI mogą być zaskoczone, że wartością atrybutu ''MUIA_Pressed'' wyzwalającą notyfikację jest ''FALSE''. Jest to związane z domyślnym zachowaniem się przycisków. Przycisk uruchamia akcję, gdy lewy przycisk myszy jest '''zwalniany''', a więc na końcu kliknięcia. Atrybut ''MUIA_Pressed'' otrzymuje wartość ''TRUE'' gdy przycisk myszy jest wciskany a następnie ''FALSE'', gdy przycisk myszy jest zwalniany, dlatego notyfikacja jest na wartość ''FALSE''.
  
'''''MUIM_NoNotifySet()''''' works the same as ''MUIM_Set()'' with one important exception. It does not trigger any notifications set on the target object when the attribute has changed. It is often used to avoid notification loops, not only in notification, but also standalone in the code.
+
'''''MUIM_NoNotifySet()''''' działa podobnie jak ''MUIM_Set()'', ale jest jedna istotna różnica. Ustawienie atrybutu przy pomocy tej metody nie wyzwala ewentualnych notyfikacji ustawionych na ten atrybut na obiekcie docelowym. Często ustawia się atrybuty tą metodą w celu uniknięcia zapętlania się notyfikacji, dlatego jest stosowana nie tylko jako akcja notyfikacji, ale również samodzielna metoda.
  
'''''MUIM_MultiSet()''''' allows for setting the same attribute to the same value for multiple objects. Objects are specified as this method's arguments and the final argument should be ''NULL''. Here is an example, disabling three buttons after a checkmark is deselected:
+
'''''MUIM_MultiSet()''''' daje możliwość ustawienia jednej wartości tego samego atrybutu dla wielu obiektów jednocześnie. Obiekty podaje się jako kolejne parametry metody, listę tę zakończa się wskaźnikiem zerowym. Poniższy przykład ustawia atrybut ''MUIA_Disabled'' na wartość ''TRUE'' trzem przyciskom:
  
 
  DoMethod(checkmark, MUIM_Notify, MUIA_Selected, FALSE, application, 7, MUIM_MultiSet,
 
  DoMethod(checkmark, MUIM_Notify, MUIA_Selected, FALSE, application, 7, MUIM_MultiSet,
 
   MUIA_Disabled, TRUE, button1, button2, button3, NULL);
 
   MUIA_Disabled, TRUE, button1, button2, button3, NULL);
  
What is interesting is that while the target notification object is completely irrelevant here, it must still be a valid object pointer. The application object is usually used for this purpose, or the notification source object.  
+
Co ciekawe obiekt będący celem notyfikacji jest tu zupełnie nieistotny, musi to być jednak wskaźnik do jakiegoś istniejącego obiektu. Najczęściej do tego celu używa się obiektu aplikacji albo źródłowego obiektu notyfikacji.
  
'''''MUIM_CallHook()''''' calls an external callback function called a [[hook]]. It is often abused by programmers being reluctant to perform subclassing of standard classes, instead implementing program functionality as new methods. Calling a method from a notification is usually faster and easier (however, a hook needs some additional structures to be defined).  
+
'''''MUIM_CallHook()''''' wywołuje funkcję programu, tzw. [[hook|hooka]]. Metoda ta jest często nadużywana przez programistów unikających tworzenia własnych klas i implementowania funkcjonalności programu w metodach takich klas. Wywołanie wprost metody własnej klasy jako akcji notyfikacji jest prostsze i szybsze, niż wywołanie hooka poprzez ''MUIM_CallHook()''. Dodatkowo hook wymaga zdefiniowania dodatkowych struktur - struktury ''Hook'' oraz tak zwanej "bramki" &ndash; struktury ''EmulLibEntry''.
  
'''''MUIM_Application_ReturnID()''''' returns a 32-bit integer number to [[#The_Ideal_MUI_Main_Loop|the main loop]] of a MUI program. With this method MUI's decentralized handling of input events can be turned into a centralized one. MUI programming beginners tend to abuse this method and redirect all the event handling back to the main loop, putting a big ''switch'' statement there. While rather simple, this programming technique should be avoided in favour of implementing program functionality in methods. Adding code inside the main loop degrades the GUI responsiveness. The only legitimate use of ''MUIM_Application_ReturnID()'' is to return a special value ''MUIV_Application_ReturnID_Quit'' used for ending the program.
+
'''''MUIM_Application_ReturnID()''''' zwraca 32-bitową liczbę całkowitą do [[#Idealna pętla główna|głównej pętli]] programu. Korzystając z tej metody można zmienić zdecentralizowaną obsługę zdarzeń w scentralizowaną. Początkujący programiści lubią nadużywać tej metody przekierowując wszystkie notyfikacje do głównej pętli programu, umieszczając tam następnie duże wyrażenie ''switch''. Mimo, że ta technika programowania wydaje się prosta, należy jej unikać i raczej implementować funkcje programu jako metody własnych klas tworzonych jako podklasy standardowych klas MUI. Wykonywanie dużych ilości kodu wewnątrz głównej pętli MUI zwiększa czas reakcji na zdarzenia. Jedynym w pełni uzasadnionym przypadkiem użycia ''MUIM_Application_ReturnID()'' są notyfikacje powodujące wyjście z programu, używające specjalnego identyfikatora ''MUIV_Application_ReturnID_Quit''.
  
  
==Reusing Triggering Value==
+
==Wykorzystanie wartości wyzwalającej==
  
When the action of a notification is to set an attribute in the target object, it is often desired to forward the triggering value to the target object. It is very easy, when the notification is set to occur on a particular value. Things change however, when the notification is set to occur on any value with ''MUIV_EveryTime''. A special value ''MUIV_TriggerValue'' may be used for this. It is replaced with the actual value of the triggering attribute at every trigger. Another special value, ''MUIV_NotTriggerValue'' is used for boolean attributes and is replaced by a logical negation of the current value of the triggering attribute.
+
Kiedy akcją notyfikacji jest ustawienie wartości jakiegoś atrybutu w obiekcie docelowym, bardzo często chcemy przekazać wartość, która wyzwoliła notyfikację, do obiektu docelowego. Jest to bardzo proste, jeżeli notyfikacja jest ustawiona na jedną, konkretną wartość atrybutu. Inaczej to wygląda, gdy wyzwolenie notyfikacji następuje przy każdej zmianie wartości atrybutu (przez użycie ''MUIV_EveryTime''). Rozwiązaniem jest użycie specjalnej stałej ''MUIV_TriggerValue'' jako pozornej wartości atrubutu przy definiowaniu notyfikacji. Przy każdym wyzwoleniu notyfikacji stała ta jest zastępowana aktualną wartością atrybutu. Druga predefiniowana stała ''MUIV_NotTriggerValue'' może być zastosowana do atrybutów logicznych (''TRUE''/''FALSE'') i zastępowana jest logiczną negacją aktualnej wartości atrybutu.
  
The first example uses ''MUIV_Trigger_Value'', to display the value of a slider in a string gadget:
+
Pierwszy przykład używa ''MUIV_Trigger_Value'', do wyświetlania wartości suwaka w gadżecie tekstowym:
  
 
  DoMethod(slider, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, string, 3,
 
  DoMethod(slider, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, string, 3,
 
   MUIM_Set, MUIA_String_Integer, '''MUIV_TriggerValue''');
 
   MUIM_Set, MUIA_String_Integer, '''MUIV_TriggerValue''');
  
The second example connects a checkmark with a button. When the checkmark is selected, the button is enabled. Deselecting the checkmark disables the button:
+
Drugi przykład łączy notyfikacją checkmarka z przyciskiem. Gdy checkmark jest wybrany, przycisk jest aktywny. "Odznaczenie" checkmarka blokuje przycisk:
  
 
  DoMethod(checkmark, MUIM_Notify, MUIA_Selected, MUIV_EveryTime, button, 3,
 
  DoMethod(checkmark, MUIM_Notify, MUIA_Selected, MUIV_EveryTime, button, 3,
 
   MUIM_Set, MUIA_Disabled, '''MUIV_NotTriggerValue''');
 
   MUIM_Set, MUIA_Disabled, '''MUIV_NotTriggerValue''');
  
<small>''MUIV_EveryTime'', ''MUIV_TriggerValue'' and ''MUIV_NotTriggerValue'' are defined as particular values in the 32-bit range. Because of this, it is impossible to set a notification on the value 1 233 727 793 (which is ''MUIV_EveryTime''). It is also impossible to set the value to a fixed number 1 233 727 793 (''MUIV_TriggerValue'') or 1 233 727 795 (''MUIV_NotTriggerValue'') using ''MUIM_Set()'' in a notification.</small>
+
<small>''MUIV_EveryTime'', ''MUIV_TriggerValue'' i ''MUIV_NotTriggerValue'' są zdefiniowane jako konkretne liczby 32-bitowe. Dlatego nie da się ustawić notyfikacji na wartość atrybutu równą 1 233 727 793 (wartość dla ''MUIV_EveryTime''). Nie można też zdefiniować akcji polegającej na ustawieniu wartości atrubutu na obiekcie docelowym na liczbę 1 233 727 793 (''MUIV_TriggerValue'') ani liczbę 1 233 727 795 (''MUIV_NotTriggerValue'').</small>
  
  
==Notification Loops==
+
==Zapętlone notyfikacje==
  
There may be hundreds of notifications defined in a complex program. Changing an attribute may trigger a notification cascade. It is possible that the cascade contains loops. The simplest example of a notification loop is a pair of objects having notifications on each other. Let's assume there are two sliders which should be coupled together. It means moving one slider should move the other one as well. A set of two notifications can ensure this behaviour.
+
Większy program w MUI może mieć zdefiniowane setki notyfikacji. Zmiana wartości jednego atrybutu może spowodować kaskadę notyfikacji. Może się okazać, że taka kaskada zawiera zamknięte pętle. Najprostszym przykładem takiej pętli jest para obiektów notyfikujących się wzajemnie. Przypuśćmy, że mamy dwa suwaki, które powinny być ze sobą sprzężone, to znaczy przesunięcie jednego suwaka powinno również przesuwać drugi. Można to osiągnąć dwiema notyfikacjami.
  
 
  DoMethod(slider1, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, slider2, 3,
 
  DoMethod(slider1, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, slider2, 3,
Line 82: Line 82:
 
   MUIM_Set, MUIA_Numeric_Value, MUIV_Trigger_Value);  
 
   MUIM_Set, MUIA_Numeric_Value, MUIV_Trigger_Value);  
  
When the user moves ''slider1'' to value 13, the first notification triggers and sets ''slider2'' value to 13. This triggers the second notification. Its action is to set ''slider1'' value to 13, which in turn triggers the first notification again. Then the loop triggers itself endlessly... Or rather it would, if MUI had no anti-looping measures. The solution is very simple: '''if an attribute is set for an object to the same value as the current one, any notifications on this attribute in this object are ignored'''. In our example the loop will be broken after the second notification sets the value of ''slider1''.
+
Kiedy użytkownik ustawi suwak ''slider1'' na wartość np. 13, zostanie wyzwolona pierwsza notyfikacja, przesuwając suwak ''slider2'' również do wartości 13. To z kolei wyzwoli drugą notyfikację, która to notyfikacja ustawi pierwszy suwak na 13, a to z kolei po raz drugi uruchomi notyfikację pierwszą... Pętla mogłaby krążyć w nieskończoność. Na szczęście MUI jest przygotowane na taką okoliczność. Rozwiązanie jest bardzo proste: '''jeżeli atrybut jest ustawiany na wartość równą jego wartości aktualnej, notyfikacje na ten atrybut w danym obiekcie są ignorowane'''. W naszym przykładzie pętla będzie przerwana po tym jak druga notyfikacja spróbuje ustawić wartość 13 (równą aktualnej) na obiekcie ''slider1''.
  
  
==The Ideal MUI Main Loop==
+
==Idealna pętla główna==
  
The ideal main loop of a MUI program should contain almost no code inside. All actions should be handled with notifications. Here is the code of the loop:
+
Idealna pętla główna programu w MUI powinna praktycznie nie zawierać kodu. Wszystkie akcje programu powinny być obsługiwane notyfikacjami. Oto kod takiej pętli:
  
 
  ULONG signals = 0;
 
  ULONG signals = 0;
Line 98: Line 98:
 
  }
 
  }
  
The variable ''signals'' contains a bit-mask of input event signals sent to the process by the operating system. Its initial value 0 just means "no signals". When the ''MUIM_Application_NewInput()'' method is performed on the application object, MUI sets signal bits for input events it expects in the ''signals'' variable. These signals are usually signals of application windows' message ports, where ''Intuition'' sends input events. Then the application calls the ''exec.library'' function ''Wait()''. Inside this function, the execution of the process is stopped. The process scheduler will not give any processor time to the process until one of the signals in the mask arrives. Other than input event signals set by MUI, only the system CTRL-C signal is added to the mask. Every well written MorphOS application should be breakable by sending this signal. It can be sent via the console by pressing the CTRL + C keys, or from tools like the TaskManager. When one of the signals in the mask arrives at the process, the program returns from ''Wait()''. If the CTRL-C signal is detected, the main loop ends. If not, MUI decodes the received input events based on its received signal mask, translates events to changes of attributes of relevant objects and performs the triggered notifications. All this happens inside the ''MUIM_Application_NewInput()'' method. Finally the signal mask is updated. If any notification calls the ''MUIM_Application_ReturnID()'' method, the identifier passed is returned as the result of ''MUIM_Application_NewInput()''. In the event of receiving the special value ''MUIV_Application_ReturnID_Quit'' the loop ends.
+
Zmienna ''signals'' zawiera maskę bitową sygnałów wysyłanych do programu przez system operacyjny. Wartością początkową tej maski jest 0, co oznacza brak sygnałów. Kiedy metoda ''MUIM_Application_NewInput()'' zostaje wykonana na obiekcie aplikacji, MUI ustawia w tej zmiennej bity sygnałów, których się spodziewa. Z reguły są to sygnały z portów komunikacyjnych okien, gdzie ''Intuition'' umieszcza wiadomości o nadchodzących zdarzeniach. Następnie program wywołuje funkcję systemową ''Wait()'' z ''exec.library''. Funkcja ta zatrzymuje proces do czasu nadejścia któregokolwiek sygnału, dla którego bit w podanej masce jest jedynką. W trakcie oczekiwania na sygnał proces jest wstrzymany i nie zużywa czasu procesora. Oprócz sygnałów "zamówionych" przez MUI do maski dodawany jest systemowy sygnał CTRL-C. Każdy dobrze napisany program dla MorphOS-a powinien dać się przerwać za pomocą tego sygnału. Sygnał ten może być wysłany z okna konsoli przez wciśnięcie kombinacji klawiszy CTRL + C (stąd nazwa), a także za pomocą TaskManagera i innych narzędzi. W momencie, gdy do programu nadejdzie co najmniej jeden z określonych maską sygnałów, funkcja ''Wait()'' wychodzi z powrotem do programu. W przypadku, gdy otrzymano sygnał CTRL-C, pętla ulega zakończeniu. Pozostałe sygnały są tłumaczone przez MUI na zmiany atrybutów poszczególnych obiektów, a następnie wykonywane są przypisane im notyfikacje. Wszystko to odbywa się wewnątrz metody ''MUIM_Application_NewInput()''. Na jej zakończenie aktualizowana jest maska sygnałów. Jeżeli którakolwiek z notyfikacji wykonała na obiekcie aplikacji metodę ''MUIM_Application_ReturnID()'', parametr tej metody jest zwracany jako wynik ''MUIM_Application_NewInput()''. Gdy parametrem tym okaże się być stała ''MUIV_Application_ReturnID_Quit'', pętla ulega przerwaniu.
  
Any additional code inserted into the loop will introduce delay in GUI handling and redrawing. If a program does some processor intensive calculations, the best way to deal with them is to delegate them into a subprocess. Loading the main process with computational tasks may result in the program being perceived as slow and unresponsive to user actions.
+
Każdy dodatkowy kod umieszczony w głównej pętli programu wprowadza opóźnienia w obsłudze i rysowaniu GUI. Jeżeli program wykonuje jakieś zadania silnie obciążające procesor na dłuższy czas, najlepszym sposobem jest wykonywanie tych zadań w procesie potomnym. Obciążanie głównego procesu programu długotrwałymi zadaniami obliczeniowymi powoduje, że program będzie sprawiał wrażenie powolnego i nie reagującego na poczynania użytkownika.

Latest revision as of 12:46, 19 January 2011

Grzegorz Kraszewski


Ten artykuł w innych językach: angielski

Programowanie sterowane zdarzeniami

Naturalną konsekwencją wynalezienia i późniejszego rozwoju graficznych interfejsów użytkownika było powstanie programów sterowanych zdarzeniami. Większość tradycyjnych programów uruchamianych z linii komend działa potokowo: dane są ładowane, przetwarzane i zapisywane. W programach tych nie ma interakcji z użytkownikiem, albo jest ograniczona do prostego wyboru parametrów przetwarzania danych. Interfejs graficzny zasadniczo to zmienia. Program używający interfejsu graficznego po inicjalizacji wyświetla okno z tymże interfejsem i czeka na poczynania użytkownika. Fragmenty programu są wykonywane w odpowiedzi na akcje w rodzaju kliknięcie gadżetu, czy wciśnięcie klawisza. Po wykonaniu odpowiedniej funkcji program wraca do czekania. W ten sposób przebieg wykonywania się programu jest określony nie przez jego kod, ale przez zdarzenia zewnętrzne generowane przez użytkownika, a dostarczane programowi przez system operacyjny. To podstawowa idea programowania sterowanego zdarzeniami.


Mzone eventdriven 1 pl.png


Rys. 1. Przebieg wykonania programu sterowanego zdarzeniami.


Notyfikacje w MUI

Do odbierania i przetwarzania zdarzeń generowanych przez użytkownika można podejść na dwa sposoby. Jeden z nich można nazwać podejściem scentralizowanym, drugi – zdecentralizowanym. Dekodowanie zdarzeń w wersji scentralizowanej składa się z rozbudowanego wyrażenia warunkowego (najczęściej konstrukcja switch, ale bywa też długa kaskada if-ów) znajdującego się w głównej pętli algorytmu z rys. 1. Po zidentyfikowaniu każdego zdarzenia, wykonywana jest odpowiadająca mu procedura. Zdecentralizowana obsługa zdarzeń, to nowsza idea, która rozpowszechniła się wraz z programowaniem obiektowym. W tym przypadku biblioteka tworząca GUI sama odbiera i przetwarza zdarzenia, a następnie mapuje je na zmiany atrybutów obiektów GUI (na przykład kliknięcie na przycisk myszą, zmienia jego atrybut na "wciśnięty"). Następnie autor programu może przypisać akcje wykonywane przez program do określonych zmian atrybutów wybranych obiektów. Robi się to poprzez ustawianie notyfikacji na obiektach.

MUI odbiera i rozpoznaje zdarzenia w sposób zdecentralizowany. Wszystke zdarzenia są tłumaczone na zmiany atrybutów różnych obiektów interfejsu. Zazwyczaj są to po prostu gadżety w oknie programu, ale niektóre zdarzenia mogą być mapowane na atrybuty obiektu okna lub obiektu aplikacji (ten ostatni nie ma widzialnej reprezentacji na ekranie). Po stworzeniu kompletnego drzewa obiektów, ale przed wejściem do głównej pętli, program ustawia notyfikacje, przypisując akcje do zmian atrybutów. Notyfikacje mogą być również tworzone i usuwane dynamicznie, jeżeli zachodzi taka potrzeba.

Notyfikacja łączy ze sobą dwa obiekty. Obiekt źródłowy wyzwala zdefiniowaną akcję po zmianie jednego z jego atrybutów. Akcja ta (jest to zawsze metoda) wykonywana jest na obiekcie docelowym. Notyfikację ustawia się wykonując metodę MUIM_Notify() na obiekcie źródłowym. Argumenty tej metody można podzielić na dwie grupy: grupę źródła i grupę celu. Ogólna postać wywołania MUIM_Notify() wygląda następująco:

DoMethod(zrodlo, MUIM_Notify, atrybut, wartosc, cel, ilosc_parametrow, akcja, /* parametry */);

Pierwsze cztery argumenty tworzą grupę źródła, pozostałe grupę celu. Całe wywołanie można "przetłumaczyć" na ludzki język następująco:

Kiedy źródło zmieni swój atrybut na określoną wartość,
wykonaj metodę akcja na obiekcie cel z parametrami.


Pozostał nam jeden nieobjaśniony argument nazwany ilość_parametrów. To po prostu ilość parametrów MUIM_Notify(), jakie występują po tym argumencie. Minimalna ilość parametrów to 1 (identyfikator metody do wykonania), górnym limitem jest jedynie zdrowy rozsądek.

Notyfikacja zostaje wyzwolona, gdy atrybut wyzwalający zostaje ustawiony na wskazaną wartość. Często chcemy wyzwolić notyfikację przy każdej zmianie atrybutu, niezależnie od wartości. Jako wartość wyzwalającą należy wtedy podać predefiniowaną stałą MUIV_EveryTime.

Akcja wykonywana na obiekcie docelowym może być dowolną metodą tego obiektu. MUI posiada kilka metod specjalnie zaprojektowanych pod kątem użycia ich w notyfikacjach. Są to metody klasy Notify, po której dziedziczą wszystkie klasy MUI:

MUIM_Set() to inny sposób na ustawienie atrybutu obiektu, używany w sytuacji gdy notyfikacja powinna ustawić atrybut obiektu docelowego. Metoda OM_SET() się tu nie nadaje, ponieważ jako argumentu wymaga taglisty zawierającej atrybuty do ustawienia. Taglista ta nie może być dynamicznie zbudowana z argumentów i w związku z tym musi być zdefiniowana oddzielnie. MUIM_Set() natomiast ustawia jeden atrybut na podaną wartość. Atrybut i jego wartość są podawane bezpośrednio jako dwa oddzielne argumenty tej metody. Notyfikacja poniżej otwiera okno po naciśnięciu przycisku.

DoMethod(przycisk, MUIM_Notify, MUIA_Pressed, FALSE, okno, 3, MUIM_Set, MUIA_Window_Open, TRUE);

Osoby dopiero poznające MUI mogą być zaskoczone, że wartością atrybutu MUIA_Pressed wyzwalającą notyfikację jest FALSE. Jest to związane z domyślnym zachowaniem się przycisków. Przycisk uruchamia akcję, gdy lewy przycisk myszy jest zwalniany, a więc na końcu kliknięcia. Atrybut MUIA_Pressed otrzymuje wartość TRUE gdy przycisk myszy jest wciskany a następnie FALSE, gdy przycisk myszy jest zwalniany, dlatego notyfikacja jest na wartość FALSE.

MUIM_NoNotifySet() działa podobnie jak MUIM_Set(), ale jest jedna istotna różnica. Ustawienie atrybutu przy pomocy tej metody nie wyzwala ewentualnych notyfikacji ustawionych na ten atrybut na obiekcie docelowym. Często ustawia się atrybuty tą metodą w celu uniknięcia zapętlania się notyfikacji, dlatego jest stosowana nie tylko jako akcja notyfikacji, ale również samodzielna metoda.

MUIM_MultiSet() daje możliwość ustawienia jednej wartości tego samego atrybutu dla wielu obiektów jednocześnie. Obiekty podaje się jako kolejne parametry metody, listę tę zakończa się wskaźnikiem zerowym. Poniższy przykład ustawia atrybut MUIA_Disabled na wartość TRUE trzem przyciskom:

DoMethod(checkmark, MUIM_Notify, MUIA_Selected, FALSE, application, 7, MUIM_MultiSet,
 MUIA_Disabled, TRUE, button1, button2, button3, NULL);

Co ciekawe obiekt będący celem notyfikacji jest tu zupełnie nieistotny, musi to być jednak wskaźnik do jakiegoś istniejącego obiektu. Najczęściej do tego celu używa się obiektu aplikacji albo źródłowego obiektu notyfikacji.

MUIM_CallHook() wywołuje funkcję programu, tzw. hooka. Metoda ta jest często nadużywana przez programistów unikających tworzenia własnych klas i implementowania funkcjonalności programu w metodach takich klas. Wywołanie wprost metody własnej klasy jako akcji notyfikacji jest prostsze i szybsze, niż wywołanie hooka poprzez MUIM_CallHook(). Dodatkowo hook wymaga zdefiniowania dodatkowych struktur - struktury Hook oraz tak zwanej "bramki" – struktury EmulLibEntry.

MUIM_Application_ReturnID() zwraca 32-bitową liczbę całkowitą do głównej pętli programu. Korzystając z tej metody można zmienić zdecentralizowaną obsługę zdarzeń w scentralizowaną. Początkujący programiści lubią nadużywać tej metody przekierowując wszystkie notyfikacje do głównej pętli programu, umieszczając tam następnie duże wyrażenie switch. Mimo, że ta technika programowania wydaje się prosta, należy jej unikać i raczej implementować funkcje programu jako metody własnych klas tworzonych jako podklasy standardowych klas MUI. Wykonywanie dużych ilości kodu wewnątrz głównej pętli MUI zwiększa czas reakcji na zdarzenia. Jedynym w pełni uzasadnionym przypadkiem użycia MUIM_Application_ReturnID() są notyfikacje powodujące wyjście z programu, używające specjalnego identyfikatora MUIV_Application_ReturnID_Quit.


Wykorzystanie wartości wyzwalającej

Kiedy akcją notyfikacji jest ustawienie wartości jakiegoś atrybutu w obiekcie docelowym, bardzo często chcemy przekazać wartość, która wyzwoliła notyfikację, do obiektu docelowego. Jest to bardzo proste, jeżeli notyfikacja jest ustawiona na jedną, konkretną wartość atrybutu. Inaczej to wygląda, gdy wyzwolenie notyfikacji następuje przy każdej zmianie wartości atrybutu (przez użycie MUIV_EveryTime). Rozwiązaniem jest użycie specjalnej stałej MUIV_TriggerValue jako pozornej wartości atrubutu przy definiowaniu notyfikacji. Przy każdym wyzwoleniu notyfikacji stała ta jest zastępowana aktualną wartością atrybutu. Druga predefiniowana stała MUIV_NotTriggerValue może być zastosowana do atrybutów logicznych (TRUE/FALSE) i zastępowana jest logiczną negacją aktualnej wartości atrybutu.

Pierwszy przykład używa MUIV_Trigger_Value, do wyświetlania wartości suwaka w gadżecie tekstowym:

DoMethod(slider, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, string, 3,
 MUIM_Set, MUIA_String_Integer, MUIV_TriggerValue);

Drugi przykład łączy notyfikacją checkmarka z przyciskiem. Gdy checkmark jest wybrany, przycisk jest aktywny. "Odznaczenie" checkmarka blokuje przycisk:

DoMethod(checkmark, MUIM_Notify, MUIA_Selected, MUIV_EveryTime, button, 3,
 MUIM_Set, MUIA_Disabled, MUIV_NotTriggerValue);

MUIV_EveryTime, MUIV_TriggerValue i MUIV_NotTriggerValue są zdefiniowane jako konkretne liczby 32-bitowe. Dlatego nie da się ustawić notyfikacji na wartość atrybutu równą 1 233 727 793 (wartość dla MUIV_EveryTime). Nie można też zdefiniować akcji polegającej na ustawieniu wartości atrubutu na obiekcie docelowym na liczbę 1 233 727 793 (MUIV_TriggerValue) ani liczbę 1 233 727 795 (MUIV_NotTriggerValue).


Zapętlone notyfikacje

Większy program w MUI może mieć zdefiniowane setki notyfikacji. Zmiana wartości jednego atrybutu może spowodować kaskadę notyfikacji. Może się okazać, że taka kaskada zawiera zamknięte pętle. Najprostszym przykładem takiej pętli jest para obiektów notyfikujących się wzajemnie. Przypuśćmy, że mamy dwa suwaki, które powinny być ze sobą sprzężone, to znaczy przesunięcie jednego suwaka powinno również przesuwać drugi. Można to osiągnąć dwiema notyfikacjami.

DoMethod(slider1, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, slider2, 3,
 MUIM_Set, MUIA_Numeric_Value, MUIV_Trigger_Value); 
DoMethod(slider2, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, slider1, 3,
 MUIM_Set, MUIA_Numeric_Value, MUIV_Trigger_Value); 

Kiedy użytkownik ustawi suwak slider1 na wartość np. 13, zostanie wyzwolona pierwsza notyfikacja, przesuwając suwak slider2 również do wartości 13. To z kolei wyzwoli drugą notyfikację, która to notyfikacja ustawi pierwszy suwak na 13, a to z kolei po raz drugi uruchomi notyfikację pierwszą... Pętla mogłaby krążyć w nieskończoność. Na szczęście MUI jest przygotowane na taką okoliczność. Rozwiązanie jest bardzo proste: jeżeli atrybut jest ustawiany na wartość równą jego wartości aktualnej, notyfikacje na ten atrybut w danym obiekcie są ignorowane. W naszym przykładzie pętla będzie przerwana po tym jak druga notyfikacja spróbuje ustawić wartość 13 (równą aktualnej) na obiekcie slider1.


Idealna pętla główna

Idealna pętla główna programu w MUI powinna praktycznie nie zawierać kodu. Wszystkie akcje programu powinny być obsługiwane notyfikacjami. Oto kod takiej pętli:

ULONG signals = 0;

while (DoMethod(application, MUIM_Application_NewInput, (ULONG)&signals)
 != (ULONG)MUIV_Application_ReturnID_Quit)
{
  signals = Wait(signals | SIGBREAKF_CTRL_C);
  if (signals & SIGBREAKF_CTRL_C) break;
}

Zmienna signals zawiera maskę bitową sygnałów wysyłanych do programu przez system operacyjny. Wartością początkową tej maski jest 0, co oznacza brak sygnałów. Kiedy metoda MUIM_Application_NewInput() zostaje wykonana na obiekcie aplikacji, MUI ustawia w tej zmiennej bity sygnałów, których się spodziewa. Z reguły są to sygnały z portów komunikacyjnych okien, gdzie Intuition umieszcza wiadomości o nadchodzących zdarzeniach. Następnie program wywołuje funkcję systemową Wait() z exec.library. Funkcja ta zatrzymuje proces do czasu nadejścia któregokolwiek sygnału, dla którego bit w podanej masce jest jedynką. W trakcie oczekiwania na sygnał proces jest wstrzymany i nie zużywa czasu procesora. Oprócz sygnałów "zamówionych" przez MUI do maski dodawany jest systemowy sygnał CTRL-C. Każdy dobrze napisany program dla MorphOS-a powinien dać się przerwać za pomocą tego sygnału. Sygnał ten może być wysłany z okna konsoli przez wciśnięcie kombinacji klawiszy CTRL + C (stąd nazwa), a także za pomocą TaskManagera i innych narzędzi. W momencie, gdy do programu nadejdzie co najmniej jeden z określonych maską sygnałów, funkcja Wait() wychodzi z powrotem do programu. W przypadku, gdy otrzymano sygnał CTRL-C, pętla ulega zakończeniu. Pozostałe sygnały są tłumaczone przez MUI na zmiany atrybutów poszczególnych obiektów, a następnie wykonywane są przypisane im notyfikacje. Wszystko to odbywa się wewnątrz metody MUIM_Application_NewInput(). Na jej zakończenie aktualizowana jest maska sygnałów. Jeżeli którakolwiek z notyfikacji wykonała na obiekcie aplikacji metodę MUIM_Application_ReturnID(), parametr tej metody jest zwracany jako wynik MUIM_Application_NewInput(). Gdy parametrem tym okaże się być stała MUIV_Application_ReturnID_Quit, pętla ulega przerwaniu.

Każdy dodatkowy kod umieszczony w głównej pętli programu wprowadza opóźnienia w obsłudze i rysowaniu GUI. Jeżeli program wykonuje jakieś zadania silnie obciążające procesor na dłuższy czas, najlepszym sposobem jest wykonywanie tych zadań w procesie potomnym. Obciążanie głównego procesu programu długotrwałymi zadaniami obliczeniowymi powoduje, że program będzie sprawiał wrażenie powolnego i nie reagującego na poczynania użytkownika.