Servis

Mirek Fídler - Vývoj aplikací (6:00 | 24.6.)  Jagg.cz  linkuj.cz

GUI v Ultimate++

V minulé části vyprávění o U++ jsme prošli základními principy designu U++. Nyní je na čase se ponořit do oblasti, která většinu nově příchozích uživatelů zajímá nejvíce: GUI.

1. Čí jsou GUI elementy

Podstatnou vlastností GUI v Ultimate++ je fakt, že GUI elementy (widgety) nejsou vlastněny samotnou GUI knihovnou. Jinak řečeno, knihovna nikdy sama objekty GUI elementů nemaže ani nevytváří, to je ponecháno na kódu aplikace. Samotné GUI elementy jsou přitom na GUI celkem nezávislé, dalo by se říct že element může hrát svoji roli GUI, ale existuje nezávisle na ní před tím než se objeví na obrazovce jako součást GUI i potom. Většinu vlastností a hodnot elementu tak lze měnit a číst zcela nezávisle.

Důležitou praktickou implikací je to, že GUI elementy není třeba vytvářet na heapu. Dialog lze tudíž zcela jednoduše vyjádřit jako obyčejnou C++ strukturu

struct MyDialog {
  Option option;
  EditField edit;
  Button ok;
};

2. Šablony dialogů jsou C++ šablony

Od tohoto jednoduchého způsobu vyjádření GUI hierarchie je už jenom maličký krůček k tomu nejzábavnějšímu aspektu GUI v U++ - šablon rozvržení.

Vývojové prostředí pro U++, TheIDE obsahuje vizuální návrhář rozvržení dialogu, přičemž výsledek tohoto návrhu se v U++ programu reflektuje jako C++ šablona.

Tato šablona je zkonstruována tak, že dědí z libovolné třídy a přidává k ní všechny GUI elementy z vizuálního návrhu. Zároveň se grafické rozmístění prvků reprezentuje kódem ve speciální funkci InitLayout.

Taková šablona může vypadat například takto:

template <class T>
struct WithMyDialogLayout : public T {
  Option option;
  EditField edit;
  Button ok;
};

template <class T>
void InitLayout(WithMyDialogLayout<T> *layout, ...);
// implementation details omitted

Hlavním důvodem proč zde používáme šablony místo jednoduché třídy je možnost použít různé bázové třídy. Šablona rozvržení je tak univerzálně použitelná, nejčastěji je sice použita se třídou hlavního okna, čímž se zformuje klasický dialog, ale lze ji použít třeba i pro jednotlivé záložky "zápisníku" nebo pro formování jiného GUI elementu.

Tento přístup vede k radikálnímu zjednodušení GUI kódu, protože zcela odstraňuje řadu obtěžujících záležitostí které jsou v jinde spojeny s identifikací jednotlivých součástí GUI. V U++ jsou všechny GUI elementy přímo přístupné jako jako přímé proměnné.

3. Value a Null

Důležitou součástí U++ je datový typ reprezentující hodnotu. Všechny hodnotových typů v U++, jako int, double, String, Color, Rect, Image a další, je možné uložit do Value a načíst z Value (pokud příslušný případně kompatibilní typ obsahuje). Value také poskytuje informaci o typu hodnoty kterou obsahuje.

Souvisejícím je koncept "prázdné hodnoty" reprezentovaný speciální konstantou Null. Velká část hodnotových typů v U++ tento koncept podporuje. Null je také "dodatečně" definován pro některé fundamentální typu, třeba pro int je definován jako hodnota INT_MIN. Pomocí přetížené funkce funkci IsNull lze otestovat jestli příslušná hodnota je prázdná.

Většina GUI elementů má nějakou svojí přirozenou hodnotu, třeba editační pole datumu má přirozenou hodnotu typu Date. Existence Value umožňuje mimo jiné zavést jednotný přístup k této hodnotě pomocí metod

virtual void   SetData(const Value& data);
virtual Value  GetData() const;

Programování GUI je tak opět o něco jednodušší a intuitivnější. Například smazání obsahu dialogu lze intuitivně dosáhnout přiřazením Null do všech jeho GUI elementů.

4. Display a Convert

Koncept Value dále rozvíjejí třídy odvozené od základních tříd Convert a Display.

Třída Convert je definována jako

class Convert {
public:
    virtual Value  Format(const Value& q) const;
    virtual Value  Scan(const Value& text) const;
    .....
};

a zprostředkovává dvousměrnou konverzi Value. Většinou, ale ne nutně, se jedná o konverzi z hodnoty na text (Format) a zpět (Scan). Convert se v GUI uplatní třeba u vstupních editačních polích - EditField. EditField má referenci na Convert jako jeden ze svých atributů, vhodným nastavením lze instanci EditField upravit tak že je schopna zpracovávat libovolný datový typ.

