Créer un contrôle utilisateur (userControl) en C++/CLI avec Visual Studio 2005 : Un lecteur de fil RSS

Nico-pyright(c)

Ce tutoriel vous présente l'élaboration d'un contrôle utilisateur simple (userControl) par l'exemple avec le C++/CLI. ♪

- Titre : Créer un contrôle utilisateur (userControl) en C++/CLI avec Visual Studio 2005 : Un lecteur de fil RSS

- Auteur : Nico-pyright(c)

- Parution : 20 mars 2006

- Mise à jour : 25 mars 2006

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2006 Nico-pyright(c). Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 1.Introduction

- 2.Création du contrôle utilisateur (userControl)

- 2.1.Création du projet

- 2.2.RSS

- 2.2.1.Le format RSS

- 2.2.2.L'objet RSS

- 2.3.Les attributs (propriétés)

- 2.4.Création du contrôle et rafraîchissement

- 2.5.Associer une image à son contrôle (l'attribut ToolboxBitmap)

- 2.6.Ajout d'un évènement sur le contrôle

- 2.7.Cacher un évènement par défaut sur le contrôle

- 2.8.Améliorons l'apparence de notre contrôle

- 2.9.Téléchargement du contrôle

- 3.Création du programme d'exemple d'utilisation de notre contrôle utilisateur

- 4.Conclusion

- Remerciements

- Contact

1.Introduction

Vous souhaitez commencer à développer des contrôles utilisateurs .Net (userControl) et votre langage préféré est le C++/CLI ?

Alors, ce tutoriel est pour vous. Il va vous guider dans vos premiers pas, dans l'apprentissage de l'élaboration de contrôles utilisateurs avec le framework .net 2.0. En particulier, nous aborderons l'élaboration pas à pas d'un contrôle qui va permettre de lire vos fils RSS préférés.

J'utiliserai dans ce tutoriel le logiciel Visual Studio 2005 de Microsoft et plus particulièrement Visual C++ 2005 et le framework dotnet 2.0.

2.Création du contrôle utilisateur (userControl)

Visual C++ 2005 nous propose une grande quantité de contrôles standards (voir le tutoriel de démarrage avec les Winforms), mais nous permet aussi de créer nos propres contrôles utilisateurs.

Nous allons voir dans ce tutoriel comment créer un tel composant.

2.1.Création du projet

Tout d'abord, créez un nouveau projet CLR / Windows Forms Control Library

Visual Studio nous génère une classe rssFeederControl, qui dérive de public System::Windows::Forms::UserControl, ce qui permet de faire de notre classe un userControl.

public ref class rssFeederControl : public System::Windows::Forms::UserControl

Il s'agit donc d'une form particulière, qui ressemble à ceci en design.

Il s'agit donc de notre contrôle. Ce carré, pour l'instant rudimentaire, est ce que vous verrez lorsque vous ajouterez ce contrôle dans une form. Nous allons bien sûr l'étoffer dans les chapitres suivants…

2.2.RSS

2.2.1.Le format RSS

Le format RSS (pour « RDF - Resource Description Framework - Site Summary », « Rich Site Summary » ou « Real Simple Syndication », selon l'usage que l'on en fait) est une manière de décrire le contenu d'un site, d'un blog, etc., et plus généralement de toute page structurée qui est mise à jour périodiquement.
Nous souhaitons faire un lecteur RSS, intéressons-nous donc rapidement à ce format.

Il s'agit de pouvoir lire un fichier XML qui décrit la structure de la page. Il existe à ce jour plusieurs versions RSS. Le but n'étant pas de faire un lecteur RSS compatible avec toutes les versions, je vais m'intéresser à la version 2.0. qui est utilisée pour les blogs de developpez.com. Plus d'infos ici (anglais).

Voici un extrait d'un tel fichier XML : (pour des raisons de présentation, j'ai coupé les lignes trop longues […])

