WPF – Redimensionnement d’une fenêtre personnalisée

3 minutes read

Dans l’article précédent nous avons vu comment implémenter les comportements de base d’une fenêtre personnalisée, soit dans l’ordre :

  • Boutons réduire, maximiser, fermer
  • Déplacement de la fenêtre grâce à une zone sensible
  • Maximisation par double clic sur la zone sensible

Aujourd’hui nous allons nous attaquer à l’implémentation du redimensionnement d’une fenêtre grâce à la souris en sélectionnant un des bords de celle-ci.

Première étape, rendre les bord sensibles. Pour celà nous allons superposer une série de rectangles à notre zone de contenu.

CustomWPFWindowWithSensitiveAreas

Dans la capture ci-dessus vous pouvez voir les dits rectangles. Il y en a 8 au total ce qui fait 4 pour chaque bord et 4 pour chaque angles. Biensûr dans notre application ils ne seront pas visibles. Commençons par changer le layout de notre application.

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="180" Width="350"
        AllowsTransparency="True" WindowStyle="None"
        Background="{x:Null}"
        PreviewMouseMove="ResetCursor">
    <Grid x:Name="LayoutRoot">
        <Grid x:Name="ContentGrid" Background="White">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="25" />
                <ColumnDefinition Width="25" />
                <ColumnDefinition Width="25" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="25" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Rectangle Fill="Gray" Grid.Row="0" Grid.ColumnSpan="4"
                       PreviewMouseDown="Header_Drag" />
            <TextBlock FontSize="24" Text="Fenêtre custom" Width="190" 
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       Grid.Row="1" Grid.ColumnSpan="4" />
            <Button Content="_" Grid.Column="1" Click="MinimizeButton_Click" />
            <Button Content="[]" Grid.Column="2" Click="RestoreButton_Click" />
            <Button Content="+" Grid.Column="3" Click="CloseButton_Click" />
        </Grid>
        <Grid x:Name="FrameGrid"
              PreviewMouseDown="Resize" MouseMove="DisplayResizeCursor">
            <Grid.Resources>
                <Style TargetType="{x:Type Rectangle}">
                    <Setter Property="Fill" Value="#00000000" />
                    <Setter Property="Stroke" Value="{x:Null}" />
                 </Style>
            </Grid.Resources>
            <Rectangle x:Name="top" Height="7" Margin="8,0,8,0"
                       VerticalAlignment="Top" />
            <Rectangle x:Name="bottom" Height="7" Margin="8,0,8,0"
                       VerticalAlignment="Bottom" />
            <Rectangle x:Name="left" Width="8" Margin="0,7,0,7"
                       HorizontalAlignment="Left" />
            <Rectangle x:Name="right" Width="8" Margin="0,7,0,7"
                       HorizontalAlignment="Right" />
            <Rectangle x:Name="bottomLeft" Width="8" Height="7"
                       HorizontalAlignment="Left" VerticalAlignment="Bottom" />
            <Rectangle x:Name="bottomRight" Width="8" Height="7"
                       VerticalAlignment="Bottom" HorizontalAlignment="Right" />
            <Rectangle x:Name="topRight" Width="8" Height="7"
                       HorizontalAlignment="Right" VerticalAlignment="Top" />
            <Rectangle x:Name="topLeft" Width="8" Height="7"
                       HorizontalAlignment="Left" VerticalAlignment="Top" />
            <Path Data="M9.5390625,2.4619789 L9.5390625,11.133854 L0.8671875,11.133854 z"
                  HorizontalAlignment="Right" VerticalAlignment="Bottom"
                  Height="8.5" Width="8.5" Margin="0,0,1,1"
                  StrokeThickness="0" StrokeDashArray="0.5 1"
                  RenderTransformOrigin="0.5,0.5" Stretch="Fill" Fill="Black"
                  IsHitTestVisible="False" />
        </Grid>
    </Grid>
</Window>

Vous noterez que notre grille principale (LayoutRoot) contient désormais deux grilles superposées nommées respectivement ContentGrid et FrameGrid.

