Introduction
I had to create a transparent fuzzy drop shadow similar to those found in various Windows applications for a project I'm working on. Want to have them too? Then read on! (or just download the sources).
Background
Unfortunately, there's no support at all for these drop shadows in Visual Studio 2005/WinForms (there is in WPF!). Fortunately, I only needed panels with shadows. And shadows for rectangular shapes are easy. I just took the method I use for web pages and applied it to WinForms.
The trick is as follows. Using strategically placed and tiled pictures of a shadow, we can simulate one. When transparent .PNG's are used, it looks just like the real thing.
The image parts we need are outlined in red below:
Normally, I'd start with creating these images. Luckily for you, I actually did that and I have provided them with the sources. Time to start coding!
The project
File > New > Project, and select Class Library. Call it ShadowPanel and click OK. Now rename class1.cs to ShadowPanel.cs. That's the file we'll work with.
It's always handy to create a test project. So right click the solution in Solution Explorer and select Add > New Project. Pick Windows Application this time and call it ShadowPanelTest.
Properties
First, we need some properties. It would be nice to provide at least a border and a background color. Time to open ShadowPanel.cs and add the following:
using System;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace ShadowPanel
{
public class ShadowPanel : Panel
{
private Color _panelColor;
public Color PanelColor
{
get { return _panelColor; }
set { _panelColor = value; }
}
private Color _borderColor;
public Color BorderColor
{
get { return _borderColor; }
set { _borderColor = value; }
}
We can't use the original panel BackgroundColor
property, because that's for the entire panel, including the shadow area! So I created a PanelColor
property that defines the color for the area inside the shadow.
And about those references to System.Drawing
and System.Drawing.Drawing2D
. You might have to add them manually by right-clicking on References in Solution Explorer and selecting them in the list.
Images
Now for the images we can use to actually draw the shadow. You can download them with the sources if you haven't done that already. Create a folder in the project called 'Images' and add the tshadowXXXX.png files. Select them in Solution Explorer and change the Build Action to Embedded resource. That way the files will be compiled into the .dll and you won't need to distribute them with your application.
I added the images as static in the code (kudos to Remco Schrijvers for proposing this). I also added two private members to describe the size of the images (they are 5X5 pixel images) and the offset (margin) of the shadow from the left and top. Please add the following code to the ShadowPanel
class:
private int shadowSize = 5;
private int shadowMargin = 2;
static Image shadowDownRight = new Bitmap(typeof(ShadowPanel), "Images.tshadowdownright.png");
static Image shadowDownLeft = new Bitmap(typeof(ShadowPanel), "Images.tshadowdownleft.png");
static Image shadowDown = new Bitmap(typeof(ShadowPanel), "Images.tshadowdown.png");
static Image shadowRight = new Bitmap(typeof(ShadowPanel), "Images.tshadowright.png");
static Image shadowTopRight = new Bitmap(typeof(ShadowPanel), "Images.tshadowtopright.png");
OnPaint()
Everything is prepared for us to do the cool stuff. Creating GDI+ drawing code! Let's override the OnPaint
method of the Panel
base class:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
First we'll do the tiled images on the bottom and the right:
I think the code says it all. Just add after Graphics.g = e.Graphics;
TextureBrush shadowRightBrush = new TextureBrush(shadowRight, WrapMode.Tile);
TextureBrush shadowDownBrush = new TextureBrush(shadowDown, WrapMode.Tile);
shadowDownBrush.TranslateTransform(0, Height - shadowSize);
shadowRightBrush.TranslateTransform(Width - shadowSize, 0);
Rectangle shadowDownRectangle = new Rectangle(
shadowSize + shadowMargin,
Height - shadowSize,
Width - (shadowSize * 2 + shadowMargin),
shadowSize
);
Rectangle shadowRightRectangle = new Rectangle(
Width - shadowSize,
shadowSize + shadowMargin,
shadowSize,
Height - (shadowSize * 2 + shadowMargin)
);
g.FillRectangle(shadowDownBrush, shadowDownRectangle);
g.FillRectangle(shadowRightBrush, shadowRightRectangle);
You can add the ShadowPanel
to the test project by opening the Form1.cs designer. If you build the project once, the ShadowPanel
should appear in the toolbox. Just drag it to the form and run it. You'll (hopefully) get the following result:
By the way, I set my form's background color to white, so it might look different for you.
Bring in the corners
To draw the small bitmaps in the corners, just add the following:
g.DrawImage(shadowTopRight, new Rectangle(Width - shadowSize,
shadowMargin, shadowSize, shadowSize));
g.DrawImage(shadowDownRight, new Rectangle(Width - shadowSize,
Height - shadowSize, shadowSize, shadowSize));
g.DrawImage(shadowDownLeft, new Rectangle(shadowMargin,
Height - shadowSize, shadowSize, shadowSize));
Almost too easy.
Filling and bordering
We already have the PanelColor
and the BorderColor
properties. Now it's time to put them to use.
Rectangle fullRectangle = new Rectangle(
1,
1,
Width - (shadowSize + 2),
Height - (shadowSize + 2)
);
if (PanelColor != null)
{
SolidBrush bgBrush = new SolidBrush(_panelColor);
g.FillRectangle(bgBrush, fullRectangle);
}
if (_borderColor != null)
{
Pen borderPen = new Pen(BorderColor);
g.DrawRectangle(borderPen, fullRectangle);
}
Time to check our results so far:
Well, that's about it. We're almost done.
Finishing up
Just a formality, end the OnPaint
method with:
shadowDownBrush.Dispose();
shadowRightBrush.Dispose();
shadowDownBrush = null;
shadowRightBrush = null;
}
}
}
Done!
Points of interest
Of course, I won't stop you from adding more functionality. Please do! And let me know if you appreciated this tutorial!
History
- June 17, 2007 - First version.