Szybki start


Ponieważ projekt jest w trakcie rozwoju i jeszcze wiele może się w nim zmienić nie ma pełnej dokumentacji. Poniżej przedstawione zostały tylko podstawowe informacje mające na celu pomoc w zrozumieniu działania przykładów i umożliwiające wykorzystanie pewnych elementów frameworku.


Tworzenie klas

Aby utworzyć klasę należy stworzyć plik NazwaKlasy.h i NazwaKlasy.c. W pierwszym z nich musi się znaleźć deklaracja klasy

proto_class (NazwaKlasy);

Dodatkowo mogą też wystąpić następujące elementy:

  • Deklaracje metod
    proto_method (PrefiksNazwaMetody, zwracany typ lub void, typ pierwszego argumentu, typ drugiego argumentu, itd.);
  • Deklaracje setterów
    proto_setter (Prefiks, NazwaWłasności, typ_własności);
  • Deklaracje getterów
    proto_getter (Prefiks, NazwaWłasności, typ_własności);

W pliku NazwaKlasy.c muszą być następujące elementy:

  • Definicja pól klasy
        class_data (PrefiksNazwaKlasy)
        {
            typ1 pole1;
            typ2 pole2;
            itd.
        };
    
  • Definicje konstruktorów
        PrefiksNazwaKlasy *PrefiksNazwaKlasyConstructor(class_arg, dalsze argumenty)
        {
            PrefiksNazwaKlasy *self = (PrefiksNazwaKlasy*) PrefixNazwaKlasyBazowejConstructor(clazz, dalsze argumenty);
    
            using_data;
    
            // Treść konstruktora
    
            return self;
        }
    
  • Definicje metod
        typ PrefiksNazwaMetody(object_arg, pozostałe argumenty)
        {
            using_data;
    
            // Treść metody
        }
    
  • Definicja klasy
        class_begin (PrefiksNazwaKlasy, PrefiksNazwaKlasyBazowej)
        {
            getter (Prefix, NazwaWłasności, nazwa funkcji realizującej odczyt własności, opcjonalnie metadane);
            setter (Prefix, NazwaWłasności, nazwa funkcji realizującej zapis własności, opcjonalnie metadane);
            method (NazwaMetody, nazwa funkcji realizującej metodę, opcjonalnie metadane);
        }
        class_end
    

Ponieważ język C nie ma przestrzeni nazw, to aby zapobiec konfliktom stosowane są prefiksy. Powinny być pisane dużymi literami i nie być zbyt długie, najlepiej 2 lub 3 znaki. Wszystkie prefiksy zawierające F jako drugą literę są zarezerwowane dla frameworku.

Czy można jeszcze prościej i szybciej utworzyć klasę. Tak! Stworzyliśmy narzędzie, je również udostępnimy w jednym z kolejnych wydań, które z definicji klasy tworzy pliki .h i .c. Po zmianie definicji klasy można je przegenerować, zmienione zostaną tylko elementy generowane, a nie te dodane przez programistę.

Przykładowy plik definicji klasy:

    class TR::TimeoutButton : AF::Button
    {
        constructor();

        object AF::ProgressBar *progressBar;
        object AF::TextView *textView;
        object AF::Animation *animation;
        type TRState state;
        type float progressBarValue;
        type float progressBarPreviousValue;

        object AF::String *actionText var set;
        object AF::String *onTimeText var set;
        object AF::String *tooLateText var set;

        override void AF::draw(object AF::Graphics *graphics);
        override void AF::mouseUp(object AF::MouseEventArguments *mouseEventArguments);
        override void AF::setupComponent(object AF::Application *application);

        void TR::reset();
    }

Wewnątrz metody dostępna jest zmienna self wskazująca na obiekt, na rzecz którego metoda została wywołana. W całym pliku *.c dostępna jest zmienna clazz wskazująca na klasę w nim zdefiniowaną. Aby uzyskać dostęp do pól klasy należy w metodzie użyć polecenia using_data; wtedy dostępna staje się zmienna data wskazująca na strukturę zawierającą dane klasy. Struktura ta jest typu PrefiksNazwaklasyData. Wewnątrz metody można wywołać metodę klasy bazowej dodając do nazwy metody sufiks Super np:

AFDrawSuper(graphics);

Aby móc używać utworzonej klasy należy ją najpierw zarejestrować używając następującego polecenia:

