用于 WPF 无边框窗口的 DropShadow

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3372303/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 21:58:40  来源:igfitidea点击:

DropShadow for WPF Borderless Window

wpfwindowstransparencydropshadow

提问by TripShock

I have a WPF Window with WindowStyle set to none. Is there some way I can force this window to drop a shadow (like the one you get when WindowStyle is not none)? I don't want to set AllowTransparency to true, because it affects the performance. And I also don't want to disable hardware rendering (I read somewhere that transparency performs better with it disabled).

我有一个 WindowStyle 设置为 none 的 WPF 窗口。有什么方法可以强制这个窗口投下阴影(就像你在 WindowStyle 不是 none 时得到的那样)?我不想将 AllowTransparency 设置为 true,因为它会影响性能。而且我也不想禁用硬件渲染(我在某处读到透明度在禁用时性能更好)。

回答by cprcrack

I have written a little utility class that is able to do exactly what you want: drop a standard shadow over a borderless Windowbut having AllowsTransparencyset to false.

我编写了一个小实用程序类,它能够完全满足您的要求:在无边框上放置一个标准阴影,Window但已AllowsTransparency设置为false.

You just have to call the DropShadowToWindow(Window window)method. It is preferred that you make this call just after the window's constructor's InitializeComponent(), but it will work even if you call it after the window is shown.

您只需要调用该DropShadowToWindow(Window window)方法。最好在窗口的构造函数的 之后进行此调用InitializeComponent(),但即使在显示窗口之后调用它也能工作。

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;
        }
    }
}

回答by Omer Ran

Patrick's answer works great, except when a win32 window is hosted. When that happens, you notice that the hosted window is "washed out" (it looks like windows is applying the 'glass sheet' effect to the entire hosted window). This odd behavior is fixed when defining the structure locally, e.g.

帕特里克的回答很有效,除非托管了 win32 窗口。发生这种情况时,您会注意到托管窗口已被“清除”(看起来窗口正在将“玻璃板”效果应用于整个托管窗口)。在本地定义结构时,这种奇怪的行为是固定的,例如

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

回答by Rob Perkins

If you permit the window to have resize borders, by setting ResizeModeto CanResize, then you will get the OS drop shadow. You can then set the MaxWidth, MinWidth, MaxHeight, and MinHeightto values which will prevent the resize.

如果您允许窗口调整边框大小,通过设置ResizeModeCanResize,那么您将获得操作系统投影。然后MaxWidth,您可以将MinWidthMaxHeight、 和设置为MinHeight防止调整大小的值。

If you have a borderless window without a style you will have to provide all the appearance for the window in your own visual tree, including a drop shadow, since this combination of settings is the same as saying that you don't want what the OS provides.

如果您有一个没有样式的无边框窗口,则必须在您自己的可视化树中为窗口提供所有外观,包括投影,因为这种设置组合与说您不想要操作系统相同提供。

EDIT:

编辑:

From that point, if your window size is fixed, simply add the dropshadow, perhaps as a <Rectangle/>as the first element in the content of a <Canvas/>

从那时起,如果您的窗口大小是固定的,只需添加阴影,也许作为 a<Rectangle/>作为 a 内容中的第一个元素<Canvas/>

something like this:

像这样:

<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>

Note that the Fillproperty of that first Rectangleis partially transparent, which you could also do with the Opacityproperty of the Rectangle. You could use a graphic of your own or a different shape, to customize the appearance of the drop shadow.

注意,Fill那性质首先Rectangle是部分透明的,您也可以做的Opacity财产Rectangle。您可以使用自己的图形或不同的形状来自定义投影的外观。

Note that this violates your requirement to have AllowsTransparencybe False, but you have no choice: if you want transparency, you have to allow it.

请注意,这侵犯了您的要求有AllowsTransparencyFalse的,但你别无选择:如果你想要的透明度,你必须允许它。

回答by Carol

Why not just create the shadow with the same object as your "window" but bigger and behind it.

为什么不使用与您的“窗口”相同的对象创建阴影,而是在它的后面创建更大的阴影。

<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>

Or if you need a transparent title bar, it could be replaced by a <Border>

或者,如果您需要一个透明的标题栏,则可以将其替换为 <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: I just noticed OP wants AllowsTransparency set to False. I can't see a shadow to work without it being "True", thouth.

编辑:我刚刚注意到 OP 希望 AllowsTransparency 设置为 False。如果没有“真实”,我就看不到影子工作。