<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" [...]>
<channel>
<title>Blogs des développeurs - Developpez.com</title>
<link>http://blog.developpez.com/index.php?blog=1</link>
<description>Les blogs de Developpez.com, Club informatique gratuit : Blogs, Forum,
Cours et tutoriels en programmation : [...].</description>
<language>fr-FR</language>
<docs>http://backend.userland.com/rss</docs>
<item>
<author>Nono40</author>
<title>La version stable 3.20 de la JVCL 3.20 est dispnible</title>
<link>http://blog.developpez.com/index.php?blog=48&title=la_version_stable_[...]</link>
<pubDate>Wed, 22 Feb 2006 19:06:24 +0000</pubDate>
<category domain="main">Articles & cours</category>
<guid isPermaLink="false">1670@http://blog.developpez.com</guid>
<description>http://jvcl.sourceforge.net L'équipe JVCL [...] </description>
<comments>http://blog.developpez.com/index.php?blog=1&p=1670&c=1&tb=1&pb=1#comments</comments>
</item>
<item>
<author>Morpheus</author>
<title>WinFX Runtime Components (February CTP): Maintenant disponible !</title>
<link>http://blog.developpez.com/index.php?blog=9&title=winfx_runtime_c[...];/link>
<pubDate>Wed, 22 Feb 2006 17:19:41 +0000</pubDate>
<category domain="main">.NET</category>
<guid isPermaLink="false">1669@http://blog.developpez.com</guid>
<description>http://blogs.msdn.com/tims/archive/2006/02/22/536985.aspx Si comme moi, [...]</description>
<comments>http://blog.developpez.com/index.php?blog=1&p=1669&c=1&tb=1&pb=1#comments</comments>
</item>
</channel>
</rss>

Pour résumer, on a un nœud principal qui donne le titre, le lien, la description, la langue, etc. … de la page.
Puis nous avons une collection d'items, qui correspondent aux news structurées, et qui contiennent entre autres : l'auteur, le titre, la description, l'URL, etc.

Sur chaque news, il est possible d'ajouter un commentaire. Les commentaires sont structurés de même dans un fichier XML. Il ressemble beaucoup au fichier RSS décrivant la page web.

Dans notre cas, les XML offrent des informations dont nous n'aurons pas besoin. Nous nous intéresserons donc qu'à certains champs, et pour les lire, nous allons créer une classe.

2.2.2.L'objet RSS

Nous allons créer un objet qui va s'occuper de la récupération des informations qui nous intéressent dans le fichier XML.

Créons donc deux classes, l'une pour la description globale du site, et une pour récupérer les éléments (c'est-à-dire chaque news).
Soit :

ref class CRssObject
{
public:
CRssObject(void);
String ^ title;
String ^ link;
String ^ description;
String ^ language;
System::Collections::ArrayList ^ listItem;
bool LoadFromUrl(String ^, int, String ^);
bool LoadFromFile(String ^, int, String ^);
private:
bool isRssVersion2(Xml::XmlNode ^);
String ^ GetText(Xml::XmlNode ^, String ^);
bool LoadObject(Xml::XmlDocument ^, int, Xml::XmlDocument ^);
};

Nous avons donc quatre attributs (title, link, description, language), ainsi qu'une collection (ArrayList) qui va contenir les items (donc les objets CItemRss, ci-dessous).

Nous avons aussi deux méthodes publiques LoadFromUrl et LoadFromFile qui vont nous permettre de remplir l'objet depuis une URL, ou depuis un fichier XML. Nous devrons préciser en paramètre le nombre maximum d'éléments à charger, ainsi qu'éventuellement, l'URL ou le nom du fichier XML de commentaires.
Ensuite, nous avons des méthodes privées qui vont permettre (entre autres) de vérifier si on a bien un fichier RSS en version 2.

ref class CItemRss
{
public:
CItemRss(void);
String ^ author;
String ^ title;
String ^ link;
int nbComments;
};

La classe CItemRss contient simplement quatre attributs : author, title, link et nbComments.

