web-dev-qa-db-fra.com

Fenêtre sans bordure WPF avec style shadow VS2012

J'essaie de créer une application qui ressemble à Visual Studio 2012. J'ai utilisé WindowChrome pour supprimer les bordures de la fenêtre et changé la couleur de la bordure dans mon xaml.

Ce que je ne sais pas faire, c'est peindre l'ombre de la fenêtre, ici vous pouvez voir une capture d'écran de ce que je dis:

Visual Studio Borderless window with shadow

Comme vous pouvez le voir, il y a une ombre et sa couleur est également la couleur de la bordure

Savez-vous comment l'implémenter en utilisant WPF?

21
Daniel Peñalba

Mise à jour (octobre '17)

Cela fait maintenant quatre ans et j'étais intéressé à résoudre ce problème à nouveau et j'ai donc été déranger avec MahApps.Metro encore une fois et dérivé ma propre bibliothèque basée sur elle =. Ma bibliothèque ModernChrome fournit une fenêtre personnalisée qui ressemble à Visual Studio 2017:

ModernChrome Sample

Comme vous n'êtes probablement intéressé que par la partie sur la bordure rougeoyante, vous devez soit utiliser MahApps.Metro lui-même, soit voir comment j'ai créé une classe GlowWindowBehavior qui attache des bordures lumineuses à ma classe ModernWindow personnalisée. Il dépend fortement de certains éléments internes de MahApps.Metro et des deux propriétés de dépendance GlowBrush et NonActiveGlowBrush.

Si vous souhaitez uniquement inclure les bordures lumineuses à vos applications personnalisées, faites simplement référence à MahApps.Metro et copiez sur mon GlowWindowBehavior.cs et créez une classe de fenêtre personnalisée et adaptez les références en conséquence. C'est une question de 15 minutes tout au plus.

Cette question et ma réponse ont été consultées très fréquemment, donc j'espère que vous trouverez ma nouvelle solution appropriée utile :)


Article d'origine (février '13)

J'ai travaillé sur une telle bibliothèque pour copier l'interface utilisateur de Visual Studio 2012. Un chrome chrome n'est pas si difficile, mais ce que vous devez faire attention est cette bordure brillante qui est difficile à mettre en œuvre. Vous pourriez simplement dire définir la couleur d'arrière-plan de votre fenêtre sur transparent et définir la le remplissage de la grille principale à environ 30 pixels. Une bordure autour de la grille peut être colorée et associée à un effet d'ombre coloré, mais cette approche vous oblige à définir AllowsTransparency sur true, ce qui réduit considérablement les performances visuelles de votre application, ce qui est quelque chose que vous ne voulez certainement pas faire!

Mon approche actuelle pour créer une telle fenêtre qui a juste un effet d'ombre colorée sur une bordure et est transparente mais n'a aucun contenu. Chaque fois que la position de ma fenêtre principale change, je mets simplement à jour la position de la fenêtre qui contient la bordure. Donc, au final, je gère deux fenêtres avec des messages pour simuler que la bordure ferait partie de la fenêtre principale. Cela était nécessaire car la bibliothèque DWM ne fournit pas un moyen d'avoir un effet d'ombre portée colorée pour les fenêtres et je pense que Visual Studio 2012 fait la même chose que j'ai essayé.

Et pour étendre ce message avec plus d'informations: Office 2013 fait cela différemment. La bordure autour d'une fenêtre n'a qu'une épaisseur et une couleur de 1px, mais l'ombre est dessinée par DWM avec un code comme celui-ci ici. Si vous pouvez vivre sans avoir de bordures bleues/violettes/vertes et juste habituelles, c'est l'approche que je choisirais! Ne définissez simplement pas AllowsTransparency sur true, sinon vous avez perdu.

Et voici une capture d'écran de ma fenêtre avec une couleur étrange pour mettre en évidence à quoi elle ressemble:

Metro UI


Voici quelques astuces pour commencer

Veuillez garder à l'esprit que mon code est assez long, de sorte que je ne pourrai vous montrer que les choses de base à faire et vous devriez pouvoir au moins commencer en quelque sorte. Tout d'abord, je vais supposer que nous avons en quelque sorte conçu notre fenêtre principale (soit manuellement soit avec le MahApps.Metro package que j'ai essayé hier - avec quelques modifications au code source c'est vraiment bien(1)) et nous travaillons actuellement pour implémenter la bordure d'ombre rougeoyante, que j'appellerai désormais GlowWindow. L'approche la plus simple consiste à créer une fenêtre avec le code XAML suivant

