Windows 7 style Dropshadow in borderless form

20,641

Solution 1

Okay, so after about 4 hours of brainstorming and coding, I have finally developed a solution. Basically, I made 2 forms.

Form #1: Create the dropshadow by modifying and combining 8 images (4 corners gradients + 4 linear gradients for each direction) and set them as a background using the second code I posted above (C# Code 2: Replace form GDI with Bitmap). Code pretty much explains it.

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: This just launches the dropshadow form at launch and I also created some interfaces to allow further integration and flexibility that I omitted to avoid extra confusion. Basically methods to ensure that the Dropshadow form was not taking away mouse clicks from the active form and wouldn't force the user to have to click a button twice if the Dropshadow form was on top.

Solution 2

Thanks, Corylulu.

A workable class is here.

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

f.RefreshShadow();

DEMO

The DrawShadow create a shadow like bitmap, but not perfect yet. This class not perfect, but it works.

BTW, I don't know how to hide the shadow form in task bar. Set ShowInTaskBar = false will case the form disappear.

EDIT

I rewrite the class,now it looks like this, a real DropShadow.

Source is here.

One thing you should know is this class not consider the border-radius(takes form css).

Main property is

  • ShadowColor
  • ShadowV
  • ShadowH
  • ShadowSpread
  • ShadowBlur

The property is same as css box-shadow, see here

These propertyies

  • ShadowSpread
  • ShadowBlur
  • ShadowColor

require you manual call RefreshShadow().

Go to the demo project

Share:
20,641
corylulu
Author by

corylulu

Updated on March 31, 2020

Comments

  • corylulu
    corylulu about 4 years

    Short Version:

    Goal: A deep, dark, Windows 7 dropshadow in borderless WinForm in C#


    Known existing solutions 1: Simple XP-style dropshadow using CreateParams.

    Problem: Too weak, too light, too ugly.


    Known existing solutions 2: Replace GDI of form with bitmap.

    Problem: Lose the ability to use controls, only functional as a splash screen.


    Objective by this post: Find a median solution to this problem or an all together better one.

    . . .

    Long Version:

    (Edit: I am referring to the drop-shadow going along the border of any windows form, if that wasn't clear.) I understand that there is a way to make XP style dropshadows in C# using:

    C# Code 1 - Simple XP-style dropshadow (Problem: to light, to weak, to ugly)

    // 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;
        }
    }
    

    However, I am trying to figure out how to make them appear like the do in Windows 7 (deeper and larger shadows) and can't figure out the best way of doing this.

    I have a method now created that will let me override the entire form GDI and appear like a splash screen would (credit not mine):

    C# Code 2: Replace form GDI with Bitmap (Problem: can't use form controls, hard to maintain 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;
            }
        }
    

    However, this does give me a full 32-bit background (as I require to add the dropshadow manually), but I lose the ability to create form elements that are visible.

    So basically, I am trying to figure out a median between these two methods. Something that will give me deep and dark drop shadows without losing other functionality / causing excessive repainting requirements.

  • Mike Mayer
    Mike Mayer about 12 years
    I don't know if this is the best way to do this but I think it's pretty clever.
  • corylulu
    corylulu over 10 years
    That's interesting about the ShowInTaskbar issue. I don't seem to have the same issue. I'll gut out the code that I have and send it do you if you'd like. It's very old code though, so it's not exactly optimized.
  • corylulu
    corylulu over 10 years
    My version gets a little more involved, and I forget exactly why :P but here is all the things I did to make the code work well. Some of it might be specific to what I was trying to do, but I mostly tried to take all that out. pastie.org/8588447
  • wener
    wener over 10 years
    I create a new project to test the dropshadow,find out the ShowInTaskBar is fine. Now, the difficult part is draw a shadow bitmap.I'd like to use the parameter described here.I noticed that you draw the shadow use the image, which is not changeable.
  • wener
    wener over 10 years
    I update my answer, implement a real DrowShadow.Source here
  • corylulu
    corylulu over 10 years
    Yeah, I used images because I didn't want to add needless processing power for each time I used the dropshadow on an app that was going to use the same dropshadow consistently. However, this was for my own use when I first started getting into WinForms years ago. I don't use this code anymore, since now I primarily use WPF now. Good job on advancing my original code though.
  • Simos Sigma
    Simos Sigma about 6 years
    Greetings my friend @wener!!! I am trying to convert your class into vb.net but I get some errors... Can you help please help me?