Je ne vais pas décrire complètement le contenu du fichier RssObject.cpp, je vous encourage à aller consulter le fichier dans l'archive disponible en téléchargement à la fin du chapitre.
Sachez cependant que la méthode LoadObject est appelée avec un object Xml::XmlDocument qui correspond au contenu du fil RSS. Cette méthode va parcourir le fichier XML pour charger les enregistrements dans notre objet.
Il est à noter aussi que j'utilise une méthode à moi pour déterminer le nombre de commentaires sur une news. En effet, comme je n'ai pas trouvé de spécifications précises concernant la structure du fichier de commentaires, j'ai déduit à partir de plusieurs fichiers XML de commentaires une méthode pour connaître le nombre de commentaires sur une news.
Il s'agit simplement de regarder dans le fichier de commentaire si l'URL complète courante contient l'URL de la news dont on cherche le nombre de commentaires. Si c'est le cas, alors on en déduit qu'il s'agit d'un commentaire.
Cette méthode fonctionne pour les quelques fils RSS que je consulte, mais je ne peux pas garantir de son fonctionnement absolu.

Comme exemple de code, je vais vous montrer comment récupérer le fichier XML depuis une URL, grâce à l'objet HttpWebRequest :

HttpWebRequest ^ HttpWReq = dynamic_cast<HttpWebRequest^>(WebRequest::Create(url));
HttpWReq->CachePolicy = gcnew Cache::HttpRequestCachePolicy(Cache::HttpRequestCacheLevel::Reload);
HttpWResp = dynamic_cast<HttpWebResponse^>(HttpWReq->GetResponse());
sr = gcnew StreamReader(HttpWResp->GetResponseStream());
Xml::XmlDocument ^ xml = gcnew Xml::XmlDocument();
XML->LoadXml(sr->ReadToEnd());

2.3.Les attributs (propriétés)

Ce contrôle dispose déjà d'un certain nombre de propriétés par défaut (visible, Size, Location, etc. …). Nous voulons pouvoir lui en rajouter.
Nous souhaitons en effet pouvoir saisir l'URL du fichier XML à lire, éventuellement l'URL du fichier XML de commentaires, ainsi que le nombre de news à afficher et enfin la fréquence de rafraîchissement du contrôle.
Nous allons stocker ces valeurs comme attributs privés de la classe :

private:
String ^ url_, ^ urlComment_;
int nbMax_;
int interval_;

