Přehled
Příklady
Screenshoty
Srovnání
Aplikace
Stažení
Documentation
Bazar
Stav & Plány
FAQ - Často kladené dotazy
Autoři & Licence
Forum
Financování U++
Hledej na tomto webu
Jazyk
čeština













SourceForge.net Logo



Seznámení s Ultimate++

 

Malý předkrm

Ultimate++ slibuje radikální zjednodušení kódu typických aplikací. Začněme jednoduchým příkladem - aplikací, která zobrazí počet dní mezi dvěma daty. Počet dní se obnovuje kdykoliv uživatel vloží nebo změní datum ve vstupních polích:

 

Rozložení jednotlivých prvků v okně je vytvořeno pomocí vizuálního designéru přímo v TheIDE:

 

 

Zdrojový kód celé aplikace je takhle "složitý":

 

#include <CtrlLib/CtrlLib.h>

 

#define LAYOUTFILE <Days/Days.lay>

#include <CtrlCore/lay.h>

 

class Days : public WithDaysLayout<TopWindow> {

public:

    void Compute();

 

    typedef Days CLASSNAME;

    Days();

};

 

void Days::Compute()

{

    result = IsNull(date1) || IsNull(date2) ? "" :

             Format("There is %d day(s) between %` and %`",

                   abs(Date(~date1) - Date(~date2)), ~date1, ~date2);

}

 

Days::Days()

{

    CtrlLayout(*this, "Days");

    date1 <<= THISBACK(Compute);

    date2 <<= THISBACK(Compute);

}

 

GUI_APP_MAIN

{

    Days().Run();

}

 

Všechno někam patří

V Ultimate++ jsou všechny objekty svázané s určitým logickým rámcem [scope]. Díky tomu v kódu napsaném v Ultimate++ uvidíte jen velmi málo operátorů new a skoro žádné delete operátory, vyjma implementace kontejnerů.

To samozřejmě neznamená, že by operátory nebyly dovoleny, jen je dobrý zvyk používat je pouze k ukazování na objekty, nikoliv ke správě objektů na haldě. Zároveň to zabraňuje zmatkům o vlastnictví objektů, času jejich destrukce atd. Pokud potřebujete pracovat s datasety různé délky nebo polymorfními typy, měli byste dát přednost některému z Ultimate++ kontejnerů.

A když už o tom mluvíme: V Ultimate++ nejsou na úrovni rozhraní žádné chytré sdílené ukazatele (jako je třeba boost::shared_ptr), které by spravovali objekty na haldě. Nejsou potřeba a považujeme je za špatný zvyk.

V C++ se tento přístup ukazuje být stejně dobrý nebo lepší než garbage collector v jazycích jako je Java nebo C#. Zatímco tyto jazyky jsou schopny obstarat automatickou správu objektů na haldě, přístup U++ poskytuje zcela deterministickou, automatickou správu veškeré paměti.

Ultimate++ kontejnery

Jeden z aspektů Ultimate++ je často kritizován: Ultimate++ nepoužívá příliš standartní C++ knihovny. Proto jsou zde samozřejmě vážné důvody. Díky přehnanému požadavku že každý prvek uložený v kontejneru musí mít kopírovací konstruktor poněkud ztěžuje použití STL při vývoji GUI.

U Ultimate++ kontejnerů žádný takový požadavek obecně neexistuje. Namísto toho Ultimate++ poskytuje kontejnery ve dvou "příchutích".

Vector má jako podmínku přesunutelnost [Moveable], která dovoluje pro některé typy velmi rychlou implementaci (např. vložení prvku na libovolnou pozici je v Ultimate++ při použití Vector<String> více než 10x rychlejší než stejná operace s typickou implementací std::vector<std::string>).

Array nemá vůbec žádné požadavky na typ prvků za cenu o něco nižšího výkonu.

Díky tomu je v Ultimate++ například povoleno vytvořit kontejner GUI widgetů které editují celá čísla ( Array<EditInt> integer_editors) a dokonce je seřadit použitím standartního Ultimate++ Sort algoritmu. Aby bylo něco podobného možné v STL, museli by se použít ukazatele (std::vector<EditInt *>) nebo použít nějaký druh chytrých ukazatelů (již brzy std:: boost::shared_ptr), ale obojí zesložiuje kód a porušuje pravidlo Ultimate++ podle kterého všechno někam patří.

