vendredi 16 décembre 2011

MoSync : Introduction à MAUI

Ce tutoriel donne une introduction à l’API d’interface utilisateur de MoSync (MAUI). Il décrit comment utiliser les classes de la librairie afin de construire des écrans et widgets.

C’est quoi MAUI ?

MAUI est une collection de composantes de l’interface utilisateur que vous pouvez utiliser dans vos propres applications. Il est possible de créer toute une application mobile sans eux, mais ils fournissent des fonctionnalités très utiles pour gagner du temps. Le modèle MAUI est extensible. Vous pouvez créer vos propres composantes avec.
MAUI s’inspire des interfaces graphique modernes telles que Java Swing, Windows forms et QT. Il a par contre été conçu avec les particularités des appareils mobiles prises en compte.

Les Screens (écrans)

Les blocs de construction principaux de Mosync sont les Screens et les Widgets.
Les Screens représentent un affichage d’écran complet. Un screen est généralement associé à une activité telle que le login ou la navigation sur une liste d’albums par exemple. Les Screens sont des emplacements logiques différents dans votre application. L’utilisateur peut ainsi naviguer entre les Screens.
Pour utiliser la classe Screen vous devez inclure l’entête
#include
Afin d’afficher un Screen en particulier (et renvoyer tous les autres Screens en arrière plan), vous utilisez la fonction show. La fonction hide renvoie le Screen en arrière plan.
Les Screens sont des conteneurs de Widgets. Il y’a toujours une Widget principale qui est redimensionnée afin de correspondre à la taille de l’écran. Pour attribuer la Widget principale, utilisez la fonction setMain et pour la récupérer utilisez getMain.
La classe Screen implémente certains listeners d’événements intéressants afin de lui permettre de détecter les événements UI tels que l’appuie sur clavier par exemple.

Widgets

Chaque élément de l’interface graphique est une Widget. Chaque Widget effectue des tâches GUI communes. Exemples :
·         Boutons
·         List boxes
·         Labels
·         Images
Toutes les widgets sont des dérivées directes ou indirectes de MAUI ::Widget. Pour utiliser les Widgets dans votre application vous devez inclure le header correspondant. Par exemple pour inclure un Label :
#include
Une Widget peut être un conteneur pour d’autres Widgets. Par exemple, si vous voulez une image avec du texte au dessus, vous pouvez créer une Image, puis mettre Image comme parent d’une Widget Label. Quand une Widget est détruite, tous ses enfants sont détruits.

Disposition des Widget

L’UI d’un Screen est construit sous forme d’hiérarchie de Widgets.
Il existe une Widget nommée Layout qui peut être utilisée pour définir une grille pour y disposer d’autres Widgets. Une autre Widget nommée ListBox permet de créer des listes horizontales ou verticales de Widgets.
Vous pouvez voir ici une hiérarchie de Widget Layout et ListBox utilisée pour arranger la disposition dans Screen.
Quand vous créez une Widget, vous spécifiez sa taille, sa position et son parent :
Widget* widget = new Widget(0, 0, 240, 320, NULL);
Dans un mobile, les positions x et y sont souvent moins importantes que l’on pourrait croire. Vous verrez qu’en réalité, vous ferez la grande partie de votre positionnement grâce à Layout et ListBox et en attribuant l’espacement avec des fonctions comme w->setPaddingBottom(5); qui va ajouter une marge en bas de la Widget.
Certaines fonctions standards des Widgets changent la couleur de fond :
// Change la couleur de fond de la Widget à un vert sombre
widget->setBackgroundColor(0x30A030);
// S’assurer que la Widget affiche une couleur de fond
widget ->setDrawBackground(true);
Parfois vous voudriez afficher des arrières plans, parfois pas. Dans la Widget ci-dessus, un arrière plan vert sombre est utilisé au lieu d’un thème. Par contre, si vous ajoutez un Layout au dessus d’une image que vous voudriez utiliser comme arrière plan, alors vous devrez ajouter setDrawBackground(false) pour vous assurer que le Layout ne cache pas l’image en affichant au dessus.

