在无边框的WinForm上添加阴影效果

26
我想在整个表单周围添加一个阴影,就像第一张图片那样,只是那是一个WPF,而不是WinForm。现在我想在WinForm上添加相同的阴影。
这就是我想要的...

Windows Form Shadow

不是这个..¬ Windows Form Shadow
(来源: codeproject.com)

https://dev59.com/s3A75IYBdhLWcg3wS3BA - Nikhil Agrawal
你有看过这个链接吗?https://dev59.com/d3E95IYBdhLWcg3wE56C 你尝试覆盖 createparams 吗? - Edper
1
@Edper 覆盖 cp 将会在第二张图片中产生阴影。 - Sam Oyl
1
您可以通过使用分层窗口来实现所需的效果。 - User 12345678
我看到第一个和第二个例子之间有区别,但是这是因为背景(一张图片与白色)和窗口前景颜色(白色与浅蓝色)不同。这两者都与投影无关。第二个例子到底有什么问题? - Cody Gray
2
@CodyGray 先生,您是错误的。第一个示例中的投影阴影也可在窗口的左侧和顶部看到(阴影完全环绕控件 - 就像太阳正在看着窗口),但在第二个示例中,就好像太阳在左边,因此只能在第二个示例窗口的底部和右侧看到阴影。 - SE13013
5个回答

50

在WinForms中,您可以仅覆盖窗体的受保护的CreateParams属性,并向类样式添加CS_DROPSHADOW标志。例如:

public class ShadowedForm : Form {
    protected override CreateParams CreateParams {
        get {
            const int CS_DROPSHADOW = 0x20000;
            CreateParams cp = base.CreateParams;
            cp.ClassStyle |= CS_DROPSHADOW;
            return cp;
        }
    }

    // ... other code ...
}

但是,有几个注意事项…

  1. 此标志仅适用于顶级窗口,在 Win32 中指重叠和弹出窗口。它对子窗口(例如控件)没有影响。我记得在某个地方听说过这种限制已经从 Windows 8 中删除,但我找不到确认这一点的链接,并且我没有安装用于测试的 Windows 8。

  2. 用户可能已经完全禁用了此功能。如果是这样,无论你如何要求,都不会得到阴影效果。这是设计上的考虑。你的应用程序不应该尝试覆盖此请求。你可以通过 P/Invoke SystemParametersInfo 函数并传递 SPI_GETDROPSHADOW 标志来确定是否启用或禁用了投影效果。

  3. Aero 主题还为顶级窗口添加阴影。这个效果与 CS_DROPSHADOW 是分开且独立的,只有在启用 Aero 时才起作用。没有办法为单个窗口关闭或打开此效果。此外,由于 Aero 主题已从 Windows 8 中删除,它将永远不会有这些阴影。


他已经有了那个阴影效果。DWM PInvoke在Winforms中同样有效,请使用OnHandleCreated。所有这些在Win8中看起来都非常糟糕。 - Hans Passant
1
正如@HansPassant所说,cp也会像第二张图片一样产生相同的阴影。 - Sam Oyl
这很简单且效果不错!但是,在我看来,阴影不如@Ryan Loggerythm发布的解决方案漂亮。它只有较少的扩散,并且可能比他的略微更暗。但是伟大的答案和伟大的选择。 - Heriberto Lugo

24

这是我的C#实现。它类似于Al.Petro的,但您会注意到当您失去焦点并重新获取焦点时,阴影会重新绘制。

我还添加了允许鼠标拖动的代码。

public partial class Form1: Form
{
    [DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
    private static extern IntPtr CreateRoundRectRgn
    (
        int nLeftRect, // x-coordinate of upper-left corner
        int nTopRect, // y-coordinate of upper-left corner
        int nRightRect, // x-coordinate of lower-right corner
        int nBottomRect, // y-coordinate of lower-right corner
        int nWidthEllipse, // height of ellipse
        int nHeightEllipse // width of ellipse
     );        

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

    [DllImport("dwmapi.dll")]
    public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

    [DllImport("dwmapi.dll")]
    public static extern int DwmIsCompositionEnabled(ref int pfEnabled);

    private bool m_aeroEnabled;                     // variables for box shadow
    private const int CS_DROPSHADOW = 0x00020000;
    private const int WM_NCPAINT = 0x0085;
    private const int WM_ACTIVATEAPP = 0x001C;

    public struct MARGINS                           // struct for box shadow
    {
        public int leftWidth;
        public int rightWidth;
        public int topHeight;
        public int bottomHeight;
    }

    private const int WM_NCHITTEST = 0x84;          // variables for dragging the form
    private const int HTCLIENT = 0x1;
    private const int HTCAPTION = 0x2;

    protected override CreateParams CreateParams
    {
        get
        {
            m_aeroEnabled = CheckAeroEnabled();

            CreateParams cp = base.CreateParams;
            if (!m_aeroEnabled)
                cp.ClassStyle |= CS_DROPSHADOW;

            return cp;
        }
    }

    private bool CheckAeroEnabled()
    {
        if (Environment.OSVersion.Version.Major >= 6)
        {
            int enabled = 0;
            DwmIsCompositionEnabled(ref enabled);
            return (enabled == 1) ? true : false;
        }
        return false;
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_NCPAINT:                        // box shadow
                if (m_aeroEnabled)
                {
                    var v = 2;
                    DwmSetWindowAttribute(this.Handle, 2, ref v, 4);
                    MARGINS margins = new MARGINS()
                    {
                        bottomHeight = 1,
                        leftWidth = 1,
                        rightWidth = 1,
                        topHeight = 1
                    };
                    DwmExtendFrameIntoClientArea(this.Handle, ref margins);

                }
                break;
            default:
                break;
        }
        base.WndProc(ref m);

        if (m.Msg == WM_NCHITTEST && (int)m.Result == HTCLIENT)     // drag the form
            m.Result = (IntPtr)HTCAPTION;

    }

