web-dev-qa-db-fra.com

DropShadow pour la fenêtre sans bordure WPF

J'ai une fenêtre WPF avec WindowStyle définie sur none. Existe-t-il un moyen de forcer cette fenêtre à supprimer une ombre (comme celle que vous obtenez lorsque WindowStyle n’en est pas une)? Je ne veux pas définir AllowTransparency sur true, car cela affecte les performances. Et je ne veux pas non plus désactiver le rendu du matériel (j'ai lu quelque part que la transparence fonctionnait mieux si elle était désactivée).

22
TripShock

J'ai écrit une petite classe utilitaire capable de faire exactement ce que vous voulez: déposez une ombre standard sur une Window sans bordure, mais AllowsTransparency étant défini sur false.

Il vous suffit d'appeler la méthode DropShadowToWindow(Window window). Il est préférable d’appeler cet appel juste après la fonction InitializeComponent() du constructeur de la fenêtre, mais cela fonctionnera même si vous l’appelez une fois la fenêtre affichée.

using System;
using System.Drawing.Printing;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

public static class DwmDropShadow
{
    [DllImport("dwmapi.dll", PreserveSig = true)]
    private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

    [DllImport("dwmapi.dll")]
    private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref Margins pMarInset);

    /// <summary>
    /// Drops a standard shadow to a WPF Window, even if the window is borderless. Only works with DWM (Windows Vista or newer).
    /// This method is much more efficient than setting AllowsTransparency to true and using the DropShadow effect,
    /// as AllowsTransparency involves a huge performance issue (hardware acceleration is turned off for all the window).
    /// </summary>
    /// <param name="window">Window to which the shadow will be applied</param>
    public static void DropShadowToWindow(Window window)
    {
        if (!DropShadow(window))
        {
            window.SourceInitialized += new EventHandler(window_SourceInitialized);
        }
    }

    private static void window_SourceInitialized(object sender, EventArgs e)
    {
        Window window = (Window)sender;

        DropShadow(window);

        window.SourceInitialized -= new EventHandler(window_SourceInitialized);
    }

    /// <summary>
    /// The actual method that makes API calls to drop the shadow to the window
    /// </summary>
    /// <param name="window">Window to which the shadow will be applied</param>
    /// <returns>True if the method succeeded, false if not</returns>
    private static bool DropShadow(Window window)
    {
        try
        {
            WindowInteropHelper helper = new WindowInteropHelper(window);
            int val = 2;
            int ret1 = DwmSetWindowAttribute(helper.Handle, 2, ref val, 4);

            if (ret1 == 0)
            {
                Margins m = new Margins { Bottom = 0, Left = 0, Right = 0, Top = 0 };
                int ret2 = DwmExtendFrameIntoClientArea(helper.Handle, ref m);
                return ret2 == 0;
            }
            else
            {
                return false;
            }
        }
        catch (Exception ex)
        {
            // Probably dwmapi.dll not found (incompatible OS)
            return false;
        }
    }
}
31
cprcrack

La réponse de Patrick fonctionne très bien, sauf lorsqu'une fenêtre win32 est hébergée. Lorsque cela se produit, vous remarquez que la fenêtre hébergée est "effacée" (il semblerait que windows applique l'effet "feuille de verre" à l'ensemble de l'hébergement hébergé. fenêtre). Ce comportement étrange est corrigé lors de la définition locale de la structure,

[StructLayout(LayoutKind.Sequential)]
public struct Margins
{
    public int Left;
    public int Right;
    public int Top;
    public int Bottom;
}  
5
Omer Ran

Utilisez la Bibliothèque d'intégration WPF de Microsoft , plus simple et plus performante

5
Eduardo Molteni

Si vous permettez à la fenêtre de redimensionner les bordures en définissant ResizeMode sur CanResize, vous obtiendrez l'ombre portée du système d'exploitation. Vous pouvez ensuite définir les valeurs MaxWidth, MinWidth, MaxHeight et MinHeight pour éviter le redimensionnement.

Si vous avez une fenêtre sans bordure sans style, vous devrez fournir toute l'apparence de la fenêtre dans votre propre arbre visuel, y compris une ombre portée, car cette combinaison de paramètres revient au même que de dire que vous ne voulez pas ce que le système d'exploitation fournit. 

MODIFIER:

À partir de ce point, si la taille de votre fenêtre est fixe, ajoutez simplement l’ombre portée, peut-être en tant que <Rectangle/> en tant que premier élément du contenu d’un <Canvas/>.

quelque chose comme ça:

<Window x:Class="MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="MainWindow" AllowsTransparency="True" Background="Transparent" WindowStyle="None">
    <Canvas>
        <Rectangle Fill="#33000000" Width="100"  Height="100"/>
        <Rectangle Fill="#FFFF0000" Width="95"  Height="95" />
    </Canvas>
</Window>

Notez que la propriété Fill de cette première Rectangle est partiellement transparente, ce que vous pouvez également faire avec la propriété Opacity de la Rectangle. Vous pouvez utiliser un graphique de votre choix ou une forme différente pour personnaliser l'apparence de l'ombre portée. 

Notez que cela enfreint votre exigence de AllowsTransparency à False, mais vous n’avez pas le choix: si vous voulez de la transparence, vous devez l’autoriser. 

3
Rob Perkins

Pourquoi ne pas simplement créer l'ombre avec le même objet que votre "fenêtre" mais plus grand et derrière celui-ci.

<Window x:Class="WPF_Custom_Look.ShadowWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="ShadowWindow" Height="400" Width="450" ResizeMode="NoResize" Background="Transparent" AllowsTransparency="True" WindowStyle="None">
<Grid>
    <Rectangle Fill="Black" Width="330" Opacity="0.5" Height="279">
        <Rectangle.Effect>
            <BlurEffect Radius="30"/>
        </Rectangle.Effect>
    </Rectangle>
    <Rectangle Fill="#FFFDFDFD" Width="312"  Height="260"/>

</Grid>

Ou si vous avez besoin d’une barre de titre transparente, elle pourrait être remplacée par un <Border>

<Canvas>
    <Border BorderBrush="Black" BorderThickness="7" Height="195" Width="304" Canvas.Left="53" Canvas.Top="25">
        <Border.Effect>
            <BlurEffect Radius="20"/>
        </Border.Effect>
    </Border>
    <Rectangle Fill="#FF86B0F9" Width="285"  Height="177" Opacity="0.7" Canvas.Left="62" Canvas.Top="34" MouseDown="Border_MouseDown"/>
    <Rectangle Fill="#FFFDFDFD" Width="285"  Height="143" Canvas.Left="62" Canvas.Top="68"/>
</Canvas>

Edit: Je viens de remarquer que OP veut que le paramètre AllowsTransparency soit défini sur False. Je ne peux pas voir une ombre pour travailler sans que ce soit "vrai", thouth.

0
Carol