Taille et orientation du Screen (écran)

Il existe beaucoup de tailles d’écrans mobiles. Il est recommandé de demander au mobile de vous donner sa taille d’écran avant de commencer l’affichage avec la fonction maGetScrSize() :
MAExtent screenSize = maGetScrSize();
int scrWidth = EXTENT_X(screenSize);
int scrHeight = EXTENT_Y(screenSize);
Pour savoir si l’écran est en portrait ou paysage, vous devez faire un test :
if(scrWidth > scrHeight)
{
// Mode paysage
}
else
{
      // Mode portrait

}

Moblets

Les moblets sont le cœur de la programmation événementielle sur MoSync. Lorsque l’application démarre, c’est la Moblet qui est lancée en premier. La Moblet va créer (au moins) le premier écran de votre application et l’afficher. La Moblet est également responsable de la fermeture de l’application, et de la gestion du système des évenements. Certains événements sont passés à l’écran automatiquement, par exemple les appuis sur touches ou sur écran. D’autres événements doivent être récupérés manuellement tels que la récupération d’une nouvelle position GPS.
Lorsque vous créez un nouveau projet Moblet, vous avez un code comme celui-ci (avec quelques modifications) :
#include    //MAUI utilise Moblet comme base
#include      //Permet la création des écrans (ou screens)
#include //Permet la création des textes Labels
#include "MAHeaders.h"

using namespace MAUtil; //Pour des raisons pratiques d'appel des classes de MAUtil
using namespace MAUI;   //Pour des raisons pratiques d'appel des classes de MAUI

class MyScreen : public Screen {   //Héritage de la classe Screen
ublic:
      MyScreen() {      //Constructeur de l'écran
      }

      void keyPressEvent(int keyCode, int nativeCode) {    //Fermeture de l'application si une touche est appuyée
            maExit(0);
      }

      void keyReleaseEvent(int keyCode, int nativeCode) {
      }
     
      ~MyScreen() {     //Destructeur de l'écran
      }
private:
};

class MAUIMoblet : public Moblet { //Héritage de la classe Moblet
public:
      MAUIMoblet() {    //Construction de la Moblet

            screen = new MyScreen();     //Création de l'écran
            screen->show();   //Affichage de l'écran screen
      }

      void keyPressEvent(int keyCode, int nativeCode) {    //Cette fonction n'est appelée que s'il n'y a aucun écran d'affiché
      }

      void keyReleaseEvent(int keyCode, int nativeCode) {  //Cette fonction n'est appelée que s'il n'y a aucun écran d'affiché
      }
      MyScreen* screen; //Déclaration de l'écran screen

      ~MAUIMoblet() {   //Destruction du Moblet
            delete screen;    //Destruction de l'écran
      }
     
};

extern "C" int MAMain() {    //Fonction princpale avec le même rôle
      Moblet::run(new MAUIMoblet());
      return 0;
};
Vous devriez faire des changements à ce code quand vous commencez un nouveau projet. Tout d’abord, vous devez séparer les classes dans leurs propres fichiers .h et .cpp. En second lieu, la Moblet fait passer les événements appuis sur le clavier et l’écran directement à l’écran. Donc ce n’est pas la peine de définir les ces fonctions de callback dans Moblet, la déclaration dans Screen suffit. Elles peuvent par contre être utiles si vous voulez capturer des événements lorsqu’aucun écran n’est affiché ou pour capturer des événements communs sur tous les écrans.

Screen (écran)

Les écrans sont les principales unités de l’application. Un écran est un conteneur de Widgets et du code qui permet de traiter les interactions avec l’utilisateur. Les différents types de Widgets sont expliqués plus bas. Aussi, le projet MAUI donne un bon exemple de l’utilisation des écrans et Widgets.

Labels

C’est probablement la Widget la plus basique. Un label va ajouter du texte au Screen :
Label* myLabel = new Label(0, 0, scrWidth, scrHeight, NULL, "My Label", 0xFFC0C0, myFont);