ContentGrid est la grille dans laquelle le contenu de notre application est placée.

FrameGrid est la grille qui contient les rectangles rendus visibles dans la capture ci-dessus.

Maintenant il nous faut ajouter les nouveaux gestionnaires d’évènements associés à notre fenêtre. Dans l’ordre d’apparition du XAML nous avons :

  • ResetCursor (permet de réinitialiser le curseur à chaque fois que la souris se déplace au dessus de la fenêtre)
  • Resize (permet de redimensionner la fenêtre en fonction des mouvements de la souris)
  • DisplayResizeCursor (permet d’afficher le bon curseur de redimensionnement en fonction de la position de la souris sur les bords de la fenêtre)

Vous verrez que nous avons encore besoin d’interop.

private void ResetCursor(object sender, MouseEventArgs e)
{
    if (Mouse.LeftButton != MouseButtonState.Pressed)
    {
        Cursor = Cursors.Arrow;
    }
}

private const int WmSyscommand = 0x112;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

private void ResizeWindow(ResizeDirection direction)
{
    SendMessage(_hwndSource.Handle, WmSyscommand, (IntPtr)(61440 + direction), IntPtr.Zero);
}

private void Resize(object sender, MouseButtonEventArgs e)
{
    var clickedRectangle = e.OriginalSource as Rectangle;
    if (clickedRectangle == null)
        return;

    switch (clickedRectangle.Name)
    {
        case "top":
            Cursor = Cursors.SizeNS;
            ResizeWindow(ResizeDirection.Top);
            break;
        case "bottom":
            Cursor = Cursors.SizeNS;
            ResizeWindow(ResizeDirection.Bottom);
            break;
        case "left":
            Cursor = Cursors.SizeWE;
            ResizeWindow(ResizeDirection.Left);
            break;
        case "right":
            Cursor = Cursors.SizeWE;
            ResizeWindow(ResizeDirection.Right);
            break;
        case "topLeft":
            Cursor = Cursors.SizeNWSE;
            ResizeWindow(ResizeDirection.TopLeft);
            break;
        case "topRight":
            Cursor = Cursors.SizeNESW;
            ResizeWindow(ResizeDirection.TopRight);
            break;
        case "bottomLeft":
            Cursor = Cursors.SizeNESW;
            ResizeWindow(ResizeDirection.BottomLeft);
            break;
        case "bottomRight":
            Cursor = Cursors.SizeNWSE;
            ResizeWindow(ResizeDirection.BottomRight);
            break;
        default:
            break;
    }
}

private void DisplayResizeCursor(object sender, MouseEventArgs e)
{
    var clickedRectangle = e.OriginalSource as Rectangle;
    if (clickedRectangle == null)
        return;

    switch (clickedRectangle.Name)
    {
        case "top":
            Cursor = Cursors.SizeNS;
            break;
        case "bottom":
            Cursor = Cursors.SizeNS;
            break;
        case "left":
            Cursor = Cursors.SizeWE;
            break;
        case "right":
            Cursor = Cursors.SizeWE;
            break;
        case "topLeft":
            Cursor = Cursors.SizeNWSE;
            break;
        case "topRight":
            Cursor = Cursors.SizeNESW;
            break;
        case "bottomLeft":
            Cursor = Cursors.SizeNESW;
            break;
        case "bottomRight":
            Cursor = Cursors.SizeNWSE;
            break;
        default:
            break;
    }
}

public enum ResizeDirection
{
    Left = 1,
    Right = 2,
    Top = 3,
    TopLeft = 4,
    TopRight = 5,
    Bottom = 6,
    BottomLeft = 7,
    BottomRight = 8,
}

Nous avons enfin une fenêtre qui a tout les comportements que l’on attendrai d’une fenêtre classique à la différence près que nous avons la possibilité de la personnaliser de A à Z.

Je vous invite à télécharger le code source complet de l’application (Visual Studio 2010, .NET 3.5 Client Profile) sur mon skydrive.

Updated:

Leave a Comment