Kdo vlastní widgety

Jedna z věcí, objevená během našich nespočetných experimentů s C++ GUI je fakt, že GUI toolkit by neměl vlastnit objekty widgetů. GUI objekty by měli být vždy vlastněny klientem a patřit do nějakého rozsahu klientova kódu (všechno někam patří). GUI toolkit je pouze referencuje, ani je nevytváří, ani neničí. Každý widget může hrát svou roli v GUI v nějakém kontextu (jako být viditelný v nějakém okně), ale pořád by měl být samostatnou jednotkou se souborem vlastních atributů, které mohou být měněny nebo čteny bez ohledu na to jaký je zrovna jeho GUI statut.

Toto má několik důležitých následků. Nejdůležitějším je, že Ultimate++ nevyžaduje aby byly widgety alokované na haldě. To dále umožňuje velmi efektivní způsob uspořádání struktury GUI dialogů. Namísto:

 

struct MyDialog {

    Option *option;

    EditField *edit;

    Button *ok;

};

 

používáme:

 

struct MyDialog {

    Option option;

    EditField edit;

    Button ok;

};

 

Ještě důležitější je, že doba života těchto widgetů nezávisí na životním cyklu GUI MyDialog - MyDialog může být zavřen, nebo ještě neotevřen, ale vlastnosti widgetů jsou dostupné kdykoliv.

Šablony dilaogů jsou C++ šablony

Teď, když jsme položili základy, je na čase představit nejúžasnější aspekt programování GUI v Ultimate++ - šablony rozvržení [layout templates]:

Pokud vizuálně navrhnete layout (obvykle, ale nikoliv nutně, rozvržení dialogu) pomocí Layout designeru v TheIDE, tento layout je ve vašem kódu reprezentován jako C++ šablona, která je odvozená od třídy která která deklaruje všechny widgety jako svoje členy a také funkci (InitLayout), která nastaví polohy všech widgetů a jejich přednastavené výchozí atributy.

Taková šablona by mohla 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

 

Důvodem proč je layout poskytnut jako šablona namísto obyčejné třídy je ten, že takto použít jakýkoliv widget jako základní třídu, nejen ten který reprezentuje dialogové okno (TopWindow).

Tento přístup radikálně snižuje komplexnost - mnoho otravných věcí, které se zdají být nezbytné k identifikaci widgetu v klientském kódu, najednou zmizí navždy. Vše s čím se musíte v Ultimate++ vypořádat jsou vaše proměnné.

Value a Null

Jedním z aspektů, které dělají programování v Ultimate++ tak přímočaré, je existence Value - typu pro polymorfní hodnoty. Kterýkoliv ze základních typů v ultimate++ (int, double, String, Color, Rect, Font, Image atd.) může být uložen i přečten z Value. Value sama o sobě může být dotazována na typ který obsahuje. Také je velmi snadné udělat vlastní typy kompatibilní s Value.

S Value souvisí i obecný koncept "prázdné hodnoty" V Ultimate++ reprezentuje prázdnou hodnotu speciální konstanta Null. Většina konkrétních typů podporuje Null. Null je také definována pro fundamentální typy - int, double, int64 - jako nejnižší hodnota, kterou daný typ může vyjádřit (např. pro int je Null rovno INT_MIN). K otestování, zda je nějaká proměná Null, můžete použít generickou funkci IsNull.

Value (a Null) mají pozoruhodný efekt na flexibilitu GUI. Mnoho widgetů má svoji "přirozenou" hodnotu (pro pole pro zadávání celých čísel je to zadané číslo, pro přepínač je to true nebo false, podle toho v jakém je stavu), a Ultimate++ poskytuje jednotný přístup k těmto hodnotám pomocí Value a virtuálních metod GetData a SetData. Na příklad vymazání formuláře může být ve většině příkladů provedeno přiřazením Null všem jeho widgetům.

Display a Convert

Třídy odvozené od Display a Convert dále rozšiřují flexibilitu Ultimate++ pomocí Value.

