无边框窗体中的Windows 7风格阴影效果

16

简短版:

目标: 在C#的无边框WinForm中实现深色Windows 7阴影。


已知解决方案1: 使用CreateParams实现简单的XP样式阴影。

问题: 效果不够强烈、亮度过低,不够美观。


已知解决方案2: 将窗体的GDI替换为位图。

问题: 无法使用控件,只能作为闪屏功能。


本帖目的: 寻找一个中间解决方案或者更好的解决方案。

. . .

详细版:

(编辑:如果没有表述清楚,我指的是任何窗体边框上的阴影。) 我知道在C#中可以使用以下方法制作XP样式的阴影:

C#代码1 - 简单的XP样式阴影(问题:亮度过低、效果不够强烈、不够美观)

// Define the CS_DROPSHADOW constant
private const int CS_DROPSHADOW = 0x00020000;

// Override the CreateParams property
protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ClassStyle |= CS_DROPSHADOW;
        return cp;
    }
}
然而,我正在尝试找出如何使它们看起来像在Windows 7中一样(更深更大的阴影),但无法找到最好的方法来实现这一点。
我现在有一个方法可以让我覆盖整个表单GDI并显示出类似于闪屏的效果(荣誉不属于我): C#代码2:用位图替换表单GDI(问题:无法使用表单控件,难以维护GUI)
    public void SetBitmap(Bitmap bitmap, byte opacity)
    {
        if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
            throw new ApplicationException("The bitmap must be 32ppp with alpha-channel.");

        // 1. Create a compatible DC with screen;
        // 2. Select the bitmap with 32bpp with alpha-channel in the compatible DC;
        // 3. Call the UpdateLayeredWindow.

        IntPtr screenDc = Win32.GetDC(IntPtr.Zero);
        IntPtr memDc = Win32.CreateCompatibleDC(screenDc);
        IntPtr hBitmap = IntPtr.Zero;
        IntPtr oldBitmap = IntPtr.Zero;

        try
        {
            hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));  // grab a GDI handle from this GDI+ bitmap
            oldBitmap = Win32.SelectObject(memDc, hBitmap);

            Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height);
            Win32.Point pointSource = new Win32.Point(0, 0);
            Win32.Point topPos = new Win32.Point(Left, Top);
            Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION();
            blend.BlendOp = Win32.AC_SRC_OVER;
            blend.BlendFlags = 0;
            blend.SourceConstantAlpha = opacity;
            blend.AlphaFormat = Win32.AC_SRC_ALPHA;

            Win32.UpdateLayeredWindow(this.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA);
        }
        finally
        {
            Win32.ReleaseDC(IntPtr.Zero, screenDc);
            if (hBitmap != IntPtr.Zero)
            {
                Win32.SelectObject(memDc, oldBitmap);
                Win32.DeleteObject(hBitmap);
            }
            Win32.DeleteDC(memDc);
        }
    }


    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle |= 0x00080000; // This form has to have the WS_EX_LAYERED extended style
            return cp;
        }
    }

然而,这确实给了我一个完整的32位背景(因为我需要手动添加投影效果),但我失去了创建可见表单元素的能力。

所以基本上,我正在尝试找出这两种方法之间的中间方法。某些东西可以在不失去其他功能或导致过多的重绘要求的情况下,给我深沉而黑暗的投影效果。


1
你的问题究竟是什么?这不是得到你解决方案的地方。 - Security Hound
4
问题很简单。我希望在这方面得到正确的指引。我的问题已经做了充分的研究,而且已经尽可能地表达清楚。我只是想知道是否有人知道更好的方法来创建类似于Windows 7的下拉阴影效果。 - corylulu
1
你能添加所需结果的屏幕截图或模型吗? - Kevin McCormick
2
@KevinMcCormick 这是我想要的最终效果,但我发布了我最终采用的方法。如果您有任何建议,欢迎提出。 - corylulu
2个回答

8
好的,经过约4小时的头脑风暴和编码,我终于开发出了一个解决方案。基本上,我制作了2个表单。
表单#1:通过修改和组合8个图像(4个角落梯度+每个方向4个线性梯度)来创建阴影,并使用我发布的第二个代码(C#代码2:将表单GDI替换为位图)将其设置为背景。 代码已经非常清楚地说明了它。
public partial class Dropshadow : Form
{

