【问题标题】:DropShadow for WPF Borderless Window用于 WPF 无边框窗口的 DropShadow
【发布时间】:2011-03-23 06:52:33
【问题描述】:

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

【问题讨论】:

    标签: wpf windows transparency dropshadow


    【解决方案1】:

    我已经编写了一个小实用程序类,它能够完全按照您的要求进行操作:在无边框 Window 上放置标准阴影,但将 AllowsTransparency 设置为 false

    您只需调用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;
            }
        }
    }
    

    【讨论】:

    • 这很好,除了我遇到一个问题,即打开子窗口(也带有阴影)会减少父窗口的阴影,然后当子窗口关闭时,将其完全删除。奇怪的错误,尚未找到导致它的原因。当 childwindow 不使用 dropshadow 时,它似乎也这样做)
    • 工作就像一个魅力。 :) 谢谢。
    • 注意:当我尝试在本机应用程序中使用这种方法时,阴影不会出现,直到我将边距设为非零。 (对于无边框窗口,实际值似乎无关紧要,只要它们不为 0)。
    • 虽然这种方法很有前途,但它还有另一个问题。如果你离开你的应用程序然后再返回,阴影就消失了。
    • 不要使用System.Drawing.Printing.Margins,它会在某些机器上导致奇怪的图形故障。而是按照下面的@Omer Ran 建议在本地定义结构。
    【解决方案2】:

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

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

    【讨论】:

    • 这是一个非常好的观点。还需要注意的是,这个问题只能在一小部分机器上重现,所以特别讨厌。
    • 到目前为止,我已经在 Intel HD 520 显卡以及其他笔记本电脑上的其他 Intel HD 卡上注意到它。
    • 另外,如果你把Margins 转换成class,那么问题又来了。这一定与System.Drawing.Printing也是class有关。
    【解决方案3】:

    如果您允许窗口调整边框大小,通过将ResizeMode 设置为CanResize,您将获得操作系统阴影。然后,您可以将 MaxWidthMinWidthMaxHeightMinHeight 设置为阻止调整大小的值。

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

    编辑:

    从那时起,如果您的窗口大小是固定的,只需添加阴影,可能作为&lt;Rectangle/&gt; 作为&lt;Canvas/&gt; 内容中的第一个元素

    类似这样的:

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

    请注意,第一个RectangleFill 属性是部分透明的,您也可以使用RectangleOpacity 属性来做到这一点。您可以使用自己的图形或其他形状来自定义投影的外观。

    请注意,这违反了您将 AllowsTransparency 设为 False 的要求,但您别无选择:如果您想要透明度,则必须允许它。

    【讨论】:

    • 关于编辑:我确实尝试过这样的事情,但它确实会影响性能。我在 Windows XP 上做这个,不知道 Vista/7。
    • 您不必在将AllowsTransparency 设置为False 和能够投下阴影之间做出选择。您可以在位于主窗口边缘的 4 个窗口中将 AllowsTranspareceny 设置为 True,这将负责删除阴影。这样,你的主窗口性能就会完好无损。
    【解决方案4】:

    为什么不直接使用与“窗口”相同的对象创建阴影,但要更大且在其后面。

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

    或者如果你需要一个透明的标题栏,可以用&lt;Border&gt;代替

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-12
      • 1970-01-01
      • 2011-09-22
      • 1970-01-01
      • 2012-11-23
      • 1970-01-01
      相关资源
      最近更新 更多