Przyjrzymy się szkieletowi aplikacji wygenerowanemu w jednym ze środowisk programistycznych i przybliżymy role wykonywane przez kolejne fragmenty kodu.
Aplikacje PalmOS są oparte na zasadzie obsługi pojawiających się w systemie zdarzeń. Zdarzeniem może być akcja wykonana przez użytkownika, bądź zaistaniała w systemie sytuacja. Wynikiem obsługi jednego zdarzenia może być narodzenie się kolejnych zdarzeń (np. dwa zdarzenia: opuszczenie i podniesienie rysika może zaowocować np. zdarzeniem uruchomienia aplikacji - w wyniku tapnięcia na ikonce Launchera).
Zdarzenie może zostać obsłużone przez naszą aplikację, system lub inną aplikację. Jeśli dane zdarzenie nie interesuje nas i nie będzie przez nas obsługiwane, jest przekazywane do systemu. Przepływ informacji o zdarzeniu jest przedstawiony na poniższym diagramie (obrazek pochodzi z podręcznika do SDK3.0).
Szkielet aplikacji PalmOS skonstruowany jest tak, by odpowiadał powyższemu modelowi przepływu informacji o zdarzeniach.
Najpierw rzućmy okiem na kod w pełnej formie, potem przejedziemy do opisywania kolejnych jego fragmentów.
// Main Include file for SDK 3.5
#include "PalmOS.h"
#include "PalmCompatibility.h"
// Main Include file for SDK 1.0, SDK 2.0, SDK 3.0, SDK 3.1
//#include "Pilot.h"
#include "@IncludeFile@.h" // Change this to: #include "YourProjectName.h"
static int StartApplication(void);
static void EventLoop(void);
static void StopApplication(void);
static Boolean frmMainEventH(EventPtr event);
//---------------------------------------------------------------------------
DWord PilotMain (Word cmd, Ptr cmdPBP, Word launchFlags)
{
int error;
if (cmd == sysAppLaunchCmdNormalLaunch)
{
error = StartApplication(); // Application start code
if (error)
return error;
EventLoop(); // Event loop
StopApplication (); // Application stop code
}
return 0;
}
//---------------------------------------------------------------------------
static int StartApplication(void)
{
FrmGotoForm(Form1004);
return 0;
}
//---------------------------------------------------------------------------
static void EventLoop(void)
{
short err;
int formID;
FormPtr form;
EventType event;
do
{
EvtGetEvent(&event, 200);
if (SysHandleEvent(&event))
continue;
if (MenuHandleEvent((void *)0, &event, &err))
continue;
if (event.eType == frmLoadEvent)
{
formID = event.data.frmLoad.formID;
form = FrmInitForm(formID);
FrmSetActiveForm(form);
switch (formID)
{
case Form1004:
FrmSetEventHandler(form, (FormEventHandlerPtr) frmMainEventH);
break;
}
}
FrmDispatchEvent(&event);
}
while(event.eType != appStopEvent);
}
//---------------------------------------------------------------------------
static void StopApplication(void)
{
//Insert stop code here
FrmCloseAllForms();
}
//---------------------------------------------------------------------------
static Boolean frmMainEventH(EventPtr event)
{
FormPtr form;
int handled = 0;
switch (event->eType)
{
case frmOpenEvent:
form = FrmGetActiveForm();
FrmDrawForm(form);
handled = 1;
break;
case ctlSelectEvent:
if (event->data.ctlEnter.controlID == Button1005 )
{
handled = 1;
}
break;
case nilEvent:
handled = 1;
break;
}
return handled;
}
PilotMain()
Jest to główna funkcja programu (odpowienik main()
z innych programów pisanych w C). Argument cmd
przenosi informację o trybie uruchomienia aplikacji. Na przykładzie widzimy tryb sysAppLaunchCmdNormalLaunch
. Ten tryb uruchomienia następuje, gdy tapniemy na ikonkę aplikacji w Launcherze, żeby ją uruchomić. Ten tryb jest zupełnie intuicyjny i nie trzeba go tłumaczyć. Inne znane nam tryby następują w sytuacjach takich jak:
- gdy wyszukujemy informacji (ikonka Find na silkscreenie) - zadaniem naszej aplikacji jest zwrócenie wszystkich rekordów odpowiadających poszukiwanemu ciągowi znaków
- gdy przechodzimy do znalezionej informacji - zadaniem naszej aplikacji jest otworzenie się na znalezionej informacji
- gdy wykonujemy HotSync lub zresetowaliśmy system - aplikacja ma możliwość wykonania kilku akcji (np. zgłoszenie swoich potrzeb do różnego typów menadżerów systemu, odpowiedzialnych za różne funkcje systemu (np. alarmy))
- inne zaistniałe zdarzenia w systemie, o których nasza aplikacja poinformowała któregoś z menadżerów (alrmów, notyfikacji), że chce być o takich informowana (tj. uruchomiona w odpowiednim trybie po zajściu zgłoszenia)
Tak więc widzimy, że nasza aplikacja może być częściej uruchamiana niż jesteśmy to w stanie dostrzec. (BTW: Jeśli w chwil restartu systemu lub odtwarzania backupów widzicie błąd krytyczny, tzn. że w systemie występuje źle napisana aplikacja, która zostaje uruchomiona zaraz po restarcie - błąd nie wynika z uszkodzenia systemu lub sprzętu).
W przykładzie główny blok programu upewnia się, czy został uruchomiony w trybie normalnym. Potem wykonuje czynności związane z przygotowaniem się do uruchomienia aplikacji (StartApplication()
), jej obsługą (pętla EventLoop()
). Przed zamknięciem aplikacji wykonuje blok StopApplication()
- a więc jest to moment np. tapnięcia na ikonkę Applications na silkscreenie.
DWord PilotMain (Word cmd, Ptr cmdPBP, Word launchFlags)
{
int error;
if (cmd == sysAppLaunchCmdNormalLaunch)
{
error = StartApplication(); // Application start code
if (error)
return error;
EventLoop(); // Event loop
StopApplication (); // Application stop code
}
return 0;
}
StartApplication()
W poprzednim bloku programu ustaliliśmy, że nasza aplikacja ma się uruchomić w trybie normalnym. Teraz przystępujemy do jej faktycznego uruchomienia. W bloku StartAppliaction()
powinnien znaleźć się kod przygotowywujący program do pełnego działania. W przykładzie-szkielecie jedynym zadaniem jest otwarcie głównej formy programu. Ale jest to również doskonałe miejsce na odczytanie zapisanych Preferencji programu, czy otwarcie dostępu do bazy danych.
Wspomniane Preferencje to mechanizm na wygodne zapamiętywanie ustawień programu. Możemy poprosić system o przydzielenie pewnego obszaru pamięci, związany z aplikacją, w którym możemy przechowywać struktury danych opisujące stan programu. Zauważ, jak zachowują się wbudowane aplikacje PalmOS (notatnik, kalendarz, etc.). Kiedy przełączasz się pomiędzy tymi aplikacjami, masz wrażenie, że nie są one zamykane, ponieważ w momencie ich ponownego wybrania zastajesz je w pozostawionym stanie. Jest to tylko pozór. Te aplikacje są faktycznie zamykane. Powrót do poprzedniego stanu odbywa się poprzez zapamiętanie go jako Preferencji. Jeśli nie oprogramujesz takiego zachowania, Twoja aplikacja zawsze będzie uruchamiała się "od początku".
static int StartApplication(void)
{
FrmGotoForm(Form1004);
//otwiera formę, Form1004 jest nazwą zasobu (tj. tej formy),
//przypisaną w środowisku programistycznym; pod tą nazwą kryje się numer zasobu
//niektore numery zasobów są zarezerwowane (np. na trzymanie nazwy aplikacji)
return 0;
}
EventLoop()
Jest to strategiczny blok aplikacji, który odpowiada za dystrybuowanie zdarzeń w systemie po funkcjach je obsługujących. Jest to o tyle sympatyczna funkcja, że nie wymaga od programisty wprowadzania żadnych poprawek, poza wyjątkiem podbloku switch(formID)
. W tym podbloku wykonuje się kojarzenie ładowanych do pamięci form z funkcjami je obsługującymi. Dzięki takiemu "podczepieniu" funkcji, wszystkie zdarzenia, ktore nastąpią w ramach danej formy, będą obsługiwane przez wybraną funkcję.
static void EventLoop(void)
{
short err;
int formID;
FormPtr form;
EventType event;
do
{
EvtGetEvent(&event, 200);
//pobieranie kolejnego zdarzenia z kolejki zdarzeń; gdy w kolejce nie ma zdarzeń,
//zwracane jest zdarzenie puste
if (SysHandleEvent(&event))
//za obsługę częśći zdarzeń odpowiedzialny jest system;
//nasza aplikacja nie ma nic do tego
continue;
if (MenuHandleEvent((void *)0, &event, &err))
//obsługa zdarzeń związanych z wyświetlaniem i obsługą menu
continue;
if (event.eType == frmLoadEvent) //jeśli zdarzeniem jest otwarcie nowej formy..
{
formID = event.data.frmLoad.formID;
form = FrmInitForm(formID);
FrmSetActiveForm(form);
switch (formID)
{
case Form1004:
FrmSetEventHandler(form, (FormEventHandlerPtr) frmMainEventH);
//skojarzenie formy z obsługującą
//ją (jej zdarzeia) funkcją
break;
}
}
FrmDispatchEvent(&event);
//przekazanie zdarzenia do funkcji obsługującej daną formę
//lub domyślna obsługa zdarzenia
}
while(event.eType != appStopEvent);
//pętlę może zakończyć tylko zdarzenie zatrzymywania aplikacji
}
StopApplication()
Blok analogiczny do bloku StartApplication()
. Doskonały moment na zachowanie wszyskich niezapisanych informacji. Czyli zapisanie Preferencji, zrzucenie ostatnich zmian do baz i ich zamknięcie.
static void StopApplication(void)
{
//Insert stop code here
FrmCloseAllForms();
}
frmMainEventH(EventPtr event)
Blok obsługujący główną formę. Zadaniem tej funkcji jest reakcja na różne zgłoszone przez system zdarzenia związane z daną formą.
static Boolean frmMainEventH(EventPtr event)
{
FormPtr form;
int handled = 0;
switch (event->eType) //w zależności od zdarzenia, które nastąpiło..
{
case frmOpenEvent: //otwarcie formy
form = FrmGetActiveForm(); //pozyskanie wskażnika do aktywnej formy
FrmDrawForm(form);
//narysowanie formy na ekraniku;
//parametrem jest wskaźnik - operacje na wszystkich
//elementach interfejsu graficznego wywoływane są poprzez wskzaźniki
handled = 1;
break;
case ctlSelectEvent: //tapnięcie na elemencie interfejsu graficznego
if (event->data.ctlEnter.controlID == Button1005 )
//jesli było to tapnięcie na przycisku; Form1005 to numer
//zasobu, który jest owym przyciskiem
{
handled = 1;
}
break;
case nilEvent:
handled = 1;
break;
}
return handled;
//zwracana wartość mówi, czy zdarzenie zostało obsłużone,
//czy nie trzeba go obsłużyć na sposób domyślny;
//czasami warto wykonać sztuczkę, w której funkcja faktycznie obsługuję zdarzenie,
//natomiast nie raportuje go jako obsłużonego;
//wtedy możliwe jest, żeby po zajściu zdarzenia wykonać
//jakieś dodatkowe czynności, a finalnie i tak wykorzystać
//domyślną funkcję do obsługi zdarzenia
}
Analogiczną funkcją należy stworzyć do każdej umieszczonej w aplikacji formy. Wyjątkiem są proste statyczne formy, których rolą jest drobna interakcja z użytkownikiem (czyli np. wyświetlenie okienka "O programie" czy menu w formie przycisków) - takie formy mogą być wyświetlane i obsługiwane przez istniejące ku temu w systemie funkcje.
To wszystko. Ponieważ rolą tego artykułu miało być jedynie powierzchowne przybliżenie tematu, zapraszam do zadawania pytań pod artykułem lub jak zawsze na forum programistów PalmPage.pl .