    public Dropshadow(Form parentForm)
    {
        /*This bit of code makes the form click-through. 
          So you can click forms that are below it in z-space */
        int wl = GetWindowLong(this.Handle, -20);
        wl = wl | 0x80000 | 0x20;
        SetWindowLong(this.Handle, -20, wl);

        InitializeComponent();

        //Makes the start location the same as parent.
        this.StartPosition = parentForm.StartPosition;

        parentForm.Activated += ParentForm_Activated; //Fires on parent activation to do a this.BringToFront() 
        this.Deactivate += This_Deactivated; //Toggles a boolean that ensures that ParentForm_Activated does fire when clicking through (this)
        parentForm.Closed += ParentForm_Closed; //Closes this when parent closes
        parentForm.Move += ParentForm_Move; //Follows movement of parent form

        //Draws border with standard bitmap modifications and merging
        /* Omitted function to avoid extra confusion */
        Bitmap getShadow = DrawBlurBorder(parentForm.ClientSize.Width, parentForm.ClientSize.Height);
        /* **This code was featured in the original post:** */
        SetBitmap(getShadow, 255); //Sets background as 32-bit image with full alpha.

        this.Location = Offset; //Set within DrawBlurBorder creates an offset 

    }
    private void ParentForm_Activated(object o, EventArgs e)
    {
        /* Sets this form on top when parent form is activated.*/
        if (isBringingToFront)
        { 
            /*Hopefully prevents recusion*/
            isBringingToFront = false;
            return;
        }

        this.BringToFront();


        /* Some special tweaks omitted to avoid confusion */
    }
    private void This_Deactivated(object o, EventArgs e)
    {
        /* Prevents recusion. */
        isBringingToFront = true;
    }
    /* Closes this when parent form closes. */
    private void ParentForm_Closed(object o, EventArgs e)
    {
        this.Close();
    }
    /* Adjust position when parent moves. */
    private void ParentForm_Move(object o, EventArgs e)
    {
        if(o is Form)
            this.Location = new Point((o as Form).Location.X + Offset.X, (o as Form).Location.Y + Offset.Y);
    }
 }

表格 #2: 这只是在启动时启动拖影表格,并且我创建了一些接口以允许更进一步的集成和灵活性,但为避免混淆而省略了。基本上是为了确保拖影表格不会从活动表格中带走鼠标点击,并且如果拖影表格位于顶部,不会强制用户必须点击两次按钮。


1
我不确定这是否是最佳的方法,但我认为它相当聪明。 - Mike Mayer

4

谢谢,Corylulu。

一个可行的类在这里

var f = new Dropshadow(this)
{
    BorderRadius = 40,
    ShadowColor = Color.Blue
};

f.RefreshShadow();

DEMO

DrawShadow类创建了一个类似位图的阴影,但并不完美。

这个类还不完美,但它可以工作。

顺便说一下,我不知道如何隐藏任务栏中的阴影形状。设置 ShowInTaskBar = false 会导致窗体消失。

编辑

我重新编写了这个类,现在它看起来像这样,一个真正的 DropShadow。

源代码在这里

你需要知道的一件事是,该类没有考虑到 border-radius(来自 CSS)。

主要属性有:

  • ShadowColor
  • ShadowV
  • ShadowH
  • ShadowSpread
  • ShadowBlur

这些属性与 CSS 的 box-shadow 相同,详情请参见此处

这些属性:

  • ShadowSpread
  • ShadowBlur
  • ShadowColor

需要手动调用 RefreshShadow()

前往演示项目


关于ShowInTaskbar问题,这很有趣。我似乎没有遇到同样的问题。如果您愿意,我可以剖析一下我的代码并将其发送给您。不过这是非常老的代码,所以并不是特别优化。 - corylulu
我的版本会更加复杂,但我忘记了具体原因:P 但是这里是我为使代码运行良好所做的所有事情。其中一些可能特定于我尝试做的事情,但我大多数时候都试图将其全部删除。http://pastie.org/8588447 - corylulu
我创建了一个新项目来测试阴影效果,发现ShowInTaskBar是正常的。现在,困难的部分是绘制阴影位图。我想使用这里描述的参数。我注意到你使用图片来绘制阴影,但这种方法不可更改。 - wener
我更新了我的答案,实现了一个真正的DrowShadow。源代码请点击此处 - wener
是的,我使用了图像,因为我不想为每次在将要一致使用相同的阴影的应用程序上使用dropshadow时添加不必要的处理能力。然而,这是多年前我开始涉足WinForms时为自己使用的。我不再使用这段代码,因为现在我主要使用WPF。不过,你对我的原始代码进行了很好的改进。 - corylulu
你好,我的朋友 @wener!!!我正在尝试将你的类转换成vb.net,但是我遇到了一些错误... 你能帮帮我吗? - Simos Sigma

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