Convert třídy fungují jako obousměrný konvertor z Value do Value. Obvykle, ačkoliv ne nutně, jde o konverzi mezi logickou hodnotou a jejím textovým vyjádřením (konverze textové hodnoty na logická typ může být občas vynechána). Příklady jsou ConvertInt nebo ConvertDate.

Mnoho Ultimate++ widgetů je schopno používat Convert třídy jako vlastnosti [properties]. Příkladem je třída EditField, obecné vstupní pole. Přiřazením specifické Convert třídy k EditField jej můžete "naučit" zpracovávat čísla, data nebo cokoliv jiného co má textovou reprezentaci.

Trochu podobné Convert jsou i třídy odvozené od Display. Tyto třídy definují jak by měly být Value zobrazovány. Opět, mnoho Ultimate++ widgetů používá Display classes jako svoje vlastnosti. Na příklad, aby jste "naučili" DropList (něco jako to čemu se říká combo box na většině ostatních platform) aby zobrazoval barvy, vše co musíte udělat je nastavit jeho atribut Display na DisplayColor (připomenutí: Color je kompatibilní s Value a DropList je seznam Value hodnot). Zároveň můžete stejný DisplayColor používat jako vlastnost mnoha dalších widgetů.

Callbacky

Zatímco virtuální metody umožňují skvělou organizaci vstupního rozhraní GUI widgetů (jako vstupy myši nebo klávesnice), každý GUI toolkit musí také poskytovat efektivní možnosti pro výstupní rozhraní (pokud nevíte co je výstupní rozhraní: když stisknete tlačítko, výstupní rozhraní je zodpovědné za doručení této informace klientovu kódu).

Naše řešení těchto potřeb se jmenuje Callback. Můžete si Callbacky představovat jako velmi zobecněnou formu ukazetelů na funkce. Každý Callback reprezentuje nějakou akci - obvykle jde o volání nějaké funkce, nebo metody nějakého objektu - který může být vykonán kdykoliv.

Callbacky jsou obecné a mohou nabývat mnoha zajimavých forem. Například jeden typ Callbacku má jednoduchou úlohu zavolat dva jiné Callbacky, což poskytuje skvělý nástroj pro seskupování. Existují Callbacky které nevyžadují argumenty, ale zavolají funkci nebo metodu s argumentem, když jsou spuštěny - tento argument navíc je uložen uvnitř Callbacku když je vytvořen. Abychom ilustrovali tento důležitý koncept, předkládáme následující ukázku kódu:

 

void MyDlg::SetEditorValue(int x)

{

    editor <<= x;

}

 

MyDlg::MyDlg()