    public Form1()
    {
        m_aeroEnabled = false;

        this.FormBorderStyle = FormBorderStyle.None;

        InitializeComponent();
    }
}

注意:如果您将MARGINS结构的所有边缘设置为-1,并从OnSizeChanged方法中调用CreateRoundRectRgn,则也可以实现圆角边缘 - 但是当窗体失去焦点时,这会导致投影阴影消失...有什么想法吗? - Matthew Layton
1
这样做可以更轻松地控制鼠标并在无边框窗体上拖动。-- protected override void WndProc(ref Message m) { if (m.Msg == 0x0084 /WM_NCHITTEST/) { m.Result = (IntPtr)2; // HTCLIENT return; } base.WndProc(ref m); } - MarkKiessling
这段代码可以运行,但是我收到了一个错误提示:“跨线程操作无效:从创建它的线程以外的线程访问控件 'MainWindow'”。该错误出现在CreateParams cp = base.CreateParams;附近,请问有什么解决方法吗? - Pavel
1
运行得非常好!非常感谢。 - WiiLF

9

只需复制代码,就可以得到像这样的Windows 7投影效果 >>> http://marcin.floryan.pl/wp-content/uploads/2010/08/WPF-Window-native-shadow.png

Imports System.Runtime.InteropServices

Public Class IMSS_SplashScreen
    Private aeroEnabled As Boolean
    Protected Overrides ReadOnly Property CreateParams() As CreateParams
        Get
            CheckAeroEnabled()
            Dim cp As CreateParams = MyBase.CreateParams
            If Not aeroEnabled Then
                cp.ClassStyle = cp.ClassStyle Or NativeConstants.CS_DROPSHADOW
                Return cp
            Else
                Return cp
            End If
        End Get
    End Property
    Protected Overrides Sub WndProc(ByRef m As Message)
        Select Case m.Msg
            Case NativeConstants.WM_NCPAINT
                Dim val = 2
                If aeroEnabled Then
                    NativeMethods.DwmSetWindowAttribute(Handle, 2, val, 4)
                    Dim bla As New NativeStructs.MARGINS()
                    With bla
                        .bottomHeight = 1
                        .leftWidth = 1
                        .rightWidth = 1
                        .topHeight = 1
                    End With
                    NativeMethods.DwmExtendFrameIntoClientArea(Handle, bla)
                End If
                Exit Select
        End Select
        MyBase.WndProc(m)
    End Sub
    Private Sub CheckAeroEnabled()
        If Environment.OSVersion.Version.Major >= 6 Then
            Dim enabled As Integer = 0
            Dim response As Integer = NativeMethods.DwmIsCompositionEnabled(enabled)
            aeroEnabled = (enabled = 1)
        Else
            aeroEnabled = False
        End If
    End Sub
End Class
Public Class NativeStructs
    Public Structure MARGINS
        Public leftWidth As Integer
        Public rightWidth As Integer
        Public topHeight As Integer
        Public bottomHeight As Integer
    End Structure
End Class
Public Class NativeMethods
    <DllImport("dwmapi")> _
    Public Shared Function DwmExtendFrameIntoClientArea(ByVal hWnd As IntPtr, ByRef pMarInset As NativeStructs.MARGINS) As Integer
    End Function
    <DllImport("dwmapi")> _
    Friend Shared Function DwmSetWindowAttribute(ByVal hwnd As IntPtr, ByVal attr As Integer, ByRef attrValue As Integer, ByVal attrSize As Integer) As Integer
    End Function
    <DllImport("dwmapi.dll")> _
    Public Shared Function DwmIsCompositionEnabled(ByRef pfEnabled As Integer) As Integer
    End Function
End Class
Public Class NativeConstants
    Public Const CS_DROPSHADOW As Integer = &H20000
    Public Const WM_NCPAINT As Integer = &H85
End Class

实现很棒,但是当控件失去/获得焦点或与其他窗口交互时,似乎会失去其阴影效果。你知道如何解决这个问题吗? - Matthew Layton
@seriesOne 看起来你只需要处理窗体的调整大小事件并调用 this.Update() 或 this.Refresh()。这样窗口就会“强制”重新绘制自己,从而也重新绘制阴影。我还没有尝试过这段代码,但我猜这就是原因。 - SE13013

7

我已经在这里回答过了:如何在无边框Winform窗体上添加阴影,且不会出现闪烁或消失

以下是我的答案:

请尝试以下步骤,并在有错误时回复:

将以下代码添加到名为 DropShadow.cs 的新代码文件中;

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Core
{
    public class DropShadow
    {
        #region Shadowing

        #region Fields

        private bool _isAeroEnabled = false;
        private bool _isDraggingEnabled = false;
        private const int WM_NCHITTEST = 0x84;
        private const int WS_MINIMIZEBOX = 0x20000;
        private const int HTCLIENT = 0x1;
        private const int HTCAPTION = 0x2;
        private const int CS_DBLCLKS = 0x8;
        private const int CS_DROPSHADOW = 0x00020000;
        private const int WM_NCPAINT = 0x0085;
        private const int WM_ACTIVATEAPP = 0x001C;

        #endregion

        #region Structures

        [EditorBrowsable(EditorBrowsableState.Never)]
        public struct MARGINS
        {
            public int leftWidth;
            public int rightWidth;
            public int topHeight;
            public int bottomHeight;
        }

        #endregion

        #region Methods

        #region Public

        [DllImport("dwmapi.dll")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);

        [DllImport("dwmapi.dll")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);

        [DllImport("dwmapi.dll")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public static extern int DwmIsCompositionEnabled(ref int pfEnabled);

        [EditorBrowsable(EditorBrowsableState.Never)]
        public static bool IsCompositionEnabled()
        {
            if (Environment.OSVersion.Version.Major < 6) return false;

            bool enabled;
            DwmIsCompositionEnabled(out enabled);

            return enabled;
        }

        #endregion

        #region Private

        [DllImport("dwmapi.dll")]
        private static extern int DwmIsCompositionEnabled(out bool enabled);

        [DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
        private static extern IntPtr CreateRoundRectRgn
        (
            int nLeftRect,
            int nTopRect,
            int nRightRect,
            int nBottomRect,
            int nWidthEllipse,
            int nHeightEllipse
         );

        private bool CheckIfAeroIsEnabled()
        {
            if (Environment.OSVersion.Version.Major >= 6)
            {
                int enabled = 0;
                DwmIsCompositionEnabled(ref enabled);

                return (enabled == 1) ? true : false;
            }
            return false;
        }

        #endregion

        #region Overrides

        public void ApplyShadows(Form form)
        {
            var v = 2;

            DwmSetWindowAttribute(form.Handle, 2, ref v, 4);

            MARGINS margins = new MARGINS()
            {
                bottomHeight = 1,
                leftWidth = 0,
                rightWidth = 0,
                topHeight = 0
            };

            DwmExtendFrameIntoClientArea(form.Handle, ref margins);
        }

        #endregion

        #endregion

        #endregion
    }
}

在您的表单中,在InitializeComponent();下面加入这行代码。
(new Core.DropShadow()).ApplyShadows(this);

然而,我建议切换到WPF。我自己曾经长期使用WinForms,但是WPF在设计用户界面方面提供了更好的灵活性。您还可以自定义所有控件,而无需任何框架或包。学习WPF需要一些时间,但是这很值得。


这个答案应该放在最上面。它提供了最自然的结果,并且容易以多种形式呈现。 - saksham
表单底部出现了一条细线!!!我认为这是由于边距引起的。也许在默认的 BackColor 下不可见,但如果您设置一个深色的 BackColor,例如 45; 45; 48,您就可以看到它。有没有什么办法可以纠正这个问题?感谢您的时间!!! - Simos Sigma

0
据我所知,在WinForms中没有直接实现这个的方法。
相反,您可以按照以下步骤操作:
1)使用Photoshop或其他工具创建具有所需投影的图像。
2)将此图像用作窗体的背景图像。
3)将窗体的FormBorderStyle属性设置为None。
4)完成!
5)注意:确保以正确的格式(例如png)保存图像,以便投影效果能够正常工作。

1
这可以在WinForms中完成。使用分层窗口可以实现。 - User 12345678
@ByteBlast,您能否详细说明一下,以便我们感到启迪...我上面讨论的方法是几种可能方法之一。 - Amit Mittal
在WinForms中,没有直接的方法来实现这一点。 - User 12345678
不使用Win32 API,这是否可能?对不起,我很无知。 - Amit Mittal
1
@AmitMittal 这是无法做到的,因为我不想使用 TransparencyKey。 - Sam Oyl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接