21

is there a way to add a drop shadow to controls?

are there any controls out there with this feature?

Luiscencio
  • 3,855
  • 13
  • 43
  • 74

5 Answers5

34

You have to overwrite the CreateParamsproperty like this:

private const int CS_DROPSHADOW = 0x00020000;
protected override CreateParams CreateParams
{
    get
    {
        // add the drop shadow flag for automatically drawing
        // a drop shadow around the form
        CreateParams cp = base.CreateParams;
        cp.ClassStyle |= CS_DROPSHADOW;
        return cp;
    }
}
Simon Linder
  • 3,378
  • 4
  • 32
  • 49
  • 4
    Awesome! Is there a complete list of other neat tricks like this? – FrustratedWithFormsDesigner Mar 17 '10 at 15:35
  • 9
    @FrustratedWithFormsDesigner: LOL - I love your nick – Luiscencio Mar 17 '10 at 15:37
  • @FrustratedWithFormsDesigner: What kind of tricks do you think about? – Simon Linder Mar 17 '10 at 15:38
  • @Simon Linder: this works great for Forms.. any ideas on applying this to controls? – Luiscencio Mar 17 '10 at 15:39
  • @Luiscencio: I'm not sure but as this property applies to a lot of standard controls (as e.g. Button), the CS_DROPSHADOW flag may also apply to them. Check the MSDN for classes that implement the CreateParams property. – Simon Linder Mar 17 '10 at 15:43
  • 1
    @FrustratedWithFormsDesigner: CS_DROPSHADOW is a class style. There are also others, see http://msdn.microsoft.com/en-us/library/ms633574(VS.85).aspx and WinUser.h from you SDK installation path. – Simon Linder Mar 17 '10 at 15:47
  • Talk about leaky abstractions! – I. J. Kennedy Apr 05 '11 at 01:49
  • @Simon Linder: I override the CreateParams property and it works fine with my application but it slows the Shadowed form while moving, can you please help me to find out the reason why it is happening ? – SharpUrBrain Aug 18 '11 at 12:34
  • @SharpUrBrain: Try to set `this.DoubleBuffered = true;` This should help. – Simon Linder Aug 19 '11 at 09:01
  • @Simon Linder: In Loaded() event I am using this but still not fixed :( – SharpUrBrain Aug 19 '11 at 09:38
  • 1
    @SharpUrBrain: Normally you wouldn't do that in the Loaded() event but in the constructor respectively in `InitializeComponents()`. You could also try adding the following in the constructor: `SetStyle(ControlStyles.DoubleBuffer, true); UpdateStyles();` – Simon Linder Aug 19 '11 at 10:11
  • In your solution , when its a Dialog Window , when u click outside of it and then back in , Shadow is lost ! – Billy Xd Mar 04 '20 at 16:06
  • @Billy Xd Override OnActivated and add this: `var cp = this.CreateParams; base.OnActivated(e); SetClassLongPtr(this.Handle, -26, (IntPtr)(cp.ClassStyle | CS_DROPSHADOW)); this.Invalidate(false);` `-26` is [`GCL_STYLE`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclasslongptrw) – Jimi May 07 '20 at 04:25
14

This question has been around for 6 years and needs an answer. I hope that anyone who needs to do this can extrapolate an answer for any control set from my solution. I had a panel and wanted to draw a drop shadow underneath every child control - in this instance one or more panels (but the solution should hold good for other control types with some minor code changes).

As the drop shadow for a control has to be drawn on the surface of that control's container we start by adding a function to the container's Paint() event.

Container.Paint += dropShadow;

dropShadow() looks like this:

    private void dropShadow(object sender, PaintEventArgs e)
    {
        Panel panel = (Panel)sender;
        Color[] shadow = new Color[3];
        shadow[0] = Color.FromArgb(181, 181, 181);
        shadow[1] = Color.FromArgb(195, 195, 195);
        shadow[2] = Color.FromArgb(211, 211, 211);
        Pen pen = new Pen(shadow[0]);
        using (pen)
        {
            foreach (Panel p in panel.Controls.OfType<Panel>())
            {
                Point pt = p.Location;
                pt.Y += p.Height;
                for (var sp = 0; sp < 3; sp++)
                {
                    pen.Color = shadow[sp];
                    e.Graphics.DrawLine(pen, pt.X, pt.Y, pt.X + p.Width - 1, pt.Y);
                    pt.Y++;
                }
            }
        }
    }

Clearly you can pick a different control type from the container's collection and you can vary the colour and depth of the shadow with some minor tweaks.

Mike
  • 391
  • 4
  • 11
  • 3
    I did test it and it did work and I did change it... changed your DrawLine and added a second one... e.Graphics.DrawLine(pen, pt.X + sp, pt.Y, pt.X + p.Width - 1 + sp, pt.Y); e.Graphics.DrawLine(pen, p.Right + sp, p.Top + sp, p.Right + sp, p.Bottom + sp); it looks like this: http://imgur.com/MfaR39s – Luiscencio Mar 22 '16 at 17:54
  • 1
    Hi, nice tweak - I only applied the bottom shadow as I was emulating Material Design (cards in particular) but I think that the combination should satisfy anyone looking for a solution. – Mike Mar 22 '16 at 19:48
7

The top answer does in fact generate a shadow, but I personally wasn't satisfied with it for a few reasons:

  • It only works for rectangles (granted, WinForms controls are all rectangles, but we might want to use this in other cases)
  • More importantly: It's not smooth. It doesn't look as natural as other shadows in other programs look.
  • Finally, it's slightly annoying to configure.

So, because of all these things, I ended up writing my own for my project and I thought I'd share it here:

public partial class Form1 : Form
{
    List<Control> shadowControls = new List<Control>();
    Bitmap shadowBmp = null;
    public Form1()
    {
        InitializeComponent();
        shadowControls.Add(panel1);
        this.Refresh();
    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        if (shadowBmp == null || shadowBmp.Size != this.Size)
        {
            shadowBmp?.Dispose();
            shadowBmp = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);
        }
        foreach (Control control in shadowControls)
        {
            using (GraphicsPath gp = new GraphicsPath())
            {
                gp.AddRectangle(new Rectangle(control.Location.X, control.Location.Y, control.Size.Width, control.Size.Height));
                DrawShadowSmooth(gp, 100, 60, shadowBmp);
            }
            e.Graphics.DrawImage(shadowBmp, new Point(0, 0));
        }
    }
    private static void DrawShadowSmooth(GraphicsPath gp, int intensity, int radius, Bitmap dest)
    {
        using (Graphics g = Graphics.FromImage(dest))
        {
            g.Clear(Color.Transparent);
            g.CompositingMode = CompositingMode.SourceCopy;
            double alpha = 0;
            double astep = 0;
            double astepstep = (double)intensity / radius / (radius / 2D);
            for (int thickness = radius; thickness > 0; thickness--)
            {
                using (Pen p = new Pen(Color.FromArgb((int)alpha, 0, 0, 0), thickness))
                {
                    p.LineJoin = LineJoin.Round;
                    g.DrawPath(p, gp);
                }
                alpha += astep;
                astep += astepstep;
            }
        }
    }
}