Puis, nous allons utiliser les attributs Category et Description, qui vont nous permettre respectivement de dire dans quelle catégorie de propriétés ranger notre propriété (et la créer si elle n'existe pas) et d'indiquer la description de la propriété.
Ainsi, dans le cas de l'URL, nous l'ajoutons à la catégorie « configuration », avec la description « Url du fil rss ».
Puis, nous définissons les accesseurs de cet attribut. (NB : nous verrons plus loin le contenu de refreshControl, qui comme son nom l'indique nous permettra de rafraîchir le contenu du contrôle).

[Category("Configuration")]
[Description("Url du fil rss")]
property String ^ URL
{
String ^ get() { return url_; }
void set(String ^ value)
{
url_ = value;
refreshControl();
}
}

Remarque : Si nous n'avions pas utilisé l'attribut Category, cette propriété aurait été rangée dans misc par défaut.

Compilons maintenant notre contrôle. Nous avons en sortie une dll qui contient notre contrôle utilisateur.
À partir d'un nouveau projet de type window form, ajoutons notre contrôle à la barre d'outils (clic droit).

On va chercher la dll grâce à browse, puis on l'ajoute

Posons le contrôle sur la form :

Et nous constatons dans la fenêtre de propriétés du contrôle la nouvelle catégorie « Configuration », ainsi que les quatre nouvelles propriétés. C'est à partir d'ici que nous pourrons configurer notre contrôle.

Tel que c'est fait pour l'instant, on constate que les valeurs par défaut sont soit 0, soit chaîne vide. Pour définir une valeur par défaut à l'attribut, retournons dans le constructeur de notre contrôle utilisateur :

rssFeederControl(void)
{
nbMax_ = 0;
interval_ = 300; // 5 minutes par défaut
InitializeComponent();
refreshControl();
}

Remarque : Nous aurions pu aussi utiliser l'attribut DefaultValue(« Valeur par défaut »). Je ne l'ai pas utilisé dans ce cas, car cela me permet de faire une seule fois l'initialisation.

Pour les besoins du rafraîchissement automatique, on rajoute un timer sur le contrôle utilisateur. Tant qu'on est là, on rajoute aussi un tooltip, dont nous nous servirons plus tard. Puis on génère l'évènement timer1_Tick en double cliquant sur le timer. C'est cette méthode qui sera appelée lorsque l'intervalle de temps défini sera écoulé.
C'est à ce moment qu'on provoquera le rafraîchissement du contrôle.

private: System::Void timer1_Tick(System::Object^  sender, System::EventArgs^  e)
{
refreshControl();
}

Voici donc à quoi ressemble le code des quatre nouvelles propriétés :

[Category("Configuration")]
[Description("Entrez le nombre maximum de news à afficher")]
property String ^ nbMax
{
String ^ get(){ return Convert::ToString(nbMax_); }
void set(String ^ value)
{
nbMax_ = Convert::ToInt32(value);
refreshControl();
}
}

[Category("Configuration")]
[Description("Url du fil rss")]
property String ^ URL
{
String ^ get() { return url_; }
void set(String ^ value)
{
url_ = value;
refreshControl();
}
}

[Category("Configuration")]
[Description("Url du fil rss de commentaires")]
property String ^ urlComment
{
String ^ get() { return urlComment_; }
void set(String ^ value)
{
urlComment_ = value;
refreshControl();
}
}

[Category("Configuration")]
[Description("Délai de rafraîchissement (en secondes)")]
property String ^ interval
{
String ^ get(){ return Convert::ToString(interval_); }
void set(String ^ value)
{
interval_ = Convert::ToInt32(value);
timer1->Stop();
timer1->Interval = interval_ * 1000;
timer1->Start();
refreshControl();
}
}

Remarque : Une propriété est forcément une chaîne de caractères, si on veut l'utiliser en tant que nombre par exemple, il faut la convertir dans l'accesseur. (comme c'est le cas dans cet exemple pour la propriété interval).
Ici les besoins sont simples, nous ne gérons que des chaînes et un nombre. Pour des types plus compliqués, qui peuvent demander une présentation particulière, on utilise l'attribut TypeConverter.
Notez aussi que l'on arrête le timer et on le relance lors d'un changement d'interval.

2.4.Création du contrôle et rafraîchissement

J'en ai parlé rapidement un peu plus haut, nous allons maintenant nous occuper de la méthode qui va créer et rafraîchir le contrôle.

Comme le nombre de news à afficher est dépendant de la configuration du contrôle, nous allons devoir créer les contrôles dynamiquement.
Pour afficher les news, nous allons nous servir d'un contrôle de type LinkLabel.
Et pour stocker toutes les news, nous allons utiliser un tableau cli::array.
Nous utilisons aussi un groupBox pour encadrer les LinkLabel.
Enfin, nous rajoutons un objet CRssObject en attribut privé pour stocker l'objet contenant les informations du XML.

cli::array<System::Windows::Forms::LinkLabel ^, 1> ^ listOfNews;
System::Windows::Forms::GroupBox ^ groupBox;
CRssObject ^ rssObj_;

Voici le code de la méthode refreshControl() :

Dans un premier temps, on efface tous les contrôles présents.
On vérifie ensuite la validité de la configuration :
Il faut que l'URL du fil RSS soit saisie, que le nombre de news à afficher soit supérieur à 0 et que le fil RSS soit de norme 2.0.
Si ce n'est pas le cas, on crée un label qui précise l'intitulé du problème. (On considère que le fil RSS de commentaires est optionnel).

void refreshControl()
{
this->Controls->Clear();
if (url_== nullptr || url_ == "")
{
System::Windows::Forms::Label ^ label = gcnew System::Windows::Forms::Label();
label->AutoSize = true;
label->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 9.75F,
System::Drawing::FontStyle::Bold,
System::Drawing::GraphicsUnit::Point, static_cast<System::Byte>(0)));
label->Location = System::Drawing::Point(0, 5);
label->Name = L"label1";
label->Size = System::Drawing::Size(51, 16);
label->TabIndex = 1;
label->Text = "! -- Saisir une URL de fil rss -- !";
this->Controls->Add(label);
return;
}
if (nbMax_ <= 0)
{
System::Windows::Forms::Label ^ label = gcnew System::Windows::Forms::Label();
label->AutoSize = true;
label->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 9.75F,
System::Drawing::FontStyle::Bold,
System::Drawing::GraphicsUnit::Point, static_cast<System::Byte>(0)));
label->Location = System::Drawing::Point(0, 5);
label->Name = L"label1";
label->Size = System::Drawing::Size(51, 16);
label->TabIndex = 1;
label->Text = "! -- Saisissez un nombre de news à afficher -- !";
this->Controls->Add(label);
return;
}