Un autre exemple de construction d’un label:
Label* myLabel = new Label(0, 0, scrWidth, scrHeight, NULL);
myLabel->setCaption("My label");


Par défaut, les Labels vont afficher uniquement la première ligne. Pour passer à la 2° ligne automatiquement, vous devez appeler :
myLabel->setMultiLine(true);
Vous pouvez également demander au Label de prendre l’espace minimale requis pour son affichage :
// Autoriser le label à changer de taille verticalement
label->setAutoSizeY(true);
// Interdire au Label de changer de taille horizontalement.
label->setAutoSizeX(false);

Images

Les images sont souvent utilisées comme boutons ou pour l’affichage d’images tout simplement. La création d’image est très simple :
// Crée un nouvel objet image basé sur le Handler IMAGE dans le fichier ressources
Image* image = new Image(0, 0, 100, 100, listbox, true, true, IMAGE);
Les 3 derniers paramètres sont le redimensionnement automatique horizontal, vertical et le MAHandle (identifiant) de la ressource Image. Si le redimensionnement n’est pas automatique, la Widget ne prendra pas automatiquement la taille de l’image.

Si vous voulez ajouter une bordure à l’image, vous pouvez utiliser le padding. Si vous avez utilisé le redimensionnement automatique, l’image sera coupée aux bords. Parce que la Widget aura exactement la même taille de l’image est le padding va consommer l’espace dans la bordure intérieur de la Widget.
// Crée une nouvelle Image basée sur un fichier PNG dans les ressources.
Image* image = new Image(0, 0, 150, 150, listbox, true, true, IMAGE);
image->setPaddingTop(10);
image->setPaddingLeft(10);
Vous pouvez animer des images en écoutant des événements de Timer par exemple, et en changeant le MAHandle de la ressource que vous utilisez pour l’image. Nous verrons ces procédés plus tard.

Layouts

La widget Layout n’affiche rien en elle-même, mais elle organise les widgets dans Screen pour vous. Vous pouvez spécifier une grille dans laquelle les widgets seront placées. Elles seront insérées du haut à gaucher vers le bas à droite. Ne confondez pas cela avec les tables HTML. Il y’a des ressemblances, mais les Layouts ne vous donnent pas toutes les options d’une table HTML.
Pour créer une une Layout, vous spécifiez le nombre de lignes et de colonnes :
Layout *mainLayout = new Layout(0, 0, scrWidth, scrHeight, NULL, 1, 2);
Dans cet exemple, nous avons une colonne et 2 lignes. Nous aurons donc 2 widgets alignées verticalement. Vous ne pouvez pas insérer plus de Widgets que vous avez de cases. Utilisez une ListBox si vous voulez ajouter dynamiquement des Widgets sans vous soucier de leur nombre.
Si vous avez plus d’une colonne, l’espace horizontal est distribué équitablement entre elles. Vous ne pouvez pas avoir une colonne plus large qu’une autre. Chaque widget peut avoir l’espace vertical dont elle a besoin si vous avez une seule colonne. Si vous en avez plus, toutes les Widgets d’une même ligne doivent avoir la même hauteur.
Vous ne pouvez pas positionner une Widget au sein de sa cellule. Les 2 premiers arguments sont donc ignorés. Vous devez augmenter la taille de la Widget pour qu’elle s’affiche avec une bordure.

ListBox

