无边框形式的 Windows 7 样式 Dropshadow

Posted

技术标签:

【中文标题】无边框形式的 Windows 7 样式 Dropshadow【英文标题】:Windows 7 style Dropshadow in borderless form 【发布时间】:2012-02-06 06:49:37 【问题描述】:

短版:

目标:在 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 位背景(因为我需要手动添加阴影),但我无法创建可见的表单元素。

所以基本上,我试图找出这两种方法之间的中位数。在不丢失其他功能/导致过度重绘要求的情况下,可以给我提供深而暗的阴影。

【问题讨论】:

您的问题到底是什么?这真的不是为您编写解决方案的地方。 @Ramhound 问题很简单。我希望在这方面指出正确的方向。我的问题经过充分研究,并且尽可能清楚。我只是想知道是否有人知道创建 Windows 7 样式阴影的更好方法。 您可以添加所需结果的屏幕截图或模型吗? @KevinMcCormick dl.dropbox.com/u/18919663/Dropshadow.png 这是我想要的最终结果,但我发布了我最终是如何做到的。如果您有任何建议,欢迎。 【参考方案1】:

谢谢,Corylulu。

一个可行的类是here。

var f = new Dropshadow(this)

    BorderRadius = 40,
    ShadowColor = Color.Blue
;

f.RefreshShadow();

DrawShadow 创建了一个类似于位图的阴影,但还不完美。 这个类并不完美,但它确实有效。

顺便说一句,我不知道如何在任务栏中隐藏阴影表单。设置ShowInTaskBar = false 会使表单消失。

编辑

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

来源是here

你应该知道的一件事是这个类不考虑border-radius(采用css形式)。

主要属性是

阴影颜色 影子V 影子H ShadowSpread 阴影模糊

属性同cssbox-shadow,见here

这些属性

ShadowSpread 阴影模糊 阴影颜色

需要您手动拨打RefreshShadow()

转到demo project

【讨论】:

ShowInTaskbar 问题很有趣。我好像没有同样的问题。如果您愿意,我会删除我拥有的代码并将其发送给您。虽然这是非常旧的代码,所以它没有完全优化。 我的版本涉及更多一点,我忘记了确切原因:P 但这是我为使代码正常工作所做的所有事情。其中一些可能特定于我想要做的事情,但我主要是想把所有这些都拿出来。 pastie.org/8588447 我创建了一个新项目来测试阴影,发现ShowInTaskBar 很好。现在,困难的部分是绘制阴影位图。我想使用here描述的参数。我注意到您使用图像绘制阴影,这是不可更改的。 我更新我的答案,实现一个真正的 DrowShadow.Source here 是的,我使用图像是因为我不想在每次在将始终使用相同阴影的应用程序上使用阴影时增加不必要的处理能力。但是,当我几年前第一次开始接触 WinForms 时,这是供我自己使用的。我不再使用此代码,因为现在我主要使用 WPF。不过,在推进我的原始代码方面做得很好。【参考方案2】:

好的,经过大约 4 小时的头脑风暴和编码,我终于找到了解决方案。基本上,我做了2个表格。

Form #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);
    
 

Form #2:这只是在启动时启动 dropshadow 表单,我还创建了一些接口以允许进一步的集成和灵活性,我省略了这些接口以避免额外的混乱。基本上是确保 Dropshadow 表单不会从活动表单中移除鼠标点击的方法,并且如果 Dropshadow 表单位于顶部,则不会强制用户必须单击按钮两次。

【讨论】:

我不知道这是否是最好的方法,但我认为它非常聪明。

以上是关于无边框形式的 Windows 7 样式 Dropshadow的主要内容,如果未能解决你的问题,请参考以下文章

c# winform panel 边框样式设置

如何创建一个有边框但没有标题栏的表单? (如 Windows 7 上的音量控制)

C#移动无边框的窗体怎么写。

删除 Windows 窗体中的标题栏

WPF无边框窗体怎么移动?C#

WPF 按钮无边框无背景