listOfNews = gcnew cli::array<System::Windows::Forms::LinkLabel ^>(nbMax_);
CRssObject ^ rssObject = GetObjRss(true);
if (!rssObject)
{
System::Windows::Forms::Label ^ label = gcnew System::Windows::Forms::Label();
label->AutoSize = true;
label->Font = (gcnew System::Drawing::Font(L"Microsoft Sans Serif", 9.75F,
System::Drawing::FontStyle::Bold,
System::Drawing::GraphicsUnit::Point, static_cast<System::Byte>(0)));
label->Location = System::Drawing::Point(0, 5);
label->Name = L"label1";
label->Size = System::Drawing::Size(51, 16);
label->TabIndex = 1;
label->Text = "! -- Le flux RSS n'est pas valide 2.0 -- !";
this->Controls->Add(label);
return;
}

groupBox = gcnew System::Windows::Forms::GroupBox();
groupBox->Location = System::Drawing::Point(0, 30);
groupBox->Name = L"groupBox1";
groupBox->AutoSize = false;
groupBox->Size = System::Drawing::Size(136, 35);
groupBox->TabIndex = 2;
groupBox->TabStop = false;
groupBox->Text = rssObject->title;

for (int i=0;i<nbMax_;i++)
{
if (i < rssObject->listItem->Count)
{
CItemRss ^ currItem = static_cast<CItemRss ^>(rssObject->listItem[i]);
System::Windows::Forms::LinkLabel^ linkLabel;
linkLabel = gcnew System::Windows::Forms::LinkLabel();
linkLabel->AutoSize = true;
linkLabel->Location = System::Drawing::Point(5, 20+(15*i));
linkLabel->Name = L"linkLabel"+Convert::ToString(i);
linkLabel->Size = System::Drawing::Size(55, 13);
linkLabel->TabIndex = 0;
linkLabel->TabStop = true;
linkLabel->AutoSize = true;
linkLabel->LinkArea = System::Windows::Forms::LinkArea(0, currItem->title->Length + 5);
linkLabel->Links[0]->LinkData = currItem->link;
linkLabel->LinkClicked += gcnew System::Windows::Forms::LinkLabelLinkClickedEventHandler(
this, &rssFeeder::rssFeederControl::linkLabel_LinkClicked);
linkLabel->MouseHover += gcnew System::EventHandler(
this, &rssFeeder::rssFeederControl::linkLabel_MouseHover);
linkLabel->Paint += gcnew System::Windows::Forms::PaintEventHandler(
this, &rssFeederControl::linkLabel_Paint);
if (currItem->nbComments >= 0)
linkLabel->Text = currItem->title + " ("+Convert::ToString(currItem->nbComments)+")";
else
linkLabel->Text = currItem->title;
listOfNews[i] = linkLabel;
groupBox->Height += 15;
groupBox->Controls->Add(listOfNews[i]);
}
}
this->Controls->Add(groupBox);

}

CRssObject ^ GetObjRss(bool forceReload)
{
if (forceReload || !rssObj_)
{
rssObj_ = gcnew CRssObject();
if (!rssObj_->LoadFromUrl(url_, nbMax_, urlComment_))
return nullptr;
}
return rssObj_;
}

