Création d'une winform non rectangulaire en C++/CLI avec Visual Studio 2005

Nico-pyright(c)

Ce tutoriel présente deux techniques pour construire une winform (form dotnet) non rectangulaire, avec l'apparence de notre choix en C++/CLI avec Visual Studio 2005. ♪

- Titre : Création d'une winform non rectangulaire en C++/CLI avec Visual Studio 2005

- Auteur : Nico-pyright(c)

- Parution : 4 mai 2006

- Mise à jour : 4 mai 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

- I. Introduction

- II. Première méthode : Limitée, mais aucune ligne de code

- II-A. Création du projet

- II-B. Création de l'image de fond de la form

- II-C. Ajouter des contrôles à notre form

- II-D. Gérer le déplacement de la form

- II-E. Téléchargement du projet

- III. Deuxième méthode : un peu de code et des régions

- III-A. Création du projet et positionnement des contrôles

- III-B. Chargement de l'image et création de la région

- III-C. Gérer le déplacement de la form

- III-D. Téléchargement du projet

- IV. Conclusion

- V. Remerciements

- VI. Contact

I - Introduction

La programmation Windows classique nous a fortement habitués aux fenêtres rectangulaires.

Même si cela est possible, il est laborieux de construire des fenêtres avec l'API WIn32. Il faut alors se munir de régions et de beaucoup de courage. Cette tâche est légèrement facilitée avec les MFC, mais elle reste toujours un travail laborieux qui décourage rapidement le développeur.
Avec les winforms par contre, cette laborieuse tâche se transforme immédiatement en une partie de plaisir. Je vais vous montrer dans ce tutoriel, deux méthodes pour créer des forms non rectangulaires avec votre langage favori, le C++/CLI.

Les exemples présentés dans ce tutoriel utiliseront le logiciel Visual Studio 2005 de Microsoft et plus particulièrement Visual C++ 2005 et le framework dotnet 2.0.

II - Première méthode : Limitée, mais aucune ligne de code

Je vais vous présenter ici une première façon de faire, qui a l'inconvénient de fonctionner uniquement sur des bureaux Windows dont le nombre maximum de couleurs est obligatoirement inférieur à 24 bits.
Cette résolution est en général bien souvent suffisante, mais cette méthode ne peut être garantie pour des résolutions supérieures.
En contrepartie de cette limitation, cette méthode fonctionne sans une seule ligne de code à ajouter pour la création de la form non rectangulaire.

II-A - Création du projet

Créez donc un nouveau projet Visual C++ 2005 --> CLR --> Windows Form Application.

II-B - Création de l'image de fond de la form

Créez ensuite une image bitmap représentant la fenêtre que vous voulez obtenir.
Le point important est d'avoir un fond d'une couleur choisie, qui va nous servir de couleur transparente. J'ai choisi le bleu de Microsoft Paint, c'est le bleu web classique.
Voici mon image :

Associez-la à la propriété BackgroudImage de votre form.
Définissez ensuite la propriété TransparencyKey et positionnez la couleur qui a servi de fond (dans mon cas, le bleu web (blue)).

II-C - Ajouter des contrôles à notre form

Vous pouvez ensuite ajouter un ou deux contrôles sur la form. N'oubliez pas de définir la propriété FormBorderStyle à None.
Voici ce que cela donne chez moi : (le label à sa propriété BackColor à Transparent).

Compilez le tout, et lancez, vous obtiendrez alors une superbe form non rectangulaire, correspondant à notre image.

Si cela ne fonctionne pas, c'est que vous devez avoir une résolution supérieure ou égale à 24 bits.

II-D - Gérer le déplacement de la form

À l'exécution, on se rend vite compte que c'est insuffisant : on ne peut pas fermer la form ni la déplacer à la souris.
Nous avons effectivement enlevé la barre de titre, ce qui empêche le déplacement de la form. Nous n'avons plus également la croix qui permet de fermer l'application.
Rassurez-vous, un petit coup de ALT+F4 et vous pourrez fermer la fenêtre.
Nous allons donc pallier cet inconvénient, en développant notre propre implémentation du déplacement de la form.

Ici plusieurs solutions :

- vous pouvez par exemple traiter l'événement MouseDown avec ce code :

ReleaseCapture();
SendMessage(Handle, WM_SYSCOMMAND, 0xF012, 0);

Ceci présente des inconvénients à mon avis. On utilise déjà du code non managé, ce qui implique de compiler avec le mode /clr (uniquement) et de se priver des modes /clr:safe ou /clr:pure. Ensuite, ceci bloque l'utilisation des autres événements souris ;

- utiliser une méthode classique où l'on utilise les événements MouseDown, MouseUp et MouseMove:

Rajoutez ces deux membres privés à la classe.

System::Drawing::Point ^ mouseOffset;
bool isMouseDown;

Initialisez ensuite le booleen à false dans le constructeur de la classe.
Enfin, traitez les événements MouseUp, MouseDown et MouseMove comme suit :