Třída Display hodnotu zobrazuje v zadaném obdélníku.

class Display {
public:
    .....
    virtual void Paint(Draw& w, const Rect& r, const Value& q,
                   Color ink, Color paper, dword style) const;
    .....
};

Display se používá pokud je třeba nějakým způsobem zobrazit v rámci elementu GUI hodnotu. Například, nabídku DropList je možné přiřazením odvozené třídy "DisplayColor" upravit tak, že místo textu bude zobrazovat barvy.

5. Callback

Zatímco virtuální metody představují rozumný způsob jak se vypořádat se vstupnímu GUI událostmi, jako jsou akce myši nebo klávesnice, určitým problémem bývá řešení výstupních událostí, například pokud uživatel zmáčkne tlačítko v dialogu, tlačítko musí tuto skutečnost nějak oznámit kódu aplikace.

U++ zde používá množinu tzv. Callback tříd. Callback by se dal označit jako zobecněný pointer na funkci, hodnota představující akci kterou lze kdykoliv vyvolat. Většinou je touto akcí volání metody nějakého objektu nebo funkce.

Tyto třídy jsou obecné a mají některé zajímavé formy. Například je možné vytvořit bezparametrový Callback který vyvolává metodu s jedním parametrem; tento parametr je pak uložen v samotné Callback hodnotě:

void MyDlg::SetEditorValue(int x)
{
    editor <<= x;
}

MyDlg::MyDlg()
{
    button1 <<= THISBACK1(SetEditorValue, 1);
    button2 <<= THISBACK1(SetEditorValue, 2);

V tomto příkladu máme v dialogu dvě tlačítka, jejichž stisknutí uloží do editačního pole hodnotu 1 resp. 2.

6. GUI elementy

I když příklady standardních U++ GUI elementů a výčet jejich možností je méně důležitý než popis principů, myslím že je pro to ta pravá chvíle:

  • Label, Button a Option nemá smysl asi vysvětlovat. Jedná se o jednoduchý neinteraktivní nápis, tlačítko a zaškrtávací volbu).

  • Switch je výčet voleb z nichž pouze jedna může být vybrána ("radio-buttons"). Zajímavé je, že v U++ je tento přepínač řešen jako jediný prvek.

  • EditField, EditInt, EditDouble, EditIntSpin, EditDate, EditString jsou příklady vstupních editačních polí. Ve skutečnosti je veškerá logika v EditField, ostatní vstupní pole jsou z něj vyrobena pomocí příslušných Convert tříd. Povšimněte si také, že U++ poskytuje pro každý typ vstupního údaje zvláštní třídu.

  • LineEdit a DocEdit jsou dva typy editoru neformátovaného textu. LineEdit je orientován řádkově zatímco DocEdit odstavcově.

  • ScrollBar a ScrollBars jsou známé posuvníky, v U++ zajišťují kompletní výpočet posunutí pohledu na základě celkové velikosti dokumentu, velikosti pohledu a pozice posuvníku.

  • HeaderCtrl slouží jako hlavička různých tabulek.

  • ArrayCtrl je jedním z nejsložitějších GUI elementů U++. Jedná se v podstatě o tabulku Value s tím, že hodnoty se mohou nejrůznějším způsobem kombinovat, měnit pomocí Convert a zobrazovat pomocí Display.

Option, EditString, DropList, Switch a ArrayCtrl

  • SqlArray je odvozeno od ArrayCtrl a přidává práci s tabulkami.

  • Splitter se používá na nastavitelné rozdělení oken na podoblasti.

  • ProgressIndicator se používá na znázornění postupu operací.

  • TabCtrl slouží k implementaci záložek v dialozích.

  • TreeCtrl je pro zobrazování hierarchických dat.

  • ColorSelector, ColorPusher a ColorButton slouží k výběru barvy.

ColorButton

  • MenuBar a ToolBar reprezentují klasické menu a pruh nástrojů. U++ řeší problematiku menu a nástrojů netradičně - jsou reprezentovány kódem, většinou metodou a stejný kód lze použít pro MenuBar i ToolBar.

  • ColumnList zobrazuje hodnoty jako se seznam formátovaný v několika sloupcích.

  • FileList je odvozený z ColumnList a je určen k zobrazování jmen souborů.

  • RichText, RichTextView a RichEdit jsou třídy pro práci s formátovaným textem, včetně wordprocessingu (RichEdit)..

RichEdit

Úplný seznam GUI elementů lze nalézt na stránkách projektu.

7. Příště

Po prvním náhledu na GUI se příště podíváme jakým způsobem se v U++ pracuje s SQL databázemi.

Diskuze