register_class (PrefiksNazwaKlasy);

Musi się ono wykonać zanim zostanie utworzona pierwsza instancja klasy, lub klasy która z niej dziedziczy.

Uwaga! W jednym pliku *.c może znajdować się definicja tylko jednej klasy.


Użycie klas

Aby utworzyć instancję klasy należy użyć następującej komendy:

var instancjaKlasy = PrefiksCreateNazwaKlasy(argumenty);

Wywołanie metody wygląda następująco:

PrefiksNazwaMetody(obiekt, argumenty);

Jeżeli obiekt nie implementuje danej metody to zostanie zgłoszony wyjątek.


Wyjątki

Wyjątki zgłasza się używając polecenia raise (obiektInformującyORodzajuWyjątku);

Aby złapać wyjątek należy użyć następującego polecenia:

    on_error
    {
        // Kod
    }
    resume (typ1)
    {
        // Obsługa wyjątku typu1
    }
    resume (typ2)
    {
        // Obsługa wyjątku typu2
    }

    itd.

    end_resume

W klauzuli resume, w zmiennej exception jest dostępny wskaźnik na obiekt będący parametrem polecenia raise, które wywołało wyjątek.


Kolekcje

Kolekcje to obiekty umożliwiające przechowywanie innych obiektów. AFMutableArrayList jest modyfikowalną kolekcją bazującą na tablicy, jej użycie wygląda następująco:

    var lista = AFCreateMutableArrayList(); // Utworzenie kolekcji

    AFAppendObject(lista, obiekt); // Dodanie elementu obiekt za ostatnim elementem
    var pobranyObiekt = AFGetObjectAtIndex(lista, 0); // Pobranie obiektu z pod indeksu 0
    AFRemoveObject(lista, obiekt); // Usunięcie obiektu
    size_t rozmiar = AFGetSize(lista); // Ilość elementów kolekcji

Aby pobrać po kolei wszystkie elementy kolekcji najlepiej użyć polecenia for_each

    for_each (obiekt in lista)
    {
        // Zmienna obiekt będzie zawierała kolejne elementy z kolekcji
    }

lub

    for_each (obiekt, indeks in lista)
    {
        // Zmienna obiekt będzie zawierała kolejne elementy z kolekcji
        // Zmienna indeks będzie zawierać index pobranego elementu
    }

AFMutableDictionary to słownik, czyli obiekt przechowujący pary klucz, wartość. Jego użycie wygląda następująco:

    var slownik = AFCreateMutableDictionary();

    AFAppendObjectForKey(slownik, klucza, wartość); // Dodaje wartość dla danego klucza
    var wartość = AFGetObjectForKey(slownik, klucza); // Pobranie obiektu dla danego klucza
    AFRemoveObjectForKey(slownik, klucza); // Usuwa wartość przypisaną danemu kluczowi

Komponenty

Do tworzenia programów wykorzystuje się różne komponenty. Część z nich to kontrolki UI, o których za chwilę, a część to elementy niegraficzne. Kilka z nich zostało wymienionych poniżej.

  • AFApplication odpowiada za przetwarzanie komunikatów i inne aspekty funkcjonowania aplikacji.
  • AFAnimation umożliwia animowanie obiektów wizualnych.
  • AFTimer pozwala na wywoływanie danej funkcjonalności w określonych odstępach czasu.

Interfejs użytkownika

Aby zbudować UI należy utworzyć odpowiednie kontrolki i połączyć je ze sobą. Każda kontrolka może mieć jednego rodzica. Kontrolki typu AFWindow, AFButton, AFLabel mogą mieć ustawioną treść. W wypadku AFWindow będzie to przeważnie panel, w wypadku AFButton i AFLabel będzie to tekst. Aby ustawić treść należy utworzyć obiekt, który będzie ją reprezentował i użyć metody AFSetContent(kontrolka, obiektTreści).

Nieco inaczej ma się sytuacja w wypadku paneli. Do nich można podłączyć dowolną ilość kontrolek używając polecenia AFAppendComponent(panel, kontrolka). W zależności od rodzaju panelu, zostaną one odpowiednio rozmieszczone. Jak na razie dostępny jest tylko jeden czyli AFFlowGridPanel.

Kontrolki zgłaszają zdarzenia np. mouse down, mouse up itd, aby z nich skorzystać należy użyć metody AFAddEventHandlerFunction lub AFAddEventHandlerMethod. Metoda AFAddEventHandlerFunction pobiera następujące parametry:

