First, we need to define our theme. We get the start and end gradient colors from alpha blending the SystemColors.Highlight color and Color.White
class Theme
{
public static Color AlphaBlend(Color value1, Color value2, int alpha)
{
int ialpha = 256 - alpha;
return Color.FromArgb((value1.R * alpha) + (value2.R * ialpha) >> 8,
(value1.G * alpha) + (value2.G * ialpha) >> 8,
(value1.B * alpha) + (value2.B * ialpha) >> 8);
}
public static Color GradientLight
{
get
{
var color = AlphaBlend(SystemColors.Highlight, Color.White, 100);
return AlphaBlend(Color.White, color, 50); ;
}
}
public static Color GradientDark
{
get
{
var color = AlphaBlend(SystemColors.Highlight, Color.Black, 256);
return AlphaBlend(Color.White, color, 50); ;
}
}
}
We'll be using the GradientFill method as well in this example. For this we need to define 2 structures, TRIVERTEX and GRADIENT_RECT
struct TRIVERTEX
{
private int x;
private int y;
private ushort Red;
private ushort Green;
private ushort Blue;
private ushort Alpha;
public TRIVERTEX(int x, int y, Color color)
: this(x, y, color.R, color.G, color.B, color.A)
{
}
public TRIVERTEX(
int x, int y,
ushort red, ushort green, ushort blue,
ushort alpha)
{
this.x = x;
this.y = y;
Red = (ushort)(red << 8);
Green = (ushort)(green << 8);
Blue = (ushort)(blue << 8);
Alpha = (ushort)(alpha << 8);
}
}
struct GRADIENT_RECT
{
private uint UpperLeft;
private uint LowerRight;
public GRADIENT_RECT(uint ul, uint lr)
{
UpperLeft = ul;
LowerRight = lr;
}
}
Using the 2 structures above we can now define our P/Invoke for GradientFill
[DllImport("coredll.dll")]
static extern bool GradientFill(
IntPtr hdc,
TRIVERTEX[] pVertex,
int dwNumVertex,
GRADIENT_RECT[] pMesh,
int dwNumMesh,
int dwMode);
Let's wrap the P/Invoke call to GradientFill in a nice method
const int GRADIENT_FILL_RECT_V = 0x00000001;
public static void GradientFill(
this Graphics graphics,
Rectangle rect,
Color startColor,
Color endColor)
{
var tva = new TRIVERTEX[2];
tva[0] = new TRIVERTEX(rect.X, rect.Y, startColor);
tva[1] = new TRIVERTEX(rect.Right, rect.Bottom, endColor);
var gra = new GRADIENT_RECT[] { new GRADIENT_RECT(0, 1) };
var hdc = graphics.GetHdc();
GradientFill(hdc, tva, tva.Length, gra, gra.Length, GRADIENT_FILL_RECT_V);
graphics.ReleaseHdc(hdc);
}
In order to use the function we need to create a few GDI objects: a Pen to draw the border, and a Brush to fill the rectangle. We will mostly use P/Invoke for creating and releasing GDI objects
const int PS_SOLID = 0;
const int PS_DASH = 1;
[DllImport("coredll.dll")]
static extern IntPtr CreatePen(int fnPenStyle, int nWidth, uint crColor);
[DllImport("coredll.dll")]
static extern int SetBrushOrgEx(IntPtr hdc, int nXOrg, int nYOrg, ref Point lppt);
[DllImport("coredll.dll")]
static extern IntPtr CreateSolidBrush(uint color);
[DllImport("coredll.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobject);
[DllImport("coredll.dll")]
static extern bool DeleteObject(IntPtr hgdiobject);
[DllImport("coredll.dll")]
static extern IntPtr CreatePatternBrush(IntPtr HBITMAP);
[DllImport("coredll.dll")]
static extern bool RoundRect(
IntPtr hdc,
int nLeftRect,
int nTopRect,
int nRightRect,
int nBottomRect,
int nWidth,
int nHeight);
We'll draw a textured rounded rectangle with a gradient fill that uses the current theme's Highlight color as the base color.
public static void DrawThemedGradientRectangle(
this Graphics graphics,
Pen border,
Rectangle area,
Size ellipseSize)
{
using (var texture = new Bitmap(area.Right, area.Bottom))
{
using (var g = Graphics.FromImage(texture))
GradientFill(g, area, Theme.GradientLight, Theme.GradientDark);
FillRoundedTexturedRectangle(graphics, border, texture, area, ellipseSize);
}
}
static IntPtr CreateGdiPen(Pen pen)
{
var style = pen.DashStyle == DashStyle.Solid ? PS_SOLID : PS_DASH;
return CreatePen(style, (int)pen.Width, GetColorRef(pen.Color));
}
public static void FillRoundedTexturedRectangle(
this Graphics graphics,
Pen border,
Bitmap texture,
Rectangle rect,
Size ellipseSize)
{
Point old = new Point();
var hdc = graphics.GetHdc();
var hpen = CreateGdiPen(border);
var hbitmap = texture.GetHbitmap();
var hbrush = CreatePatternBrush(hbitmap);
SetBrushOrgEx(hdc, rect.Left, rect.Top, ref old);
SelectObject(hdc, hpen);
SelectObject(hdc, hbrush);
RoundRect(hdc, rect.Left, rect.Top, rect.Right, rect.Bottom, ellipseSize.Width, ellipseSize.Height);
SetBrushOrgEx(hdc, old.Y, old.X, ref old);
DeleteObject(hpen);
DeleteObject(hbrush);
graphics.ReleaseHdc(hdc);
}
Let's wrap all the code above as extension methods to the Graphics class and use them in our owner drawn button control. A button control is one of the easiest owner drawn controls to create. Let's keep it as simple as possible and only have 2 states for our button: pressed and not pressed.
class ThemedImageButton : Control
{
bool pushed = false;
private Bitmap image;
private Bitmap offScreen;
public Bitmap Image
{
get { return image; }
set
{
image = value;
Invalidate();
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
if (offScreen != null)
{
offScreen.Dispose();
offScreen = null;
}
offScreen = new Bitmap(ClientSize.Width, ClientSize.Height);
}
protected override void OnPaint(PaintEventArgs e)
{
if (offScreen == null)
offScreen = new Bitmap(ClientSize.Width, ClientSize.Height);
using (var attributes = new ImageAttributes())
using (var g = Graphics.FromImage(offScreen))
{
if (pushed)
{
using (var pen = new Pen(SystemColors.Highlight))
g.DrawThemedGradientRectangle(pen, ClientRectangle, new Size(4, 4));
}
else
g.Clear(Parent.BackColor);
var textSize = g.MeasureString(Text, Font);
var textArea = new RectangleF(
(ClientSize.Width - textSize.Width) / 2,
(ClientSize.Height - textSize.Height),
textSize.Width,
textSize.Height);
if (Image != null)
{
var imageArea = new Rectangle(
(ClientSize.Width - Image.Width) / 2,
(ClientSize.Height - Image.Height) / 2,
Image.Width,
Image.Height);
var key = Image.GetPixel(0, 0);
attributes.SetColorKey(key, key);
g.DrawImage(
Image,
imageArea,
0, 0, Image.Width, Image.Height,
GraphicsUnit.Pixel,
attributes);
}
using (var brush = new SolidBrush(ForeColor))
g.DrawString(Text, Font, brush, textArea);
if (pushed)
{
var key = offScreen.GetPixel(0, 0);
attributes.SetColorKey(key, key);
}
else
attributes.ClearColorKey();
e.Graphics.DrawImage(
offScreen,
ClientRectangle,
0, 0, offScreen.Width, offScreen.Height,
GraphicsUnit.Pixel,
attributes);
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
pushed = true;
Invalidate();
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
pushed = false;
Invalidate();
}
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
Invalidate();
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Invalidate();
}
}
I hope you found this useful. If you're interested in the full source code then you can grab it here