Ensuite on crée le groupBox, puis on parcourt la liste d'item, et on crée un linkLabel pour chaque item.
On renseigne notamment linkLabel->Links[0]->LinkData avec l'URL de la news courante.
On ajoute aussi un handler pour traiter l'évènement du clic sur le LinkLabel. De la même façon, on rajoute un handler pour le MouseHover, afin d'afficher un toolTip lorsqu'on passe sur le lien.
Enfin, on affiche le titre avec entre parenthèses, le nombre de commentaires sur la news (si le fil RSS de commentaires a été renseigné).

Remarque : J'ai mis aussi un accesseur (privé) sur l'objet contenant les informations du fil RSS. Il y a un semblant de cache, avec un paramètre qui précise si on veut réeffectuer la lecture depuis l'URL ou pas.

2.5.Associer une image à son contrôle (l'attribut ToolboxBitmap)

Lors de notre première compilation, nous avons vu qu'une image par défaut était associée à notre contrôle, lorsqu'il s'affiche dans la barre d'outils de Visual Studio. Il s'agissait d'une roue crantée.
Visual Studio nous offre la possibilité de définir notre propre image pour ce contrôle.
Autant cette manipulation est simple avec C# ou VB.Net, dans le cas d'un contrôle développé en C++/CLI, la méthode est moins évidente.
Pour ce faire, il faut :

- ajouter la ligne suivante [ToolboxBitmap(rssFeeder::rssFeederControl::typeid)], juste avant la définition de la classe ;

- créer un bitmap 16x16 ;

- le nommer de la forme Namespace.NomDeLaClasse.bmp, ce qui dans notre cas donne : rssFeeder.rssFeederControl.bmp ;

- l'ajouter en tant que ressource : clic droit sur le projet --> Add --> Resource --> Import (bitmap) ;

- lier la ressource à la DLL. Clic droit sur le projet --> Properties --> Linker --> Input --> Embed Managed Resource File --> et mettre le nom du bitmap (rssFeeder.rssFeederControl.bmp) ;

- recompiler le tout.

Vous obtiendrez désormais une image personnalisée à côté de votre contrôle, comme ci-dessous. (Vous ne me tiendrez pas rigueur de mon manque de créativité dans le domaine du graphisme …).

2.6.Ajout d'un évènement sur le contrôle

J'ai parlé tout à l'heure de l'interception du clic sur le LinkLabel. On pourrait souhaiter par exemple, lors du clic sur un lien, afficher la page URL pointée dans le navigateur par défaut.
Ceci est intéressant, mais pose un problème. En effet, comme cette action est définie dans le control, à chaque clic sur le label, on affichera la page dans le navigateur par défaut ; et ceci pour tous les contrôles.
Il pourrait être avantageux d'avoir un comportement en fonction d'un contrôle. Par exemple, on peut vouloir simplement récupérer l'URL pour l'afficher dans un contrôle WebBrowser, ou gérer en plus la coloration d'un lien déjà visité. Bref, faire notre propre action lorsqu'on clique sur un lien.
Dans le cas d'un contrôle classique (par exemple un bouton), vous avez la possibilité de définir une action (par exemple un clic) personnalisée sur ce contrôle.
Or, dans notre cas, depuis le designer, on ne peut pas différencier chaque LinkLabel du contrôle, il apparaît comme un et un seul contrôle.
Pour pouvoir accéder à l'évènement du clic sur un LinkLabel, nous allons devoir créer un évènement public sur le contrôle.

Ce que nous souhaitons, c'est rajouter un évènement ItemClick qui sera levé lorsqu'on cliquera sur un LinkLabel.
Pour ce faire, rajoutons un delegate dans notre contrôle :

delegate void ItemClickHandler(Object ^ sender, LinkLabelLinkClickedEventArgs ^ e);

Ensuite, ajoutons l'évènement à la catégorie :

[Category("Configuration"), Browsable(true), Description("Évènement associé au clic sur un item")]
event ItemClickHandler ^ itemClick;

Remarque : Ici vous pouvez constater que j'utilise l'attribut Browsable, qui permet de dire si l'attribut est affiché (ce qui est le cas par défaut si l'on ne renseigne par cet attribut). Vous pouvez aussi constater comment regrouper tous les attributs sur une seule ligne.

Enfin, dans la méthode associée à l'évènement d'un clic sur un label dans notre contrôle utilisateur, rajoutons ce code.