private: System::Void Form1_MouseDown(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e)
{
int xOffset;
int yOffset;
if (e->Button == System::Windows::Forms::MouseButtons::Left)
{
System::Drawing::Size ^ size = SystemInformation::FrameBorderSize;
xOffset = -e->X - size->Width;
yOffset = -e->Y - SystemInformation::CaptionHeight - size->Height;
mouseOffset = gcnew System::Drawing::Point(xOffset, yOffset);
isMouseDown = true;
}
}
private: System::Void Form1_MouseUp(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e)
{
if (e->Button == System::Windows::Forms::MouseButtons::Left)
isMouseDown = false;
}
private: System::Void Form1_MouseMove(System::Object^  sender, System::Windows::Forms::MouseEventArgs^  e)
{
if (isMouseDown)
{
System::Drawing::Point mousePos = System::Windows::Forms::Control::MousePosition;
mousePos.Offset(mouseOffset->X, mouseOffset->Y);
this->Location = mousePos;
}
}

II-E - Téléchargement du projet

Vous pouvez télécharger les sources de l'exemple complet à cette adresse : Télécharger ici (26 ko).

III - Deuxième méthode : un peu de code et des régions

Je vais vous présenter ici une deuxième façon de faire, qui utilise les régions du framework .Net. Toujours à partir d'une image, cette méthode présente un code relativement simple et adaptable facilement.
La contrepartie ici de cet effort est que vous n'êtes pas limité à une résolution de couleur.

III-A - Création du projet et positionnement des contrôles

On peut pour ce deuxième exemple, utiliser la même image de fond que pour le premier exemple.

Toujours à partir d'un projet Windows Form Application, on positionne les contrôles sur la form, et on change la propriété FormBorderStyle à None.

III-B - Chargement de l'image et création de la région

Passons maintenant au code :
Dans le constructeur, on va charger notre bitmap et l'associer à la propriété BackgroundImage de notre form.

Bitmap ^ imageDeFond = safe_cast<Bitmap ^>(Bitmap::FromFile("fond.bmp"));
this->Width = imageDeFond->Width;
this->Height = imageDeFond->Height;
this->Region = CreeRegionDepuisBitmap(imageDeFond);
this->BackgroundImage = imageDeFond;

On remarque la fonction CreeRegionDepuisBitmap qui renvoie une région qui va nous servir à définir les contours de notre bitmap.
Voyons de plus près cette fonction.
Cette fonction parcourt tous les pixels de l'image et crée une région de 1 pixel, si le pixel rencontré n'est pas un pixel que l'on définit comme transparent.

System::Drawing::Region ^ CreeRegionDepuisBitmap(Bitmap ^ image)
{
GraphicsUnit ^ aPixel = GraphicsUnit::Pixel;
GraphicsUnit % r_aPixel = *aPixel;
RectangleF rectangleImage = image->GetBounds(r_aPixel);
System::Drawing::Size ^ tailleImage = gcnew System::Drawing::Size(
Convert::ToInt32(rectangleImage.Width),
Convert::ToInt32(rectangleImage.Height));

System::Drawing::Drawing2D::GraphicsPath ^ regionGraphique = gcnew System::Drawing::Drawing2D::GraphicsPath();

System::Drawing::Size ^ size = SystemInformation::FrameBorderSize;
int deltaX = size->Width - 1;
int deltaY = SystemInformation::CaptionHeight + 3;
Color ^ couleurTransparente;
for (int y = 0; y < tailleImage->Height; y++)
{
for (int x = 0; x < tailleImage->Width; x++)
{
if (!couleurTransparente) // on décide que la couleur transparente sera le premier pixel
couleurTransparente = image->GetPixel(x, y);
if (image->GetPixel(x, y).ToArgb() != couleurTransparente->ToArgb())
{
Rectangle regionPixelAAjouter(x + deltaX, y + deltaY , 1, 1);
regionGraphique->AddRectangle(regionPixelAAjouter);
}
}
}
return gcnew System::Drawing::Region(regionGraphique);
}

On commence par récupérer les coordonnés de l'image, puis on parcourt l'image. On choisit que le premier pixel rencontré représente la couleur transparente.
Ensuite on compare le pixel en cours au pixel transparent et s'ils sont différents, on ajoute une région de 1 pixel sur 1.
Notez le delta en (X,Y) correspondant aux dimensions de la bordure. J'avoue avoir été obligé de tâtonner pour avoir un réglage exact, que je ne m'explique pas vraiment…

III-C - Gérer le déplacement de la form

Il ne reste plus qu'à gérer le déplacement de la form, ==> cf. au chapitre 1.

III-D - Téléchargement du projet

Vous pouvez télécharger les sources de l'exemple complet à cette adresse : Télécharger ici (16 ko).

IV - Conclusion

J'espère que cet article vous aura permis de comprendre comment créer une form non rectangulaire en C++/CLI.

Remarque : il est en général assez inopportun de modifier les habitudes des utilisateurs en proposant des fenêtres non classiques. Il est important de veiller à l'ergonomie d'un logiciel et de ne pas perturber le fonctionnement classique des habitudes de l'utilisateur.
Cependant, il peut être très agréable de mettre un peu de fantaisie dans son application…

V - Remerciements

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

VI - 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.