In this implementation, all Controls added to the shadowControls will be painted with a smooth shadow. You should be able to implement this for non-rectangular shapes because the main function to generate the shadows takes a GraphicsPath. Please note that it's important you draw the shadow to another bitmap before drawing it to the form because the main function requires a compositing mode of SourceCopy to work, which means if you don't draw it to another surface first anything behind the shadow will be completely replaced and the transparency aspect is useless. I'm on a roll of answering 10-year-old questions, but hopefully, this helps someone!

Nexus Designs
  • 110
  • 1
  • 8
  • Could this be applied to a single control's Paint such as a Panel instead of applying to the Paint of the parent control ? Not sure if it was by design but applying to the parent Paint is the only way I can get it to work... This is the only good solution I've found so far, thank you. – Lee Dec 23 '21 at 20:58
  • When I try this in VB.NET, everything runs and executes but I never see any shadow - any ideas? – technonaut Apr 10 '22 at 18:59
2

There is in WPF if you can stretch to using that instead, I don't believe there is an alternative in Windows Forms due to the limited capabilities of GDI+.

jbutler483
  • 24,074
  • 9
  • 92
  • 145
Andy Shellam
  • 15,403
  • 1
  • 27
  • 41
  • he could probably do it if he wrote custom controls and added a shadow effect in an overridden Paint method. – FrustratedWithFormsDesigner Mar 17 '10 at 15:28
  • 1
    @FrustratedWithFormsDesigner - yeah, that's what that article in my post is doing. It's still a horrible, horrible way to do a relatively simple thing! – Andy Shellam Mar 17 '10 at 16:05
  • 4
    The article you mentioned does not exists anymore – AaA Nov 14 '12 at 03:12
  • 1
    After the edit this answer doesn't really give much information. I think the originally linked article [has been moved here](http://www.codeproject.com/Articles/19258/Transparent-drop-shadow-in-C-GDI). – kjbartel Aug 13 '15 at 06:37
0

Here's a controversial opinion, you do it without code. Set your main panel Border Style to Fixed Single. Create 3 panels below it, each 1 pixel larger in every direction. Each of the 3 panels is of a lighter shade of gray. Not perfect but cheap and easy.

panel with pseudo-shadow

Ilya
  • 1