Vue d'ensemble
Exemples
Captures d'écrans
Comparaisons
Applications
Télécharger
Documentation
Bazaar
État et Feuille de route
Foire aux questions
Auteurs & licence
Forums
Financement de U++
Recherche sur ce site
Langue
français













SourceForge.net Logo



Tour d'horizon de U++

 

Note : cette traduction est en cours et donc partielle. Le texte non traduit est inséré dans sa version originale (en anglais). Toute contribution est bienvenue évidemment...

Pour se mettre en appétit...

U++ promet une réduction radicale de la complexité du code pour des applications de bureau typiques. Commençons avec un exemple simple : une application qui affiche le nombre de jours entre deux dates. Le nombre de jours est rafraîchi en temps réel lorsque l'utilisateur entre ou édite l'une des dates dans les champs de texte :

 

 

La disposition de la fenêtre de l'application est élaborée avec le Designer Visuel U++ :

 

 

Le code complet de l'application (hormis le code généré par le Designer Visuel) est le suivant :

 

#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();

}

 

Tout appartient à quelque chose

Dans U++, la plupart des objets sont liés à une portée logique. En conséquence, vous ne verrez pas beaucoup d'opérateurs new dans du code utilisant U++, et pratiquement aucun opérateur delete en dehors de l'implémentation des conteneurs.

Cela ne signifie évidemment pas qu'il est interdit d'utiliser des pointeurs, mais c'est une bonne pratique d'utiliser des pointeurs uniquement pour pointer des choses et non pour gérer des ressources allouées sur le tas (heap). Cela évite aussi des confusions concernant l'appartenance de l'objet sous-jacent, la date de sa destruction, etc. Si vous devez gérer des ensembles de donnée de taille variable ou de type polymorphe, vous devriez préférer utiliser l'un des conteneurs U++.

A propos de ce sujet, il n'y a pas au sein d'U++ de "pointeur intelligent" (smart pointer) dans la lignée de boost::shared_ptr pour manipuler des resources sur le tas. Ils ne sont pas nécessaires et sont considérés comme une mauvaise pratique.

En C++, cette approche tend à être égale à ou meilleure que des langages intégrant un "ramasse-miettes" comme Java ou C#. Tandis que ces langages sont capables de fournir une gestion automatisée des resources du tas, l'approche U++ fournit une gestion automatique très déterministe de toutes les ressources.

Les conteneurs U++

Un aspect spécifique de U++ amène beaucoup de critiques : U++ utilise peu la bibliothèque standard C++. Il y a cependant de sérieuses raisons à cela. La STL, avec son pré-requis dévastateur selon lequel chaque élément stocké dans un conteneur doit avoir un constructeur par copie, rend les conteneurs standards relativement difficiles à utiliser lors du développement d'interfaces graphiques.

Il n'y a aucun pré-requis général de ce genre dans les conteneurs U++. En contrepartie, les conteneurs U++ sont proposés dans deux différentes versions.

La version Vecteur (Vector) a un pré-requis "Déplaçable" (Moveable) qui autorise une implémentation extrêmement rapide pour certains types (par exemple, l'insertion d'éléments à une position arbitraire dans un vecteur U++ Vector<String> est plus de 10 fois plus rapide que la même opérations avec une implémentation typique de std::vector<std::string>).

La version Tableau (Array) n'a absolument aucun pré-requis pour les types d'éléments, au prix d'une performance légèrement moindre.

Ainsi, avec U++ vous êtes par exemple autorisé à créer un conteneur de widgets graphiques qui éditent des nombres entiers ( Array<EditInt> integer_editors) et même de les trier avec l'algorithme U++ standard Sort. Faire une chose similaire avec la STL nécessiterait l'utilisation de pointeurs comme éléments (std::vector<EditInt *>) ou alors une sorte de pointeur intelligent (boost::shared_ptr, bientôt std::), mais les deux solutions augmenteraient la complexité du code et invalideraient la règle U++ selon laquelle tout doit appartenir à quelque chose.

Qui possède les composants/widgets

Une des choses que nous avons découvertes lors de nos innombrables expérimentations avec les interfaces graphiques en C++ est le fait que le toolkit graphique ne devrait pas posséder les objets composants. Les objets graphiques devraient toujours être possédés par le client, et avoir une portée limitée à une partie du code du client (tout appartient à quelque chose). Le toolkit graphique doit simplement les référencer, mais ne doit ni les créer, ni les détruire. Chaque objet composant peut jouer sont rôle graphique dans un certain contexte (comme être visible dans une fenêtre), mais en même temps c'est toujours une entité indépendante avec son propre jeu d'attributs qui peut être modifié ou demandé indépendamment de son statut graphique courant.

