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

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.

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

1.Introduction

La programmation Windows classique nous a fortement habitué 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 tache est légèrement facilité avec les MFC, mais elle reste toujours un travail laborieux qui décourage rapidement le développeur.
Avec les winforms par contre, cette laborieuse tache 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.

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

2.1.Création du projet

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

Image non disponible

2.2.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 :

Image non disponible

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

2.3.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).

Image non disponible



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 égal à 24 bits.

2.4.Gérer le déplacement de la form

A 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 fenetre.
Nous allons donc palier 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 :
 
Sélectionnez

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'utiliation 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.

 
Sélectionnez

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 :

 
Sélectionnez

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

2.5.Téléchargement du projet

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

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

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

Image non disponible

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

 
Sélectionnez

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 renvoi une région qui va nous servir à définir les contours de notre bitmap.
Voyons de plus prêt cette fonction :
Cette fonction parcours 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éfini comme transparent.

 
Sélectionnez

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

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

3.4.Téléchargement du projet

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

4.Conclusion

J'espère que cette 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...

Remerciements

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

Contact

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

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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.