La ListBox est probablement la Widget la plus utile dans la collection standard. Elle ressemble à Layout mais avec 2 différences majeurs. D’abord, elle utilise une seule dimension (horizontale ou verticale mais pas une grille). Puis, elle peut s’étendre au fur et à mesure que vous y insérez des Widgets.
Ne soyez pas tentés d’utiliser le constructeur basique de ListBox. Elle n’aura pas la taille à laquelle vous vous attendez.
Si vous créez une ListBox avec les paramètres par défaut, elle ne va pas glisser.
infobox = new ListBox(0, 0, scrWidth, scrHeight, layout);
Vous devez ajouter tous les arguments spécifiques à la ListBox:
infobox = new ListBox(0, 0, scrWidth, scrHeight, layout, ListBox::LBO_VERTICAL, ListBox::LBA_LINEAR, true);
Vous pouvez décider si elle va glisser horizontalement ou verticalement, si elle va être animée et si elle va rebondir au début quand elle atteindra la fin.
En général, un Screen contient une ListBox insérée dans une Layout. Le schéma en bas est un exemple de disposition à l’écran :
 Vous pouvez faire glisser une ListBox en utilisant les fonctions selectNextItem() ou selectPreviousItem(). Dans cet exemple, les entrées clavier sont capturées et l’application fait glisser la ListBox :
void Menu::keyPressEvent(int keycode)
{
      // Une liste complète des constantes keycode sont disponibles sur
      // http://www.mosync.com/docs/2.0b1/html/maapi_8h.html
      switch(keycode)
      {
      case MAK_SOFTLEFT:
            // Quitte l'application
            maExit(0); // Appelle la moblet pour quitter gracieusement
            break;
      case MAK_8:
      case MAK_DOWN:
            contentBox->selectNextItem(true); // Sélectionne l'élément suivant dans le menu
            break;
      case MAK_2:
      case MAK_UP:
            contentBox->selectPreviousItem(true); // Sélectionne l'élément précédent dans le menu
            break;
      case MAK_5:
      case MAK_FIRE:
      // Décidez quelle action vous souhaiteriez effectuer une fois l'élément sélectionné.
      // Vous pouvez récupérer l'élément sélectionné avec contentBox->getSelectedIndex();
            break;
      }
}
Vous pouvez également décider s’il faut revenir en haut une fois que le défilement passe la dernière Widget avec l’option setWrapping().

EditBox

Une EditBox vous permet de capturer du texte.
EditBox* editBox = new EditBox(
      0, 0, scrWidth, 60, NULL, “”,
      0xFFFFFF, aFont, true, true, 256,
      EditBox::IM_STANDARD
      );
EditBox est la Widget la plus compliquée vu qu’il y’a beaucoup d’options pour capturer les entrées clavier. Vous ne voulez pas par exemple que les boutons de navigation changent de Widget alors que l’utilisateur essaie juste d’entrer son nom. Vous pouvez donc paramétrer EditBox pour gérer sa propre navigation. Quand c’est le cas, EditBox devient un listener des entrées clavier et récupérera celles-ci tout comme Screen. Cela peut poser problème pour distinguer entre la navigation au sein d’EditBox et la navigation dans Screen.
EditBox a un curseur avec la couleur blanche par défaut. Si vous avec un arrière plan blanc, vous devez changer sa couleur vers le noir par exemple :
editBox->setCursorColor(0x000000);
Il y’a 2 méthodes pour entrer le texte. Vous pouvez l’utiliser pour récupérer les caractères alphanumériques ou uniquement numériques. Le symbole # agit comme échangeur temporaire (shift ou majuscule). Si l’utilisateur appuie par exemple sur # puis 4, il aura un G majuscule.
WidgetSkins (thèmes)

La plupart des Widgets ont des thèmes qu’ils appliquent. C’est une image standard qui va être appliquée aux bordures et à l’arrière plan des Widgets pour leur donner un Look&Feel uniforme. Voici un thème qui est utilisé dans l’exemple MAUI. Le thème est composé d’une grille 3x3, et des lignes ont été tracées afin de vous montrer où est définie la grille. Quand il est appliqué à une Widget, les coins restent comme ils sont et les 5 autres sections sont étirées pour occuper l’espace requis. La section 5 est étirée pour remplir l’arrière plan de la Widget. Vous devez faire attention à ça, car un joli dégradé ne sera pas si joli une fois étiré.
Pour définir le thème il suffit de définir les positions horizontales des coupes verticales, puis les positions verticales des coupes horizontales.
gSkin = new WidgetSkin(
                    RES_SELECTED, RES_UNSELECTED,
                    16, 32, 16, 32, true, true);
