Créer une fenêtre transparente sans chrome natif
Une fenêtre transparente en WPF se définit avec AllowsTransparency="True". Pour que ce paramètre soit accepté, le style de fenêtre doit être WindowStyle="None". En contrepartie, la fenêtre perd sa bordure de redimensionnement et sa barre de titre.
Pour restaurer ces fonctionnalités, on utilise la classe WindowChrome. Elle permet de conserver une fenêtre entièrement transparente tout en redéfinissant la zone de redimensionnement et la zone de déplacement.
Le point de départ est le suivant :
<Window x:Class="WpfTransparentResizable.FenetrePrincipale"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fenêtre transparente"
Height="600" Width="800"
Background="Transparent"
AllowsTransparency="True"
WindowStyle="None">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="20"/>
</WindowChrome.WindowChrome>
<!-- Contenu de la fenêtre -->
<Grid>
<Border Margin="10" Background="White" CornerRadius="25">
<Border.Effect>
<DropShadowEffect Direction="0" ShadowDepth="0"
BlurRadius="20" Opacity="0.25"
Color="#FF5B5B5B"/>
</Border.Effect>
</Border>
</Grid>
</Window>
Autoriser le redimensionnement par les bords
La propriété ResizeBorderThickness définit l’épaisseur de la zone sensible autour de la fenêtre. Plus elle est grande, plus l’utilisateur peut éloigner le curseur de la zone client tout en conservant la possibilité de redimensionner. La valeur CaptionHeight="0" supprime la zone de titre native, car on souhaite définir manuellement la zone de déplacement.
Déplacer la fenêtre par glisser-déposer
Pour déplacer la fenêtre, on ajoute un élément invisible en haut du contenu et on appelle DragMove() lors de l’appui sur le bouton gauche.
<!-- Zone de déplacement -->
<Border Name="ZoneDeplacement" Width="780" Height="100"
VerticalAlignment="Top" Margin="0,10,0,0"
Background="Transparent" Cursor="Hand"
MouseLeftButtonDown="ZoneDeplacement_MouseLeftButtonDown"/>
Le code-behind correspondant :
private void ZoneDeplacement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
return;
DragMove();
}
Désactiver l’agrandissement automatique sur les bords d’écran
Par défaut, une fenêtre redimensionnable se maximise automatiquement lorsqu’elle est glissée vers le haut de l’écran. Pour bloquer ce comportement pendant le déplacement, on bascule temporairement le ResizeMode vers NoResize, puis on le restaure dès que le glisser-déposer est terminé.
private void ZoneDeplacement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
return;
ResizeMode modeOriginal = this.ResizeMode;
if (this.ResizeMode != ResizeMode.NoResize)
this.ResizeMode = ResizeMode.NoResize;
try
{
DragMove();
}
finally
{
if (this.ResizeMode != modeOriginal)
this.ResizeMode = modeOriginal;
}
}
Maintenir un ratio largeur/hauteur fixe
Pour forcer la fenêtre à garder un ratio constant (ici 4:3), on intercepte le message Win32 WM_EXITSIZEMOVE, qui signale la fin d’une opération de redimensionnement. On ajuste alors la dimension opposée en fonction du ratio.
private const int WM_EXITSIZEMOVE = 0x0232;
private int DerniereLargeur;
private int DerniereHauteur;
private readonly double RatioFenetre = 4.0 / 3.0;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
if (PresentationSource.FromVisual(this) is HwndSource source)
{
source.AddHook(new HwndSourceHook(TraitementMessages));
}
DerniereLargeur = (int)this.Width;
DerniereHauteur = (int)this.Height;
}
private IntPtr TraitementMessages(IntPtr hwnd, int msg, IntPtr wParam,
IntPtr lParam, ref bool handled)
{
if (msg == WM_EXITSIZEMOVE)
{
if (this.Height != DerniereHauteur)
{
this.Width = this.Height * RatioFenetre;
}
else if (this.Width != DerniereLargeur)
{
this.Height = this.Width / RatioFenetre;
}
DerniereLargeur = (int)this.Width;
DerniereHauteur = (int)this.Height;
}
return IntPtr.Zero;
}
Redimensionner le contenu de manière proportionnelle
Le contenu de la fenêtre peut être mis à l’échelle automatiquement grâce au contrôle Viewbox avec la valeur UniformToFill. Tout l’intérieur du Viewbox est alors redimensionné proportionnellement lorsque la fenêtre change de taille.
<Viewbox Stretch="UniformToFill">
<Grid Width="800" Height="600">
<!-- Fond -->
<Border Margin="10" Background="White" CornerRadius="25">
<Border.Effect>
<DropShadowEffect Direction="0" ShadowDepth="0"
BlurRadius="20" Opacity="0.25"
Color="#FF5B5B5B"/>
</Border.Effect>
</Border>
<!-- Exemple de contenu -->
<Button Width="400" Height="200">
<TextBlock Text="Bonjour" FontSize="70"
TextAlignment="Center"/>
</Button>
<!-- Zone de déplacement -->
<Border Name="ZoneDeplacement" Width="780" Height="100"
VerticalAlignment="Top" Margin="0,10,0,0"
Background="Transparent" Cursor="Hand"
MouseLeftButtonDown="ZoneDeplacement_MouseLeftButtonDown"/>
</Grid>
</Viewbox>
Exemple complet
FenetrePrincipale.xaml
<Window x:Class="WpfTransparentResizable.FenetrePrincipale"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fenêtre transparente"
Height="600" Width="800"
Background="Transparent"
AllowsTransparency="True"
WindowStyle="None">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0" ResizeBorderThickness="20"/>
</WindowChrome.WindowChrome>
<Viewbox Stretch="UniformToFill">
<Grid Width="800" Height="600">
<Border Margin="10" Background="White" CornerRadius="25">
<Border.Effect>
<DropShadowEffect Direction="0" ShadowDepth="0"
BlurRadius="20" Opacity="0.25"
Color="#FF5B5B5B"/>
</Border.Effect>
</Border>
<Button Width="400" Height="200">
<TextBlock Text="Bonjour" FontSize="70"
TextAlignment="Center"/>
</Button>
<Border Name="ZoneDeplacement" Width="780" Height="100"
VerticalAlignment="Top" Margin="0,10,0,0"
Background="Transparent" Cursor="Hand"
MouseLeftButtonDown="ZoneDeplacement_MouseLeftButtonDown"/>
</Grid>
</Viewbox>
</Window>
FenetrePrincipale.xaml.cs
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
namespace WpfTransparentResizable
{
public partial class FenetrePrincipale : Window
{
public FenetrePrincipale()
{
InitializeComponent();
DerniereLargeur = (int)this.Width;
DerniereHauteur = (int)this.Height;
}
private void ZoneDeplacement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton != MouseButton.Left)
return;
ResizeMode modeOriginal = this.ResizeMode;
if (this.ResizeMode != ResizeMode.NoResize)
this.ResizeMode = ResizeMode.NoResize;
try
{
DragMove();
}
finally
{
if (this.ResizeMode != modeOriginal)
this.ResizeMode = modeOriginal;
}
}
private const int WM_EXITSIZEMOVE = 0x0232;
private int DerniereLargeur;
private int DerniereHauteur;
private readonly double RatioFenetre = 4.0 / 3.0;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
if (PresentationSource.FromVisual(this) is HwndSource source)
{
source.AddHook(new HwndSourceHook(TraitementMessages));
}
}
private IntPtr TraitementMessages(IntPtr hwnd, int msg, IntPtr wParam,
IntPtr lParam, ref bool handled)
{
if (msg == WM_EXITSIZEMOVE)
{
if (this.Height != DerniereHauteur)
{
this.Width = this.Height * RatioFenetre;
}
else if (this.Width != DerniereLargeur)
{
this.Height = this.Width / RatioFenetre;
}
DerniereLargeur = (int)this.Width;
DerniereHauteur = (int)this.Height;
}
return IntPtr.Zero;
}
}
}