DropShadow pour la fenêtre sans bordure WPF

J'ai une fenêtre WPF avec WindowStyle définie sur none. Est-il possible de forcer cette fenêtre à laisser tomber une ombre (comme celle que vous obtenez lorsque WindowStyle n'est pas none)? 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 matériel (j'ai lu quelque part que la transparence fonctionne mieux avec elle désactivée).

21
demandé sur TripShock 2010-07-30 18:14:15

5 réponses

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

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

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
répondu cprcrack 2016-10-16 13:30:03

Utilisez la Bibliothèque D'intégration Shell Microsoft WPF , plus facile et avec de meilleures performances

5
répondu Eduardo Molteni 2011-03-15 00:17:33

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 "délavée" (il semble que windows applique l'effet "feuille de verre" à l'ensemble de la fenêtre hébergée). Ce comportement étrange est corrigé lors de la définition locale de la structure, par exemple

[StructLayout(LayoutKind.Sequential)]
public struct Margins
{
    public int Left;
    public int Right;
    public int Top;
    public int Bottom;
}  
5
répondu Omer Ran 2013-10-14 16:18:47

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 la MaxWidth, MinWidth, MaxHeight, et MinHeight à valeurs qui vont empêcher 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 arborescence visuelle, y compris une ombre portée, car cette combinaison de paramètres équivaut à 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 le dropshadow, peut - être comme <Rectangle/> comme premier élément dans le contenu d'un <Canvas/>

Quelque Chose comme ceci:

<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 ce premier Rectangle est partiellement transparente, ce que vous pouvez également faire avec la propriété Opacity du Rectangle. Vous pouvez utiliser un graphique de votre propre ou d'une autre forme, pour personnaliser l'apparence de l'ombre portée.

Notez que cela viole votre exigence d'avoir AllowsTransparency être False, mais vous n'avez pas le choix: si vous voulez de la transparence, vous devez l'autoriser.

3
répondu Rob Perkins 2010-08-04 19:21:43

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

<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 peut être remplacée par <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 AllowsTransparency défini sur False. Je ne peux pas voir une ombre pour travailler sans qu'elle soit "vraie", thouth.

0
répondu Carol 2015-06-23 21:06:47