void AFAddEventHandlerFunction(
   AFObject *object,
   AFString *eventName,
   void *context,
   AFEventHandlerFunctionEntryPoint functionEntryPoint);
  • object - kontrolka której ma dotyczyć zdarzenie,
  • eventName - nazwa zdarzenia,
  • context - wskażnik na dowolne dane,
  • functionEntryPoint - wskaźnik na funkcję, która zostanie wywołana, gdy wystąpi zdarzenie.

Funkcja obsługi zdarzenia zostanie wywołana z następującymi parametrami:

void eventHandlerFunction(void* context, uint32_t index, AFObject *sender, AFObject *eventArguments);
  • context - wartość argumentu context przekazana w wywołaniu AFAddEventHandlerFunctionWithName,
  • index - nie używane,
  • sender - wskaźnik na kontrolkę, której dotyczy to zdarzenie,
  • eventArguments - dane dotyczące zdarzenia (np. położenie wskaźnika myszy).

Metoda AFAddEventHandlerMethod pobiera następujące parametry:

void AFAddEventHandlerMethod(AFObject *object, AFString *eventHandlerName, AFObject *object, AFString *methodName);
  • object - kontrolka której ma dotyczyć zdarzenie,
  • eventHandlerName - nazwa zdarzenia,
  • context - wskażnik na dowolne dane,
  • methodName - nazwa metody, która zostanie wywołana, gdy wystąpi zdarzenie.

Metoda obsługi zdarzenia zostanie wywołana z następującymi parametrami:

void eventHandlerMethod(AFObject *sender, AFObject *eventArguments);
  • sender - wskaźnik na kontrolkę, której dotyczy to zdarzenie,
  • eventArguments - dane dotyczące zdarzenia (np. położenie wskaźnika myszy).

Obiekt okna należy podłączyć do utworzonego wcześniej obiektu aplikacji AFApplication używając metody AFAppendComponent(obiektAplikacji, obiektOkna), gdy całe UI zostanie zbudowane metoda AFShow(obiektOkna) powoduje pojawienie się okna, a używając AFRunMainLoop(obiektAplikacji) uruchamia się przetwarzanie zdarzeń.


Zarządzanie pamięcią

Każdy obiekt ma w sobie licznik referencji, który pokazuje w ilu miejscach jest przechowywany wskaźnik na niego. Zaraz po utworzeniu licznik ten ma wartość 1. Metoda AFObtain(obiekt) zwiększa wartość tego licznika, metod AFRelease(obiekt) zmniejsza. Gdy wartość osiągnie 0 obiekt jest kasowany - zostaje wywołany jego destruktor, a pamięć jest zwalniana. Istnieje też pula obiektów do zwolnienia. Każdy obiekt jest do niej dołączany w chwili gdy zostaje stworzony. Pula dla każdego z obiektów, którego ma w swojej kolekcji, wywołuje metodę AFRelease i usuwa go z niej. Jeżeli zatem nigdy nie została wykonana operacja AFObtain, obiekt zostanie skasowany. Przetwarzanie puli obiektów odbywa się tuż przed zakończeniem działania aplikacji. Jeżeli aplikacja utworzyła obiekt AFApplication i wywołała metodę AFRunMainLoop, to obsługa puli odbywa się też po każdym przetworzeniu komunikatów czyli bezpośrednio zanim aplikacja zacznie czekać na kolejne komunikaty. Jeśli więc każdemu zapamiętaniu wskaźnika na obiekt towarzyszy AFObtain, a usunięciu tego zapamiętania AFRelease wszystkie, niewykorzystane obiekty, zostaną w odpowiednim momencie zwolnione i nie dojdzie do wycieków pamięci.

Bieżąca wersja frameworku

Biblioteka zawierająca funkcjonalność frameworku jest obecnie przygotowana do linkowania statycznego. Konsekwencją tego jest duży rozmiar plików wykonywalnych ją wykorzystujących. To zmieni się gdy biblioteka zostanie przekształcona na dynamiczną. Istnieją też duże możliwości optymalizacji szybkości działania zaimplementowanych metod i ilości zużywanej przez nie pamięci. Nie należy więc wyciągać wniosków co do tych kwestii na podstawie bieżącej wersji.