{

    button1 <<= THISBACK1(SetEditorValue, 1);

    button2 <<= THISBACK1(SetEditorValue, 2);

 

V této ukázce máme dvě tlačítka a celočíselné vstupní pole. Stisknutí prvního resp. druhého tlačítka nastaví vstupní pole na hodnotu 1 resp. 2.

Je také velice důležité, že Callbacky jsou naprosto nezávislé na třídách. Přestože mohou volat specifické metody určitých instancí objektu, neexistují žádné požadavky na tuto metodu (krom signatury) nebo na třídu tohoto objektu.

Abych věc trochu osvětlili čtenářu znalých knihovny boost - ano, Callback je ve skutečnosti docela podobný boost::function, s rozhraním vyladěným trochu blíže potřebám Ultimate++ frameworku (jsou Moveable - mohou tedy být uloženy v kontejnerech příchuti Vector).

Sada Ultimate++ widgetů

Zatímco standartní sada U++ widgetů je pro nás méně významné než obecné principy (částečně díky tomu, že vytvoření třídy nového widgetu je v U++ často triviální), žádný popis toolkitu by bez ní nebyl kompletní.

Takže zde následuje neúplný, ale reprezentativní seznam:

Label, Button a Option jsou základní, dobře známé widgety.

Switch je něco co se obvykle nazývá "skupina přepínačů [radio-buttons]", v U++ je to jediný widget (tímto způsobem je čtení Value switche mnohem konzistentnější).

EditField, EditInt, EditDouble, EditIntSpin, EditDate, EditString jsou základní vstupní pole. Všimněte si, že Ultimate++ poskytuje pro každý typ vlastní typ pole.

LineEdit a DocEdit jsou dva typy editorů prostého textu. LineEdit pracuje s řádky, zatímco DocEdit pracuje s odstavci.

ScrollBar a ScrollBars. Zatímco jejich jména mluví za vše (ScrollBars je jen pár pozůstávající ze svislého a vodorovného ScrollBaru), je třeba podotknout, že U++ ScrollBar zároveň poskytuje veškeré výpočty pozice zobrazované oblasti.

Slider je "analogový" vstupní widget jehož hodnota je dána polohou "čudlíku".

HeaderCtrl reprezentuje hlavičky všelijakých tabulek, například ArrayCtrl

ArrayCtrl je nejspíše nejsložitější a nejkomplikovanější widget v celém Ultimate++. V podstatě jde o tabulkový widget používaný k praci nad maticemi Value. Může kombinovat Value aby byly zobrazeny (pomocí Display třídy) jako sloupce (ano, několik Value v řádě může být pomocí Convert zkombinováno do jediného sloupce) a editovány pomocí Ctrl (ty mohou být uvnitř tabulky a zobrazené při editaci uživatelem, uvnitř tabulky a zobrazené vždy, nebo mimo tabulku v dialogu zobrazujícím hodnoty právě vybraného řádku).

Option, EditString, DropList, Switch a ArrayCtrl v akci.

SqlArray je odvozené od ArrayCtrl a přidává schopnost pracovat jako editor SQL tabulky, včetně Master-detail schopností.

Splitter je používán k rozdělení widgetů s pohyblivým předělem.

ProgressIndicator může být použit k indikaci postupu dlouhých operací.

TabCtrl je používáno pro tvorbu dialogů se záložkami.

TreeCtrl zobrazuje libovolné stromové struktury.

ColorSelector, ColorPusher a ColorButton jsou widgety pro grafický výběr barvy uživatelem.

ColorButton

MenuBar a ToolBar jsou v Ultimate++ spravovány poněkud neortodoxně, neboť akce menu, reprezentované Callbacky, jsou předávány metodám které konstruují dané menu. Má to některé významné výhody - stav a přítomnost jednotlivých tlačítek nebo položek menu může být snadno měněna podle aktuálního stavu aplikace. Často je také možné použít jedinou metodu ke konstrukci obou, tedy ToolBaru i MenuBaru.

ColumnList zobrazuje položky v nastavitelném počtu sloupců.

FileList je varianta ColumnListu pro zobrazování seznamů souborů.

Konečně, Ultimate++ má také nástroje pro práci s pokročilým formátováním textu:

RichText je třída poskytující uložiště komplexních textových dokumentů, včetně fontů, formátování odstavců a dokonce i podporou pro vnořené tabulky.

RichTextView je widget pro zobrazování RichText textů.

RichEdit je plnohodnotný RichTextový textový procesor (včetně kontroly pravopisu) ve standartním balíčku widgetů, připraven k použití v jakékoliv U++ aplikaci.

RichEdit

 

Kompletní abecední seznam widgetů můžete nalézt zde.

Programování SQL

Jednou z motivací Ultimate++ byl vždy vývoj enterprise klient-server SQL aplikací. Použitím obecné filosofie Ultimate++ jsme docílili některé velmi výjimečné výsledky a v podstatě jsme učinili Ultimate++/SQL vývoj jednodušší než použití vývojových prostředí specializovaných na SQL.

Samozřjmě, SQL je oblast, kde se ohromně vyplatí abstrakce Value. Načítání hodnot z databáze a jejich vkládání do GUI widgetů nikdy nebylo tak snadné jako v Ultimate++.

Důležitý nástroj pro práci s SQL je myšlenka "SQL výrazů". SQL výraz je entita reprezentující SQL příkaz. Ultimate++ poskytuje prostředky ke konstukci SQL výrazů pomocí mechanismu C++ přetěžování [overloading]. Ultimate++ SQL výraz může vypadat například takto:

 

Select(NAME, SURNAME).From(PERSON).Where(PERSONID == personid);

 

kde NAME, SURNAME, PERSON a PERSONID jsou speciální hodnoty typu SqlId type, zatímco personid je obyčejná C++ proměná. Důležité je zde to, že SQL výrazy mohou být sestaveny z menších podvýrazů - to je obzvláště důležité při sestavování Where podmínek.

 

SqlBool where;

if(!IsNull(findname))

    where = NAME == findname;

if(!IsNull(findsurname))

    where = where && SURNAME == findsurname;

SqlExp exp = Select(PERSONID).From(PERSON).Where(where);

 

Když je SQL výraz připraven ke spuštění, může být spuštěn na objektu Sql kurzoru pomocí operátoru *. Poté, můžete načíst výsledky.

 

Sql sql;

sql * exp;

while(sql.Fetch()) {

    Sql sqlu;

    sqlu * Update(PERSON)(SALARY, SALARY + 100).Where(PERSONID == sql[0])

}

 

Jiný velmi efektivní nástroj je koncept souborů popisujících schéma databáze. Jsou to soubory popisující databázový model pomocí specializovaných C makro konstrukcí:

 

TABLE_(PERSON)

    INT_     (PERSONID) PRIMARY_KEY

    STRING_  (NAME, 200)

    STRING_  (SURNAME, 200)

    DOUBLE_  (SALARY)

END_TABLE

 

Tyto soubory jsou poté využity k synchronizaci databázového modelu na SQL serveru, ke generování SqlId constant a v neposlední řadě ke generování C++ struktur (pojmenovaných podle tabulek s předponou S_) které mohou být použity k sestavení SQL výrazů a načítání výsledků:

 

S_PERSON person;

SQL * Select(person).From(PERSON);

while(SQL.Fetch(person))

    person_table.Add(person.PERSONID, person.NAME, person.SURNAME);

 

Díky konceptu Value popsanému výše, většina widgetů se bezproblémově váže s SQL bez jakéhokoliv zásahu. Jeden z nástrojů, které využívají tyto schopnosti je třída SqlCtrls které diriguje výměnu dat mezi dialogovými widgety a záznamy databáze:

 

void EditPerson(int persionid) {

    WithPersonLayout<TopWindow> dlg;

    SqlCtrls ctrls;

    ctrls(PERSON, dlg.person)(NAME, dlg.name)(SURNAME, dlg.surname);

    SQL * Select(ctrls).From(PERSON).Where(PERSONID == personid);

    ctrls.Fetch(SQL);

    if(dlg.Run() == IDOK)

        SQL * ctrls.Update(PERSON).Where(PERSONID == personid);

}

 

Souhrn

V tomto přehledu jsme se pokusili shrnout nejúžasnější vlastnosti Ultimate++. Je zde ovšem i mnoho dalších důležitých vlastností včetně některých zajímavých implemetačních technik jako alokátor paměti s nulovou režijí, perfektní škálování obrázků atd.

Od samého začátku jsme Ultimate++ sami používali k vývoji aplikací pro naše zákazníky. I přesto jsme v posledních letech nikdy neváhali ohrozit všechen náš kód kdykoliv jsme cítili, že některá část rozhraní či implementace této knihovny je potřeba vylepšit. To nám dovolilo pomalu vytvořit knihovnu a zdokonalit ji do jejího současného stavu.

Nyní, po nějakých 7 letech vývoje, je Ultimate++ zralá platforma, která přináší ohromné snížení nákladů na vývoj. Většina rozhraní se zdá být dokončená a optimální. Samozřejmě je zde ještě mnoho práce před námi, například portování na další platformy (Mac OS X).

Pokud shledáte způsob programování nabízený Ultimate++ zajímavým, nic vám nebrání začít stahovat. Ale buďte opatrní: měli by jste být připraveni zahodit některé staré návyky a obvyklé myšlenky o tom jak se "věci vždycky dělají", protože by mohli zničit vaši šanci získat výměnou mnoho dobrého, společně s trochou zdravého opovržení vůči jistým velmi zaběhnutým vývojovým nástrojům.

 

Naposledy upravil koldo 16.09.2010. Tato stránka je také dostupná english, deutsch, français a русский. Chcete přispět?. T++