Les 2 premiers arguments sont les MAHandles des images qui sont utilisées pour définir le thème. Le premier sera appliqué lorsque le Widget est sélectionné, le deuxième lorsqu’il ne l’est pas. Vous spécifiez ensuite les décalages des coupes verticales et les décalages des coupes horizontales. Puis vous précisez si les images que vous utilisez contiennent des zones transparentes (ce qui généralement le cas).
Il est très important que les 2 images utilisées (sélection / pas de sélection) soient exactement de la même taille. Vous pouvez voir ici les thèmes appliqués à des EditBoxes.

Créer vos propres Widgets

Ce qui est intéressant à propos des Widgets  est que vous pouvez créer vos propres Widgets : barres de progression, boutons, calendrier, sliders… Il suffit d’hériter de Widget et d’implémenter la fonction drawWidget().
Voici une Widget ProgressBar :
#ifndef _PROGRESSBAR_H_
#define _PROGRESSBAR_H_
       
#include
#include

using namespace MAUI;
namespace DatiloUI
{
    class ProgressBar : public Widget
    {
    public:
        ProgressBar(int x, int y, int width, int height,Widget* parent = NULL);
        virtual ~ProgressBar();
        int getPercentage();
        void setPercentage(int percentage);
        void drawWidget();
        void setFullImage(Handle image);
        void setEmptyImage(Handle image);
    private:
        Handle mTileFullImage;
        Handle mTileEmptyImage;
        int mPercent;
        void plotSection(MAPoint2d* offset, int width, Handle image);
    };
};
#endif // _PROGRESSBAR_H_
La fonction drawWidget() est essentielle. Elle sera appelée lorsque la Widget doit être affichée. Elle doit afficher son contenu à l’intérieur des bordures de la Widget.
Voici un extrait de la fonction drawWidget() :
void ProgressBar::drawWidget()
{
      int w = this->getWidth() - this->getPaddingLeft() - this->getPaddingRight();
      int amountRemaining = (w * percent) / 100;
      MAPoint2d* offset = new MAPoint2d();
      Point p = this->getPaddedPosition();
      Rect r = paddedBounds;
      offset->x = r.x;
      offset->y = r.y;
      // Plot the full section
      if(mTileFullImage != NULL)
      {
            plotSection(offset, amountRemaining, mTileFullImage);
      }
      else
      {
            // Plot a progress bar
            plotBar(offset, amountRemaining);
      }
      if(mTileEmptyImage != NULL)
      {
            offset->x += amountRemaining;
            plotSection(offset, w - amountRemaining, mTileFullImage);
      }
      delete offset;
}
Vous pouvez voir que vous pouvez récupérer PaddedPosition de la Widget. Il indique où se trouve la Widget dans l’écran. La fonction plotSection() indique l’espace que vous êtes autorisés à utiliser.
void ProgressBar::plotSection(MAPoint2d* offset, int  width, Handle h)
{
      if(mDrawBorder)
      {
            plotBorder();
      }
      MARect* clippedImage = new MARect();
      Rect r = this->getPaddedBounds();
      Extent imageSize = maGetImageSize(h);
      while(width > 0)
      {
            clippedImage->left = 0;
            clippedImage->width = width > EXTENT_X(imageSize) ? EXTENT_X(imageSize) : width;
            clippedImage->top = 0;
            clippedImage->height = r.height > EXTENT_Y(imageSize) ? EXTENT_Y(imageSize): r.height;
            // lprintfln("Drawing image at x:%d, y:%d", offset->x, offset->y);
            Gfx_drawImageRegion(h, clippedImage, offset, 0);
            offset->x += clippedImage->width;
            width -= clippedImage->width;
      }
      delete clippedImage;
      if(writePercentage)
            plotPercentage();
}
La fonction getPaddedBounds vous indique exactement quelle est l’espace que vous êtes autorisé à utiliser sans dessiner dans une autre Widget.
Le moteur (The engine)
Derrière toutes les Widgets se trouve le moteur MAUI. C’est la partie que vous aurez rarement à manier mais qui s’occupe d’afficher vos Widgets.
Le moteur est un singleton, et vous pouvez récupérer une référence avec
Engine& eng = Engine::getSingleton();
Le moteur peut vous permettre de spécifier la police et le thème par défaut. Alors vous n’avez pas à les spécifier à chaque fois que vous les utilisez.
eng.setDefaultFont(standardFont);
eng.setDefaultSkin(standardSkin);