Cela a beaucoup d'implications sérieuses. La plus importante est que U++ ne demande pas aux composants d'être alloués sur le tas. En retour, cela nous permet d'arranger la structure des boîtes de dialogue graphiques d'une manière très efficace. Au lieu d'écrire :

 

struct MyDialog {

    Option *option;

    EditField *edit;

    Button *ok;

};

 

Nous utilisons ceci :

 

struct MyDialog {

    Option option;

    EditField edit;

    Button ok;

};

 

Encore plus important, la durée de vie de ces composants ne dépend pas de la durée de vie de l'objet graphique MyDialog - MyDialog peut être fermée ou pas encore ouverte, les attributs de ses composants sont accessibles tout le temps.

Les modèles de boîtes de dialogue sont des modèles C++

Maintenant que nous avons posé les fondations, il est temps d'introduire l'un des aspects les plus géniaux de la programmation graphique U++ - les modèles de disposition.

Si vous définissez visuellement une disposition (souvent la disposition d'une boîte de dialogue, mais pas seulement) en utilisant le Layout designer de TheIDE, cette disposition est reflétée dans votre code comme un modèle C++ dérivant d'une classe de base de composant et déclarant ses composants comme des variables membres, et une fonction associée (InitLayout) qui configure la position des composants et les valeurs par défaut des attributs pré-définis.

Par exemple, un tel modèle pourrait ressembler à cela :

 

template <class T>

struct WithMyDialogLayout : public T {

    Option option;

    EditField edit;

    Button ok;

};

 

template <class T>

void InitLayout(WithMyDialogLayout<T> *layout, ...);

// implementation details omitted

 

La raison pour laquelle cette disposition est fournie comme un modèle et non une simple classe ou structure est que ainsi, vous pouvez utiliser n'importe quel type de composant comme classe de base, pas seulement celui qui représente une fenêtre de dialogue (TopWindow).

Cette approche permet une réduction radicale de la complexité : tous les détails contraignants qui semblent d'habitude nécessaires pour identifier les composants dans le code client (comme des IDs ou des noms de composant) sont oubliés pour de bon. Tout ce qui reste à gérer avec U++, ce sont vos variables d'instance.

Value et Null

Un aspect qui rend le développement avec U++ très orthogonal est l'existence de Value, un type de valeur polymorphe. N'importe quel type basique de U++ (int, double, String, Color, Rect, Font, Image etc...) peut être stocké et récupéré dans une Value. Value peut être directement adressée afin de savoir le type de valeur qu'elle contient. Il est de plus très facile de créer un type personnalisé compatible avec Value.

Associé à Value, existe le concept général de "valeur vide". La constante U++ spéciale Null représente une valeur vide. La plupart des types concrets supportent Null. Null est aussi défini pour les types fondamentaux - int, double et int64 - comme une valeur inférieure à toute autre valeur d'un type spécifique (par exemple, Null vaut INT_MIN pour un int). Pour tester si une variable d'un certain type est vide, vous pouvez utiliser la fonction générique IsNull.

Value (et Null) ont un effet remarquable sur la flexibilité des interfaces graphiques. Beaucoup de composants ont logiquement des valeurs "naturelles" (pour un champ d'édition d'entiers c'est le nombre entré, pour un composant d'option c'est true ou false selon son état) et U++ fournit un accès uniforme à ces valeurs à travers Value et les méthodes virtuelles GetData/SetData. Par exemple, nettoyer une boîte de dialogue peut être fait en assignant Null à tous ses composants.

Display et Convert

Les classes basées sur Display et Convert ajoutent encore de la flexibilité lors de l'utilisation de Value.

Les classes Convert sont des convertisseurs bidirectionnels Value vers Value. Souvent mais pas seulement, cette conversion s'effectue entre la valeur d'un type logique et sa représentation textuelle (La conversion de la représentation textuelle vers le type logique peut parfois être omise). ConvertInt et ConvertDate en sont des exemples.

Beaucoup de composants U++ sont capables d'utiliser ces classes Convert en tant que propriétés. Un exemple est la classe EditField, un champ d'entrée générique. En assignant une classe basée sur Convert à un EditField, on peut lui "apprendre" à éditer des nombres, des dates ou tout ce qui a une représentation textuelle.

Dans la même lignée que Convert, on trouve les classes basées sur Display. Ce sont des classes qui décrivent comment les Values doivent être affichées. Une fois encore, beaucoup de composants graphiques U++ utilisent des classes Display comme propriétés. Par exemple, pour "apprendre" au composant DropList (c'est un composant proche de ce qui est communément appelé "combo box" sur d'autres plateformes) à afficher des couleurs, tout ce qu'il faut faire est d'assigner son attribut Display à DisplayColor (souvenez-vous, Color est compatible avec Value et la liste d'une DropList est constituée de Values). Inversement, vous pouvez utiliser ce même DisplayColor comme propriété de beaucoup d'autres classes de composants.

[ end of current traduction ]

Callbacks

Tandis que les méthodes virtuelles sont un excellent moyen d'organiser les interfaces d'entrée des composants graphiques (comme la souris ou le clavier), chaque toolkit graphique doit aussi fournir un moyen efficace pour gérer les interfaces de sortie (si vous ne voyez pas ce qu'est une interface de sortie : lorsqu'un bouton est pressé, l'interface de sortie est responsable de fournir cette information au code client).

Une solution à ces besoins est appelée une Callback. Vous pouvez vous représenter une Callback comme une forme très généralisée de pointeur de fonction. Chaque Callback représente une sorte d'action - souvent, cela comprend l'appel d'une function ou d'un méthode d'objet spécifique - qui peut être exécutée à tout moment.

Les Callbacks sont génériques et peuvent prendre des formes très intéressantes. Par exemple, un type de Callback effectue la simple tâche d'appeler deux autre Callbacks données, en fournissant un outil de regroupement très simple. Il existe des Callbacks qui ne prennent pas d'argument, mais appellent une fonction ou méthode avec un argument lorsqu'elles sont appelées - cet argument additionnel est stocké dans la Callback lors de sa construction. Pour illustrer cette fonctionnalité importante, regardez le code suivant :

 

void MyDlg::SetEditorValue(int x)

{

    editor <<= x;

}

 

MyDlg::MyDlg()

{

    button1 <<= THISBACK1(SetEditorValue, 1);

    button2 <<= THISBACK1(SetEditorValue, 2);

 

Dans cet extrait, nous avons deux boutons et un champ d'entrée d'entiers. En pressant le premier ou le second bouton, on met à jour le champ d'entrée avec la valeur 1 ou 2 respectivement.

Il est aussi très important que les Callbacks soient totalement découplées des classes. Bien qu'elles appellent des méthodes spécifiques de certaines instances d'objet, il n'y a aucun autre pré-requis pour la méthode (en dehors de sa signature) ou la classe de l'objet.

Pour rendre les choses claires à ceux qui sont familiers avec les bibliothèques Boost - oui, les classes Callback sont très similaires à boost::function, avec une interface légèrement adaptée pour les besoins du framework U++ ( elles sont Moveable - peuvent être stockées dans des conteneurs de type Vector).

Le jeu de composants graphiques U++

Bien que le jeu de composants U++ est à nos yeux moins important que les principes généraux (partiellement dû au fait que créer de nouvelles classes de composant est une tâche triviale avec U++), toute description du toolkit serait incomplète sans lui.

Voici donc une liste incomplète mais représentative :

Label, Button et Option sont des composants basiques bien connus.

Switch est souvent appelé "un groupe de boutons-radio", mais ici c'est un composant unique (ainsi, lire la valeur (sous forme de Value évidemment) d'un Switch est beaucoup plus logique).

EditField, EditInt, EditDouble, EditIntSpin, EditDate, EditString sont des champs d'entrée basiques. Notez que U++ fournit des types distincts de champs d'entrée pour des types de valeur spécifiques.

LineEdit et DocEdit sont deux types d'éditeur de texte simple. LineEdit fonctionne par ligne tandis que DocEdit fonctionne par paragraphe.

ScrollBar et ScrollBars. Bien que leurs noms soient explicites (ScrollBars est juste une paire consituée d'une Scrollbar horizontale et d'une verticale), il est important de noter que la ScrollBar U++ fournit aussi tous les calculs pour la position de la zone de vue associée.

Slider est un composant d'entrée "analogique" dont la valeur est déterminée par la position du curseur.

HeaderCtrl represents headers of various tables, namely ArrayCtrl

ArrayCtrl is perhaps the most complex and complicated widget in Ultimate++. It is basically a table widget used to operate on Value matrices. It can combine Values to be displayed (using Display class) as columns (yes, several Values in row can be combined into single a column using Convert if needed) and edit them using slave Ctrls (those can be inside the table displayed on user "edit" action, inside the table always visible or outside the table in the dialog box displaying Values of currently selected line).

Option, EditString, DropList, Switch and ArrayCtrl in action.

SqlArray is derived from ArrayCtrl and adds abilities to act as SQL table editor, including master-detail capabilities.

Splitter is used to implement split view widgets with an adjustable bar.

ProgressIndicator can be used to indicate progress of lengthy operations.

TabCtrl is used for dialogs with tabs.

TreeCtrl is used to display arbitrary tree hierarchies.

ColorSelector, ColorPusher and ColorButton are widgets for graphical user color selection.

ColorButton

MenuBar and ToolBar handling is a little unorthodox in Ultimate++, as the menu actions, represented as Callbacks, are passed to the methods constructing the corresponding Bar. This has some serious advantages - state and presence of individual buttons or menu bar items can be easily adjusted according to the current application state. It is also often possible to have a single method for construction of both ToolBar and MenuBar.

ColumnList displays values in user-adjustable number of columns.

FileList is variant of ColumnList for displaying lists of files.

Finally, Ultimate++ has tools to deal with advanced text formatting:

RichText is a class that provides storage of complex text documents, including font and paragraph formatting and even nested tables support.

RichTextView is a widget for viewing RichText texts.

RichEdit is a full-featured RichText word-processor (including spell-checker) in standard widget package, readily available to any U++ application.

RichEdit

 

You can find complete alphabetical list of basic U++ widgets here.

SQL programming

One of the motivations behind Ultimate++ always used to be the development of enterprise class client-server SQL applications. Using general Ultimate++ philosophy we believe to have achieved some extraordinary results, basically making Ultimate++/SQL development easier that using SQL dedicated development tools.

Of course, SQL is an area where the Value abstraction hugely pays off. Fetching database values and putting them to GUI widgets never was as trivial as it is in Ultimate++.

An important tool related to SQL is idea of "SQL expressions". SQL expression is entity that represents SQL command. Ultimate++ provides means to build SQL expression using C++ overloading mechanism. For example, Ultimate++ SQL expression might look like:

 

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

 

where NAME, SURNAME, PERSON and PERSON are special values of SqlId type, while personid is an ordinary C++ variable. The important thing here is that SQL expressions can be built from smaller subexpressions - that is particulary important when building Where conditions.

 

SqlBool where;

if(!IsNull(findname))

    where = NAME == findname;

if(!IsNull(findsurname))

    where = where && SURNAME == findsurname;

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

 

When SQL expression is ready for execution, it can be executed on an Sql cursor object using the * operator. After this, you can Fetch the results.

 

Sql sql;

sql * exp;

while(sql.Fetch()) {

    Sql sqlu;

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

}

 

Another effective tool is the concept of database schema description files. These are files used to describe a database model using specialized C-macro constructions:

 

TABLE_(PERSON)

    INT_     (PERSONID) PRIMARY_KEY

    STRING_  (NAME, 200)

    STRING_  (SURNAME, 200)

    DOUBLE_  (SALARY)

END_TABLE

 

These description files are then used to synchronize the database model on the SQL server, to generate SqlId constants used in SQL expressions, and, last but not least, to generate C++ structures (named after the tables with S_ prefix) that can be used to form SQL expressions and to fetch query results:

 

S_PERSON person;

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

while(SQL.Fetch(person))

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

 

Thanks to the Value concept described above, most widgets couple seamlessly with SQL code out of box. One of the tools that exploit these capabilities is the SqlCtrls class that orchestrates the data interchange between dialog widgets and database records:

 

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);

}

 

Summary

In this overview we have tried to summarize the most exciting features of Ultimate++. There are of course many more important features including certain interesting implementation techniques like zero overhead memory allocator, perfect image rescaling etc.

Since the very beginning we have kept using Ultimate++ ourselves to develop applications for our customers. Even so, in recent years we never hesitated to compromise our entire code-base each time we felt that some major or minor aspect of library interface or implementation needed to be improved. This enabled us to slowly develop the library and perfect it into its current state.

Now, after some 11 years of development, Ultimate++ is a mature platform that brings vast reductions of our development costs. Most interfaces seem to be finished and optimal. There is of course still some work ahead, mostly in documentation an IDE department.

If you find our Ultimate++ way of programming interesting, nothing stays in your way to downloading it. But be careful there: you should be prepared to throw away some old habits and usual ways of thinking about how "things are always done", or they might maime your opportunity to receive a lot in the reward, together with a healthy disrespect to certain honorable, well-established development tools.

 

Dernière édition par chickenk le 06/07/2011. Cette page est aussi en english, čeština, deutsch et русский. Vous voulez nous aider ?. T++