<Window x:Class="MetroUI.Views.GlowWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    x:Name="GlowWindow"
    Title="" Width="300" Height="100" WindowStartupLocation="Manual"
    AllowsTransparency="True" Background="Transparent" WindowStyle="None"
    ShowInTaskbar="False" Foreground="#007acc" MaxWidth="5000" MaxHeight="5000">
    <Border x:Name="OuterGlow" Margin="10" Background="Transparent"
            BorderBrush="{Binding Foreground, ElementName=GlowWindow}"
            BorderThickness="5">
        <Border.Effect>
            <BlurEffect KernelType="Gaussian" Radius="15" RenderingBias="Quality" />
        </Border.Effect>
    </Border>
</Window>

La fenêtre résultante devrait ressembler à l'image suivante.

GlowWindow

Les prochaines étapes sont assez difficiles - lorsque notre fenêtre principale apparaît, nous voulons rendre GlowWindow visible mais derrière la fenêtre principale et nous devons mettre à jour la position de GlowWindow lorsque la fenêtre principale est déplacée ou redimensionnée. Ce que je suggère pour éviter les problèmes visuels qui peuvent ET se produiront, c'est de masquer GlowWindow lors de chaque changement d'emplacement ou de taille de la fenêtre. Une fois cette action terminée, montrez-la à nouveau.

J'ai une méthode qui est appelée dans différentes situations (ça peut être beaucoup mais juste pour être sûr)

private void UpdateGlowWindow(bool isActivated = false) {
    if(this.DisableComposite || this.IsMaximized) {
        this.glowWindow.Visibility = System.Windows.Visibility.Collapsed;
        return;
    }
    try {
        this.glowWindow.Left = this.Left - 10;
        this.glowWindow.Top = this.Top - 10;
        this.glowWindow.Width = this.Width + 20;
        this.glowWindow.Height = this.Height + 20;
        this.glowWindow.Visibility = System.Windows.Visibility.Visible;
        if(!isActivated)
            this.glowWindow.Activate();
    } catch(Exception) {
    }
}

Cette méthode est principalement appelée dans mon WndProc personnalisé que j'ai attaché à la fenêtre principale:

/// <summary>
/// An application-defined function that processes messages sent to a window. The WNDPROC type
/// defines a pointer to this callback function.
/// </summary>
/// <param name="hwnd">A handle to the window.</param>
/// <param name="uMsg">The message.</param>
/// <param name="wParam">Additional message information. The contents of this parameter depend on
/// the value of the uMsg parameter.</param>
/// <param name="lParam">Additional message information. The contents of this parameter depend on
/// the value of the uMsg parameter.</param>
/// <param name="handled">Reference to boolean value which indicates whether a message was handled.
/// </param>
/// <returns>The return value is the result of the message processing and depends on the message sent.
/// </returns>
private IntPtr WindowProc(IntPtr hwnd, int uMsg, IntPtr wParam, IntPtr lParam, ref bool handled) {
    // BEGIN UNMANAGED WIN32
    switch((WinRT.Message)uMsg) {
        case WinRT.Message.WM_SIZE:
            switch((WinRT.Size)wParam) {
                case WinRT.Size.SIZE_MAXIMIZED:
                    this.Left = this.Top = 0;
                    if(!this.IsMaximized)
                        this.IsMaximized = true;
                    this.UpdateChrome();
                    break;
                case WinRT.Size.SIZE_RESTORED:
                    if(this.IsMaximized)
                        this.IsMaximized = false;
                    this.UpdateChrome();
                    break;
            }
            break;

        case WinRT.Message.WM_WINDOWPOSCHANGING:
            WinRT.WINDOWPOS windowPosition = (WinRT.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WinRT.WINDOWPOS));
            Window handledWindow = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
            if(handledWindow == null)
                return IntPtr.Zero;
            bool hasChangedPosition = false;
            if(this.IsMaximized == true && (this.Left != 0 || this.Top != 0)) {
                windowPosition.x = windowPosition.y = 0;
                windowPosition.cx = (int)SystemParameters.WorkArea.Width;
                windowPosition.cy = (int)SystemParameters.WorkArea.Height;
                hasChangedPosition = true;
                this.UpdateChrome();
                this.UpdateGlowWindow();
            }
            if(!hasChangedPosition)
                return IntPtr.Zero;
            Marshal.StructureToPtr(windowPosition, lParam, true);
            handled = true;
            break;
    }
    return IntPtr.Zero;
    // END UNMANAGED WIN32
}

Cependant, il reste un problème - une fois que vous redimensionnez votre fenêtre principale, GlowWindow ne pourra pas couvrir toute la fenêtre avec sa taille. C'est-à-dire que si vous redimensionnez votre fenêtre principale à environ MaxWidth de votre écran, le widt de GlowWindow aurait la même valeur + 20 que j'y ai ajouté une marge de 10. Par conséquent, le bord droit serait interrompu juste avant le bord droit de la fenêtre principale qui a l'air moche. Pour éviter cela, j'ai utilisé un crochet pour faire de GlowWindow une fenêtre d'outils:

this.Loaded += delegate {
    WindowInteropHelper wndHelper = new WindowInteropHelper(this);
    int exStyle = (int)WinRT.GetWindowLong(wndHelper.Handle, (int)WinRT.GetWindowLongFields.GWL_EXSTYLE);
    exStyle |= (int)WinRT.ExtendedWindowStyles.WS_EX_TOOLWINDOW;
    WinRT.SetWindowLong(wndHelper.Handle, (int)WinRT.GetWindowLongFields.GWL_EXSTYLE, (IntPtr)exStyle);
};

Et encore, nous aurons des problèmes - lorsque vous passez la souris sur GlowWindow et cliquez avec le bouton gauche, il sera activé et obtiendra le focus, ce qui signifie qu'il chevauchera la fenêtre principale qui ressemble à ceci:

Overlapping GlowWindow

Pour éviter cela, il suffit d'attraper l'événement Activated de la bordure et de mettre la fenêtre principale au premier plan.

Comment devez-vous procéder?

Je suggère de NE PAS essayer ceci - il m'a fallu environ un mois pour atteindre ce que je voulais et il y a toujours des problèmes, tels que j'opterais pour une approche comme Office 2013 - bordure colorée et ombre habituelle avec les appels de l'API DWM - rien d'autre et pourtant ça a l'air bien.

Office 2013


(1) Je viens de modifier certains fichiers pour activer la bordure autour de la fenêtre qui est désactivée sur Windows 8 pour moi. De plus, j'ai manipulé le Padding de la barre de titre de telle sorte qu'il ne semble pas aussi carré et enfin j'ai changé la propriété All-Caps pour imiter la façon de Visual Studio de rendre le titre. Jusqu'à présent, le MahApps.Metro est un meilleur moyen de dessiner la fenêtre principale car il prend même en charge AeroSnap que je ne pouvais pas implémenter avec les appels P/Invoke habituels.

33
Christian Ivicevic

Vous pouvez utiliser ce simple code xaml

<Window x:Class="VS2012.MainWindow" 
         xmlns=http://schemas.Microsoft.com/winfx/2006/xaml/presentation 
         xmlns:x=http://schemas.Microsoft.com/winfx/2006/xaml 
         Title="MainWindow" 
         Height="100" Width="200" 
         AllowsTransparency="True" WindowStyle="None" Background="Transparent"> 
<Border BorderBrush="DarkOrange" BorderThickness="1" Background="White" Margin="5">
         <Border.Effect>
                <DropShadowEffect ShadowDepth="0" BlurRadius="5" Color="DarkOrange"/>
         </Border.Effect>
</Border>
</Window> 
4
Hady Mahmoodi

C'est ce qu'on appelle le "style Metro" (style Windows 8). Je pense que this Code Project article est intéressant pour vous et il vous aidera.

Vous pouvez essayer Elysium , qui a obtenu une licence sous MIT et inclus les classes ApplicationBar et ToastNotification, ou MetroToolKit , de codeplext également).

This est un excellent tutoriel sur Elysium, je pense que cela vous aide.

Pour l'ombre, ajoutez simplement un BitmapEffect à un Border de votre Grid en XAML:

<Grid>
    <Border BorderBrush="#FF006900" BorderThickness="3" Height="157" HorizontalAlignment="Left" Margin="12,12,0,0" Name="border1" VerticalAlignment="Top" Width="479" Background="#FFCEFFE1" CornerRadius="20, 20, 20, 20">
        <Border.BitmapEffect>
          <DropShadowBitmapEffect Color="Black" Direction="320" ShadowDepth="10" Opacity="0.5" Softness="5" />
        </Border.BitmapEffect>
        <TextBlock Height="179" Name="textBlock1" Text="Hello, this is a beautiful DropShadow WPF Window Example." FontSize="40" TextWrapping="Wrap" TextAlignment="Center" Foreground="#FF245829" />
    </Border>
</Grid>

enter image description here

3
Ionică Bizău

J'essaie d'obtenir le même effet, mon application utilise .NET 4 et donc je ne peux pas utiliser directement WindowChrome (donc j'utilise la bibliothèque Microsoft Windows Shell pour obtenir le même).

Dans ce thread il est correctement noté qu'en utilisant spy ++, on peut voir que Visual Studio a quatre fenêtres appelées VisualStudioGlowWindow pour implémenter le effet brillant. Il a déjà été décrit à de nombreux endroits comment la propriété AllowsTransparency sur true peut réduire les performances.

J'ai donc essayé d'aller dans le sens VS et le résultat n'est pas mauvais (du moins pour moi); pas besoin d'utiliser du flou ou un effet similaire sur la fenêtre principale, j'ai juste eu à me battre un peu avec certains états de fenêtre (focus/visible/caché).

J'ai mis toutes les informations nécessaires sur github - j'espère que cela peut vous aider.

1
SiMoStro