void linkLabel_LinkClicked(Object ^ sender, System::Windows::Forms::LinkLabelLinkClickedEventArgs ^ e)
{
if (&rssFeederControl::itemClick != nullptr)
itemClick(this, e);
}

Ainsi, après compilation et utilisation du contrôle dans une form, nous pouvons voir dans les évènements du contrôle :

C'est grâce à cet évènement que nous pourrons capter l'évènement du clic sur un linkLabel et faire notre traitement personnalisé.

2.7.Cacher un évènement par défaut sur le contrôle

Par défaut, nous disposons d'un certain nombre de propriétés et d'évènements sur notre contrôle.
Il peut être utile de supprimer un évènement (plus précisément le cacher) s'il n'a pas d'utilité et pour ne pas embrouiller l'utilisateur.

Prenons l'exemple des évènements ControlAdded, ControlRemoved et DoubleClick. Il suffira d'utiliser l'attribut Browsable à false pour les cacher, comme ci-dessous :

[Browsable(false)]
event ControlEventHandler^ ControlAdded;

[Browsable(false)]
event ControlEventHandler^ ControlRemoved;

[Browsable(false)]
event ControlEventHandler^ DoubleClick;

2.8.Améliorons l'apparence de notre contrôle

Juste histoire de peaufiner un peu, nous allons améliorer l'aspect visuel de notre contrôle, car pour l'instant, il dépasse un peu de partout en largeur.
Nous allons donc adapter le texte à afficher en fonction de la taille du contrôle.
Pour ce faire, nous allons intercepter l'évènement Paint sur le LinkLabel (je n'en ai pas parlé tout à l'heure, mais il est créé lors du refreshControl).
Puis nous allons utiliser la méthode MeasureString pour mesurer la taille en pixel de la chaîne à afficher. Tant qu'elle dépasse, on la réduit d'un caractère (on rajoute au passage les trois petits points … pour dire que la chaîne a été coupée).

private: System::Void linkLabel_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e)
{
LinkLabel ^ ll = static_cast<LinkLabel ^>(sender);
CRssObject ^ rssObject = GetObjRss(false);
CItemRss ^ itemRss;
for each (itemRss in rssObject->listItem)
if (itemRss->link == ll->Links[0]->LinkData->ToString())
break;
SizeF ^ size = e->Graphics->MeasureString(ll->Text, ll->Font);
int i = 1;
while (size->Width >= this->Width - 15)
{
if (itemRss->nbComments >=0)
ll->Text = itemRss->title->Substring(0,itemRss->title->Length - i) + " ... (" +
Convert::ToString(itemRss->nbComments) + ")";
else
ll->Text = itemRss->title->Substring(0,itemRss->title->Length - i) + " ...";
size = e->Graphics->MeasureString(ll->Text, ll->Font);
i++;
}
groupBox->Width = this->Width - 5;
}

Remarque : on aurait pu aussi le faire pour la hauteur, mais je préfère dans ce cas que l'utilisateur redimensionne son contrôle pour faire tenir le nombre de news voulues.

Encore une petite amélioration. Vu que l'on coupe le titre de la news pour qu'il rentre dans le contrôle, on va afficher la totalité du titre dans une bulle d'aide lorsque l'utilisateur passera la souris sur le lien.
Petit bonus, on rajoutera aussi le nom de l'auteur de la news, s'il y en a un.
Ceci se passe dans la méthode MouseHover, créée dans le refreshControl.

private: System::Void linkLabel_MouseHover(System::Object^  sender, System::EventArgs^  e)
{
LinkLabel ^ ll = static_cast<LinkLabel ^>(sender);
CRssObject ^ rssObject = GetObjRss(false);
for each (CItemRss ^ itemRss in rssObject->listItem)
if (itemRss->link == ll->Links[0]->LinkData->ToString())
{
String ^ author;
if (itemRss->author != "")
author = " (par " + itemRss->author +")";
toolTip1->Show(itemRss->title + author ,
static_cast<Windows::Forms::IWin32Window ^>(sender));
}
}

2.9.Téléchargement du contrôle

