// Extends the standard button control and does some custom
// drawing with a GradientFill background
public class GradientFilledButton : Button
{
// Creates a new instance of the object
public GradientFilledButton()
{
// Messages required to override
// in this control's window procedure.
WndProcHooker.HookWndProc(this,
new WndProcHooker.WndProcCallback(this.WM_Paint_Handler),
Win32.WM_PAINT);
WndProcHooker.HookWndProc(this,
new WndProcHooker.WndProcCallback(this.WM_LButtonDown_Handler),
Win32.WM_LBUTTONDOWN);
WndProcHooker.HookWndProc(this,
new WndProcHooker.WndProcCallback(this.WM_LButtonUp_Handler),
Win32.WM_LBUTTONUP);
WndProcHooker.HookWndProc(this,
new WndProcHooker.WndProcCallback(this.WM_MouseMove_Handler),
Win32.WM_MOUSEMOVE);
WndProcHooker.HookWndProc(this,
new WndProcHooker.WndProcCallback(this.WM_KeyDown_Handler),
Win32.WM_KEYDOWN);
WndProcHooker.HookWndProc(this,
new WndProcHooker.WndProcCallback(this.WM_KeyUp_Handler),
Win32.WM_KEYUP);
}
// Controls the direction in which the button is filled
public GradientFill.FillDirection FillDirection
{
get
{
return fillDirectionValue;
}
set
{
fillDirectionValue = value;
Invalidate();
}
}
private GradientFill.FillDirection fillDirectionValue;
// The start color for the GradientFill. This is the color
// at the left or top of the control depending on the value
// of the FillDirection property.
public Color StartColor
{
get { return startColorValue; }
set
{
startColorValue = value;
Invalidate();
}
}
private Color startColorValue = Color.Red;
// The end color for the GradientFill. This is the color
// at the right or bottom of the control depending on the
// value of the FillDirection property
public Color EndColor
{
get { return endColorValue; }
set
{
endColorValue = value;
Invalidate();
}
}
private Color endColorValue = Color.Blue;
// This is the offset from the left or top edge of the button
// to start the gradient fill.
public int StartOffset
{
get { return startOffsetValue; }
set
{
startOffsetValue = value;
Invalidate();
}
}
private int startOffsetValue;
// This is the offset from the right or bottom edge
// of the button to end the gradient fill.
public int EndOffset
{
get { return endOffsetValue; }
set
{
endOffsetValue = value;
Invalidate();
}
}
private int endOffsetValue;
// Used to double-buffer our drawing to avoid flicker between
// painting the background, border, focus-rect and the
// text of the control.
private Bitmap DoubleBufferImage
{
get
{
if (bmDoubleBuffer == null)
bmDoubleBuffer = new Bitmap(
this.ClientSize.Width,
this.ClientSize.Height);
return bmDoubleBuffer;
}
set
{
if (bmDoubleBuffer != null)
bmDoubleBuffer.Dispose();
bmDoubleBuffer = value;
}
}
private Bitmap bmDoubleBuffer;
// Called when the control is resized. When that happens, we need to
// recreate the bitmap we use for double-buffering.
// e - The arguments for this event
protected override void OnResize(EventArgs e)
{
DoubleBufferImage = new Bitmap(
this.ClientSize.Width,
this.ClientSize.Height);
base.OnResize(e);
}
// Called when the control gets focus. We need to repaint the control
// to ensure the focus rectangle is drawn correctly.
// e - The arguments for this control
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
this.Invalidate();
}
// Called when the control loses focus. We need to repaint the control
// to ensure the focus rectangle is removed.
// e - The arguments for this control
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
this.Invalidate();
}
// This is set to true when we get a MouseDown event. It is used
// to determine if we should fire the Click event when we get
// a MouseUp
bool gotMouseDown = false;
bool gotKeyDown = false;
// Called when a mouse button is pressed while the cursor is
// in the control.
// e - The arguments for this event.
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
gotMouseDown = true;
base.OnMouseDown(e);
}
// Called when a mouse button is released while the cursor is
// in the control
// e - The arguments for this event
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
// If the MouseDown event was fired before this event then
// that constitutes a Click.
if ((e.Button == MouseButtons.Left) && gotMouseDown)
{
base.OnClick(EventArgs.Empty);
gotMouseDown = false;
}
}
// The callback called when the window receives a WM_MOUSEMOVE
// message. If we have the mouse captured (the user had previously
// clicked down on the button), we redraw the button.
// hwnd - The handle to the window that received the
// message.
// wParam - Indicates whether various virtual keys are
// down.
// lParam - The coordinates of the cursor
// handled - Set to true if we don't want to pass this
// message on to the original window procedure.
// Returns zero if we process this message.
int WM_MouseMove_Handler(
IntPtr hwnd, uint msg, uint wParam, int lParam,
ref bool handled)
{
if (this.Capture)
{
Point coord = Win32.LParamToPoint(lParam);
if (this.ClientRectangle.Contains(coord) !=
this.ClientRectangle.Contains(lastCursorCoordinates))
{
DrawButton(hwnd,
this.ClientRectangle.Contains(coord));
}
lastCursorCoordinates = coord;
}
return -1;
}
// The coordinates of the cursor the last time we saw a WM_MOUSEMOVE,
// WM_LBUTTONDOWN or WM_LBUTTONUP message.
Point lastCursorCoordinates;
// The callback called when the window receives a WM_LBUTTONDOWN
// message. We capture the mouse and draw the button in the "pushed"
// state.
// hwnd - The handle to the window that received the
// message.
// wParam - Indicates whether various virtual keys are
// down.
// lParam - The coordinates of the cursor.
// handled - Set to true if we don't want to pass this
// message on to the original window procedure.
// Returns zero if we process this message.
int WM_LButtonDown_Handler(
IntPtr hwnd, uint msg, uint wParam, int lParam,
ref bool handled)
{
// Start capturing the mouse input.
this.Capture = true;
// someone clicked on us so grab the focus
this.Focus();
// draw the button
DrawButton(hwnd, true);
// Fire the MouseDown event
lastCursorCoordinates = Win32.LParamToPoint(lParam);
OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1,
lastCursorCoordinates.X, lastCursorCoordinates.Y, 0));
// We have handled this windows message and we don't want the
// sub-classed window to do anything else.
handled = true;
return 0;
}
// The callback called when the window receives a WM_KEYDOWN message.
// If the key was the spacebar, We draw the button in the "pushed"
// state.
// hwnd - The handle to the window that received the
// message
// wParam - Specifies the virtual-key code of the
// non system key.
// lParam - Specifies various attributes about the key
// that is down.
// handled - Set to true if we don't want to pass this
// message on to the original window procedure
// Returns>Zero if we process this message.
int WM_KeyDown_Handler(
IntPtr hwnd, uint msg, uint wParam, int lPAram,
ref bool handled)
{
if ((wParam == Win32.VK_SPACE) ||
(wParam == Win32.VK_RETURN))
{
DrawButton(hwnd, true);
handled = true;
gotKeyDown = true;
}
return handled ? 0 : -1;
}
// The callback called when the window receives a WM_KEYUP message.
// If the key was the spacebar, We draw the button in the "un-pushed"
// state and fire the Click event.
// hwnd - The handle to the window that received the
// message
// wParam - Specifies the virtual-key code of the non-
// system key.
// lParam - Specifies various attributes about the key
// that is down.
// handled - Set to true if we don't want to pass this
// message
// on to the original window procedure
// Returns zero if we process this message.
int WM_KeyUp_Handler(
IntPtr hwnd, uint msg, uint wParam, int lParam,
ref bool handled)
{
if (gotKeyDown &&
((wParam == Win32.VK_SPACE) ||
(wParam == Win32.VK_RETURN)))
{
DrawButton(hwnd, false);
OnClick(EventArgs.Empty);
handled = true;
gotKeyDown = false;
}
return handled ? 0 : -1;
}
// The callback called when the window receives a WM_LBUTTONUP
// message. We release capture on the mouse, draw the button in the
// "un-pushed" state and fire the OnMouseUp event if the cursor was
// let go of inside our client area.
// hwnd - The handle to the window that received the
// message
// wParam - Indicates whether various virtual keys are
// down.
// lParam - The coordinates of the cursor
// handled - Set to true if we don't want to pass this
// message
// on to the original window procedure
// Returns zero if we process this message.
int WM_LButtonUp_Handler(
IntPtr hwnd, uint msg, uint wParam, int lParam,
ref bool handled)
{
this.Capture = false;
DrawButton(hwnd, false);
lastCursorCoordinates = Win32.LParamToPoint(lParam);
if (this.ClientRectangle.Contains(lastCursorCoordinates))
OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1,
lastCursorCoordinates.X, lastCursorCoordinates.Y, 0));
handled = true;
return 0;
}
// The callback called when the window receives a WM_PAINT message.
// We draw the button in the appropriate state.
// hwnd - The handle to the window that received the
// message
// wParam - Indicates whether various virtual keys are
// down.
// lParam - The coordinates of the cursor
// handled - Set to true if we don't want to pass this
// message on to the original window procedure
// Returns zero if we process this message.
int WM_Paint_Handler(
IntPtr hwnd, uint msg, uint wParam, int lParam,
ref bool handled)
{
Win32.PAINTSTRUCT ps = new Win32.PAINTSTRUCT();
Graphics gr = Graphics.FromHdc(Win32.BeginPaint(hwnd, ref ps));
DrawButton(gr, this.Capture &&
(this.ClientRectangle.Contains(lastCursorCoordinates)));
gr.Dispose();
Win32.EndPaint(hwnd, ref ps);
handled = true;
return 0;
}
// Gets a Graphics object for the provided window handle and then
// calls DrawButton(Graphics, bool).
// hwnd - The handle to the window to draw as a
// button
// pressed - If true, the button is draw in the
// depressed state
void DrawButton(IntPtr hwnd, bool pressed)
{
IntPtr hdc = Win32.GetDC(hwnd);
Graphics gr = Graphics.FromHdc(hdc);
DrawButton(gr, pressed);
gr.Dispose();
Win32.ReleaseDC(hwnd, hdc);
}
// Draws the button on the specified Graphics in the specified
// state.
// gr - The Graphics object on which to draw the
// button
// pressed - If true, the button is draw in the
// depressed state
void DrawButton(Graphics gr, bool pressed)
{
// get a Graphics object from our background image
Graphics gr2 = Graphics.FromImage(DoubleBufferImage);
// fill solid up until where the gradient fill starts
if (startOffsetValue > 0)
{
if (fillDirectionValue ==
GradientFill.FillDirection.LeftToRight)
{
gr2.FillRectangle(
new SolidBrush(pressed ? EndColor : StartColor),
0, 0, startOffsetValue, Height);
}
else
{
gr2.FillRectangle(
new SolidBrush(pressed ? EndColor : StartColor),
0, 0, Width, startOffsetValue);
}
}
// draw the gradient fill
Rectangle rc = this.ClientRectangle;
if (fillDirectionValue == GradientFill.FillDirection.LeftToRight)
{
rc.X = startOffsetValue;
rc.Width = rc.Width - startOffsetValue - endOffsetValue;
}
else
{
rc.Y = startOffsetValue;
rc.Height = rc.Height - startOffsetValue - endOffsetValue;
}
GradientFill.Fill(
gr2,
rc,
pressed ? endColorValue : startColorValue,
pressed ? startColorValue : endColorValue,
fillDirectionValue);
// fill solid from the end of the gradient fill to the edge of the
// button
if (endOffsetValue > 0)
{
if (fillDirectionValue ==
GradientFill.FillDirection.LeftToRight)
{
gr2.FillRectangle(
new SolidBrush(pressed ? StartColor : EndColor),
rc.X + rc.Width, 0, endOffsetValue, Height);
}
else
{
gr2.FillRectangle(
new SolidBrush(pressed ? StartColor : EndColor),
0, rc.Y + rc.Height, Width, endOffsetValue);
}
}
// draw the text
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
gr2.DrawString(this.Text, this.Font,
new SolidBrush(this.ForeColor),
this.ClientRectangle, sf);
// draw the border.
// we need to shrink the width and height by 1 otherwise we
// won't get any border on the right or bottom.
rc = this.ClientRectangle;
rc.Width--;
rc.Height--;
Pen pen = new Pen(SystemColors.WindowFrame);
// focused buttons have a thicker border on device
if (this.Focused)
pen = new Pen(SystemColors.WindowFrame, 3f);
gr2.DrawRectangle(pen, rc);
// draw from the background image onto the screen
gr.DrawImage(DoubleBufferImage, 0, 0);
gr2.Dispose();
}
}