Rappel sur le C++ pour les habitués de Java et C#

Ce guide est composé de 3 sections. Nous parlerons d’abord de comment marche le C++, puis nous passerons à la gestion de la mémoire et enfin nous verrons un certain nombre de principes de développement qui sont adaptés à l’entreprise mais qui posent des problèmes sur l’environnement mobile.
Ceci n’est évidemment pas un guide complet sur le C++, juste quelques rappels et notions complémentaires pour pouvoir aborder le C++ par un débutant ou un développeur Java.

Comment marche le C++

En général, lorsqu’on veut créer une classe en Java, il suffit d’ajouter un fichier Java et définir la classe. En C++, vous devez définir la classe avant de pouvoir l’utiliser. Vous faites cela en incluant un fichier header dans votre code en utilisant #include ‘NomDuHeader.h’. Le header est littéralement inclut dans le code source avant la compilation. Ceci induit une problématique qui n’existe pas dans les environnements interprétés (C# ou Java). Si vous avez 2 classes Alpha et Beta, vous pouvez les inclure dans votre nouvelle classe Gamma comme suit :
#include "Alpha.h"
#include "Beta.h"
Par contre si Beta.h inclue Alpha.h, vous aurez une erreur de compilation dans Gamma. Ceci parce que le préprocesseur insert le code depuis Alpha.h, puis insert le code depuis Beta.h, qui à son tour insert le code depuis Alpha.h. Le compilateur va indiquer qu’Alpha a déjà été défini. Afin d’éviter cela, au début du header, vous devez ajouter une protection d’inclusion. Ceci est une courte directive du préprocesseur qui indique au compilateur de ne pas réinclure le header s’il a déjà été inclut. Dans Alpha.h, il suffit d’inclure un code semblable à ce qui suit :
#ifndef __ALPHA_H
#define __ALPHA_H
Quand vous créez une nouvelle classe dans MoSync, l’IDE va insérer ce code pour vous. Vous pouvez lire ceci comme ‘S’il n’y a pas de variable nommée __ALPHA_H, alors créez une variable nommée __ALPHA_H’. A la fin de du code, il y’aura un #endif afin terminer le bloc. Vous pouvez changer la convention de nommage si vous voulez, mais si vous créez un header vous-même, n’oubliez pas d’ajouter la protection d’inclusion.
Si vous voulez utiliser des classes sans spécifier leur espace de nommage (namespace) à chaque fois. Utilisez la commande suivante :
using namespace <namespace>;
Notez le point-virgule à la fin. Ceci est une commande C++ et non une commande du préprocesseur (qui commence par #)
Vous pouvez créer vos classes dans un espace de nommage dans le fichier header :
namespace MyNamespace
{
      …
};
A l’intérieur des déclarations d’espaces de nommage, vous pouvez créer une ou plusieurs classes (ou structures).
class MyClass
{
      …
};
A l’intérieur d’une classe, vous pouvez créer des fonctions. La visibilité des fonctions et variables membres est définie par groupes. Une déclaration de classe dans un fichier header est comme suit :
Class Gamma
{
public :
Gamma() ;
virtual ~Gamma() ;
void incrementCounter() ;
int getCounter();
protected:
      int mCounter;
private:
      bool isActive;
};
Le destructeur permet de libérer la mémoire et effectuer tout traitement de nettoyage.
La définition de classe se fait dans un fichier .cpp. Voici l’exemple d’une définition de classe Gamma.cpp :
#include "Gamma.h"
Gamma::Gamma()
{
mCounter = 0;
}
Gamma::~Gamma()
{
}
void Gamma::incrementCounter()
{
mCounter++;
}
int Gamma::getCounter()
{
 return mCounter;
}
Remarquez que chaque fonction commence avec Gamma::. Ceci implique que vous pouvez implémenter plusieurs classes dans le même fichier .cpp, mais ceci n’est pas recommandé. Aussi, comme vous l’avez remarqué, lors de la construction, mCounter est initialisé à 0. Il y’a un autre moyen d’accomplir cela qui est préféré en C++ pour 2 raisons. Le constructeur sera comme suit :
Gamma::Gamma() : mCounter(0)
{}
mCounter sera également initialisé à la valeur de 0. Ceci peut être important dans les environnements où il peut être possible d’accéder à une classe avant que le que constructeur ait fini son exécution. Aussi, si votre classe définit des références, elles ne peuvent pas être nulles.
Si la classe Gamma a besoin d’objets ou fonctions, incluez le fichier dans le cpp et non le header. C’est une bonne règle de n’ajouter que les includes dont vous avez besoin, et si la classe est utilisée en interne exclusivement, ajoutez l’inclusion dans le cpp.

Héritage

C++ supporte l’héritage multiple. C’est la dérivation de plusieurs classes de base (non interfaces). Quand vous spécifiez une classe, vous spécifiez les classes desquelles vous avez hérité ainsi que la visibilité de l’héritage :
class Delta : public Alpha, public Beta, private Gamma
Les interfaces en C++ sont juste des classes, il n’y a pas de déclaration d’interface. Vous créez une classe pour héritage avec des fonctions virtuelles.
class Alpha
{
public:
virtual void doSomething() =  0;
};
Les fonctions virtuelles peuvent être surchargées, mais c’est pas la peine d’utiliser la commande ‘override’.
class Beta : public Alpha
{
public:
void doSomething();
};
L’implémentation de doSomething dans Beta sera utilisée, vu qu’il n’y a pas d’implémentation dans Alpha. Ceci est appelé une fonction pure et peut être identifié par =0.

Objets, pointeurs et références

L’un des avantages des environnements où la mémoire est gérée par runtime comme Java et .net est que vous n’avez pas à vous soucier si les objets sont créés dans le heap ou dans le stack.
En guise de rappel, la mémoire stack est locale dans la portée du code où elle a été créée. La mémoire heap est partagée dans toute l’application. Vous devez savoir quand vous créez un objet s’il est dans le stack ou dans le heap. Les objets dans le heap doivent être détruits explicitement avec la commande delete. Les objets dans le stack seront détruits dès qu’ils sortent de leurs portées. La règle en C++ en général est de ne créer des objets dans le heap que si c’est vraiment nécessaire. La création d’objets dans le heap peut être bien plus lente que dans le stack. Vous créez un objet dans le heap si vous voulez par exemple contrôler sa durée de vie.
Pour créer un objet dans le stack, il suffit de le déclarer.
String myString;
Pour créer un objet dans le heap, vous devez utiliser new
String* myString = new String();
L’utilisation de * signifie que vous avez créé un pointeur vers un objet String. C’est l’adresse dans la mémoire du début de l’objet pointé.
Une référence est une adresse comme le pointeur, mais elle ne peut pas avoir la valeur null. Les références sont déclarés avec un &.
String* myString = NULL; //C’est OK
String& myString; //Va causer une erreur de compilation
Les références sont particulièrement utiles pour passer des objets dans le stack.
String& Gamma::getHeadline()
{
      return myString; 
}
Dans cet exemple, myString a précédemment été créé comme un objet dans le stack. getHeadline() va retourner une référence à myString et non l’objet en entier.
String& headline = gamma.getHeadline();
Quand vous accédez à des fonctions d’un objet, vous devez savoir si c’est un objet, référence ou pointeur. Les fonctions et membres d’un objet ou référence sont accessibles en utilisant :
myString.clear();
Les fonctions et membres d’un pointeur sont accessibles en utilisant ->
myString->clear();
Vous pouvez convertir entre pointeur et références en utilisant *
String* myString = new String();
myString->append("hello world", 11);
String& strref = *myString;
strref.clear();
Vous pouvez créer des pointeurs vers des objets même si ceux-ci sont dans le stack.
int Gamma::getStringLength(String* testString)
{
      return testString->length();
}
String hello = "Hello World";
int strLen = getStringLength(&testString);

Gestion de la mémoire

La possibilité de créer des objets dans le stack et dans le heap, ainsi que l’absence d’une libération de mémoire automatisée, implique qu’il faut gérer la durée de vie de chaque objet que vous créez. Quand vous créez un objet dans le stack, sa durée de vie est égale à sa portée.
Par exemple, si vous créez un objet dans une fonction
void Gamma::doSomething()
{
// String sera créé ici
String myString;
// String sera automatiquement détruit et la mémoire libérée parce qu’on a atteint la fin de la fonction qui est la fin de la portée de l’objet
}
Les objets encapsulés expirent également avec l’expiration de l’objet contenant
class Gamma
{
public:
Gamma();
virtual ~Gamma();
private:
String mString;
};
Ici, il y’aura un objet appelé mString dans le stack. Il sera créé quand une instance de Gamma sera créée et détruit dès que cette instance sera détruite. Vous n’avez pas à intervenir manuellement.
Vous devez être plus prudent quand vous créez des objets dans le heap. Ils ne seront pas automatiquement détruits quand vous aurez atteint la fin de la portée de la fonction. Les objets dans le heap sont justement intéressants à cause de cela.
Il y’a un design pattern qui s’appelle RAII (Resource Allocation Is Initialisation). Ce pattern recommande de créer les objets dont vous avez besoin dans le constructeur et les détruire dans le destructeur.
//Constructeur
Gamma::Gamma()
{
// Instanciez les objets don’t vous avez besoin dans le heap
myString = new String();
}
// Destructeur
virtual Gamma::~Gamma()
{
delete myString;
}
Ceci signifie que même si myString sera créé dans le heap, lorsque Gamma sera hors portée myString sera détruit.
Il y’a plusieurs cas où cette approche n’est pas appropriée. Vous pouvez avoir une application qui a besoin d’envoyer des messages complexes entre des classes. Chaque message est de classe Message
class Message
{
public:
void setMessage(const char* message);
const char* getMessage();
void setStatus(int status);
int getStatus();
private:
String mMessage;
int mStatus;
};
Nous pouvons voir que lorsque Message est instancié, un int et un String sont créés dans le stack.
La classe Gamma doit envoyer des messages à Delta. Delta a la fonction suivante :
void Delta::receiveMessage(Message* message)
{
// J’ai reçu message. Mais il peut être déjà détruit dans le stack
}
Les objets sont très puissants, mais ils peuvent être dangereux. Voici une liste à apprendre par cœur :
·         Essayez toujours de créer des objets dans le stack, pas le heap. C’est plus rapide et moins dangereux.
·         Normalement, la classe qui a créé un objet doit le détruire
·         A chaque fois que vous écrivez le mot new, pensez où le mot delete doit être. Il doit être quelque part ou vous aurez une fuite de mémoire.
·         Quand vous déléguez la responsabilité de détruire un objet (à une autre classe par exemple), insérez un commentaire pour indiquer qui s’en occupe.
·         Vous devez avoir une seul destruction. Si vous détruisez un objet 2 fois, vous aurez une erreur lors de l’exécution.

Cours sur l'environnement mobile

Ceci est une introduction générale sur les spécificités de l'environnement mobile. Utile pour guider tout développeur souhaitant réaliser des projets mobiles. Il est composé de 2 parties: