Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows 7 style Dropshadow in borderless form

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.

like image 688
corylulu Avatar asked Jan 09 '12 18:01

corylulu


2 Answers

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.

like image 142
corylulu Avatar answered Oct 18 '22 16:10

corylulu


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

like image 22
wener Avatar answered Oct 18 '22 16:10

wener