Voilà, le contrôle est terminé et prêt à l'amélioration ou à l'utilisation.
Vous pouvez le télécharger avec ses sources à cette adresse : Télécharger ici (32 ko).

3.Création du programme d'exemple d'utilisation de notre contrôle utilisateur

Pour illustrer le fonctionnement de notre nouveau contrôle, nous allons créer une petite application très simple de lecture de fil RSS.
Pour ce faire, créons une nouvelle application Winforms.

Déposez quatre contrôles, comme ci-dessous.
Sur cette copie d'écran, vous voyez :
- un contrôle où aucune propriété n'a été remplie (le contrôle affiche le message d'erreur comme quoi il faut renseigner l'URL du fil RSS) ;
- un contrôle où seulement l'URL du fil RSS a été renseignée (le contrôle affiche le message d'erreur comme quoi il faut indiquer le nombre de news à afficher) ;
- un contrôle où l'URL pointe vers un fichier XML non compatible avec la version 2.0. des spécifications RSS (le contrôle affiche le message d'erreur comme quoi le flux RSS n'est pas valide) ;
- un contrôle où tout est renseigné, même l'URL du RSS de commentaires (le contrôle affiche le contrôle tel qu'il apparaîtra lors de l'exécution).

Lorsque tout est correctement renseigné, on obtient une form comme celle ci-dessous.
Ici, je n'ai pas renseigné les fils RSS de commentaires sur les blogs d'Anomaly et de Farscape, ce qui fait qu'on ne voit pas le nombre de commentaires entre parenthèses).

Il ne nous reste plus, pour l'exemple, qu'à gérer l'évènement d'un clic sur un lien. Je prends l'exemple du blog d'Anomaly, et dans la fenêtre de propriétés / events, je génère l'évènement ItemClick.

private: System::Void rssFeederControl2_itemClick(System::Object^  sender, System::Windows::Forms::LinkLabelLinkClickedEventArgs^  e)
{
MessageBox::Show("Vous avez cliqué sur " + e->Link->LinkData->ToString());
}

Avec ce code, vous allez afficher dans un MessageBox le lien de l'URL.
Ce qui nous permet de faire un exemple plus évolué, avec le blog général de developpez.com

private: System::Void rssFeederControl4_itemClick(System::Object^  sender, System::Windows::Forms::LinkLabelLinkClickedEventArgs^  e)
{
System::Diagnostics::Process::Start(e->Link->LinkData->ToString());
e->Link->Visited = true;
}

Ainsi, lorsque je cliquerai sur le lien, il s'ouvrira dans le navigateur par défaut, puis le lien sera marqué comme lu (coloration différente).

Notez au passage le tooltip quand on passe sur le lien, qui affiche la chaîne complète ainsi que le nom de l'auteur.

(On pourrait éventuellement envisager de sérialiser les URL visitées, pour que lorsqu'on relancera l'application, on ne perde pas cette information).

Vous pouvez télécharger le lecteur RSS et ses sources à cette adresse : Télécharger ici (41 ko).

4.Conclusion

Voilà, à travers ce tutoriel d'introduction à la création de contrôles utilisateur, vous avez pu avoir un aperçu de ce qu'il faut faire pour créer son userControl (à travers l'exemple du lecteur RSS).
Vous pouvez télécharger l'ensemble des sources à cette adresse : Télécharger ici (74 ko)

Nous avons aperçu quelques utilisations des attributs du contrôle pour rendre son utilisation agréable à partir de Visual Studio.
Je ne les ai bien sûr pas toutes détaillées, le but de ce tutoriel étant de montrer comment utiliser le C++/CLI pour créer un contrôle utilisateur.
Vous trouverez sur Internet beaucoup de tutoriels qui décrivent plus précisément les attributs des contrôles et d'autres fonctionnalités. Par exemple le tutoriel d'Olivier Delmotte, qui est en VB.Net, mais à partir du moment où vous avez compris le principe avec le C++/CLI, ce sera simple à adapter.

Remerciements

Je remercie toute l'équipe C++ pour leur relecture attentive du document.

Contact

Si vous constatez une erreur dans le tutoriel, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.