Themed Image Button in .NETCF

In this article I’d like to demonstrate how to create a themed image button control. I’ll be using the same techniques in my previous article, How to draw a textured rounded rectangle and Semi-Transparent Controls in .NETCF.

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

Semi-Transparent Controls in .NETCF

Some time ago, I wrote an article called Transparent Controls in .NETCF which describes how to accomplish transparency in the .NET Compact Framework. In this article I’d like to discuss how to control the opacity levels of transparent controls. This is made possible by alpha blending the parent controls background image with the child controls background color or image.

Here is an example of semi-transparent controls.

The screen shots above contain a semi-transparent PictureBox, Label, and Button controls. The button control is very Vista inspired, having 2 vertical fading gradient fills and then alpha blending it with the background image

Implementing Semi-Transparent Controls

The container or parent control will be the same code I used in my previous article.

Windows Mobile 5 and higher offers 2 ways of alpha blending, one through the function AlphaBlend (GDI) and through the Imaging API (COM). In this example let’s use the AlphaBlend function.

To begin with we need to define the BLENDFUNCTION structure that we’ll use as a parameter to AlphaBlend.

struct BLENDFUNCTION
{
    public byte BlendOp;
    public byte BlendFlags;
    public byte SourceConstantAlpha;
    public byte AlphaFormat;
}
 
enum BlendOperation : byte
{
    AC_SRC_OVER = 0x00
}
 
enum BlendFlags : byte
{
    Zero = 0x00
}
 
enum SourceConstantAlpha : byte
{
    Transparent = 0x00,
    Opaque = 0xFF
}
 
enum AlphaFormat : byte
{
    AC_SRC_ALPHA = 0x01
}

Next step is to define our P/Invoke declaration for AlphaBlend

[DllImport("coredll.dll")]
static extern bool AlphaBlend(
    IntPtr hdcDest,
    int xDest,
    int yDest,
    int cxDest,
    int cyDest,
    IntPtr hdcSrc,
    int xSrc,
    int ySrc,
    int cxSrc,
    int cySrc,
    BLENDFUNCTION blendfunction);

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,
    uint dwNumVertex,
    GRADIENT_RECT[] pMesh,
    uint dwNumMesh,
    uint dwMode);

And then lets wrap those neatly in some extension methods to the Graphics class

public static class GraphicsExtension
{
    public static void AlphaBlend(this Graphics graphics, Image image, byte opacity)
    {
        AlphaBlend(graphics, image, opacity, Point.Empty);
    }
 
    public static void AlphaBlend(this Graphics graphics, Image image, byte opacity, Point location)
    {
        using (var imageSurface = Graphics.FromImage(image))
        {
            var hdcDst = graphics.GetHdc();
            var hdcSrc = imageSurface.GetHdc();
 
            try
            {
                var blendFunction = new BLENDFUNCTION
                {
                    BlendOp = ((byte)BlendOperation.AC_SRC_OVER),
                    BlendFlags = ((byte)BlendFlags.Zero),
                    SourceConstantAlpha = opacity,
                    AlphaFormat = 0
                };
                AlphaBlend(
                    hdcDst,
                    location.X == 0 ? 0 : -location.X,
                    location.Y == 0 ? 0 : -location.Y,
                    image.Width,
                    image.Height,
                    hdcSrc,
                    0,
                    0,
                    image.Width,
                    image.Height,
                    blendFunction);
            }
            finally
            {
                graphics.ReleaseHdc(hdcDst);
                imageSurface.ReleaseHdc(hdcSrc);
            }
        }
    }
 
    public static void GradientFill(
        this Graphics graphics,
        Rectangle rectangle,
        Color startColor,
        Color endColor,
        GradientFillDirection direction)
    {
        var tva = new TRIVERTEX[2];
        tva[0] = new TRIVERTEX(rectangle.Right, rectangle.Bottom, endColor);
        tva[1] = new TRIVERTEX(rectangle.X, rectangle.Y, startColor);
        var gra = new[] { new GRADIENT_RECT(0, 1) };
 
        var hdc = graphics.GetHdc();
        try
        {
            GradientFill(
                hdc,
                tva,
                (uint)tva.Length,
                gra,
                (uint)gra.Length,
                (uint)direction);
        }
        finally
        {
            graphics.ReleaseHdc(hdc);
        }
    }
 
    public enum GradientFillDirection
    {
        Horizontal = 0x00000000,
        Vertical = 0x00000001
    }
}

Now that we can call the AlphaBlend API we can start designing a simple UI. Let’s create a container to for the semi-transparent control that exposes its background image allowing the child control to alpha blend part of the background image. Before we create our alpha button control, let’s take a quick review on How to implement transparency in .NETCF.

In my old article I expose the background image through an interface that a container control must implement, I called it IControlBackground

interface IControlBackground
{
    Image BackgroundImage { get; }
}

Now that it’s possible to retrieve the parent controls background image through a child control we can create our alpha button control. This control will be the usual button control where we will use a boolean flag to store the pushed state of the control, this state is changed during mouse events. The control is painted using 2 gradient fills using shades of black and the text is drawn in the center. Since the entire control is painted in the OnPaint event I override OnPaintBackground with blank code.

class AlphaButton : Control
{
    private bool pushed;
 
    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 OnPaint(PaintEventArgs e)
    {
        using (var backdrop = new Bitmap(Width, Height))
        {
            using (var gxOff = Graphics.FromImage(backdrop))
            {
                var topRect = new Rectangle(0, 0, ClientSize.Width, ClientSize.Height / 2);
                var bottomRect = new Rectangle(0, topRect.Height, ClientSize.Width, ClientSize.Height / 2);
 
                if (!pushed)
                {
                    gxOff.GradientFill(
                        bottomRect,
                        Color.FromArgb(0, 0, 11),
                        Color.FromArgb(32, 32, 32),
                        GraphicsExtension.GradientFillDirection.Vertical);
 
                    gxOff.GradientFill(
                        topRect,
                        Color.FromArgb(176, 176, 176),
                        Color.FromArgb(32, 32, 32),
                        GraphicsExtension.GradientFillDirection.Vertical);
                }
                else
                {
                    gxOff.GradientFill(
                        topRect,
                        Color.FromArgb(0, 0, 11),
                        Color.FromArgb(32, 32, 32),
                        GraphicsExtension.GradientFillDirection.Vertical);
 
                    gxOff.GradientFill(
                        bottomRect,
                        Color.FromArgb(176, 176, 176),
                        Color.FromArgb(32, 32, 32),
                        GraphicsExtension.GradientFillDirection.Vertical);
                }
 
                using (var border = new Pen(Color.White))
                    gxOff.DrawRectangle(border, 0, 0, ClientSize.Width - 1, ClientSize.Height - 1);
 
                if (!string.IsNullOrEmpty(Text))
                {
                    var size = gxOff.MeasureString(Text, Font);
                    using (var text = new SolidBrush(Color.White))
                        gxOff.DrawString(
                            Text,
                            Font,
                            text,
                            (ClientSize.Width - size.Width) / 2,
                            (ClientSize.Height - size.Height) / 2);
                }
 
                try
                {
                    var bgOwner = Parent as IControlBackground;
                    if (bgOwner != null && bgOwner.BackgroundImage != null)
                        gxOff.AlphaBlend(bgOwner.BackgroundImage, 70, Location);
                }
                catch (MissingMethodException ex)
                {
                    throw new PlatformNotSupportedException(
                        "AlphaBlend is not a supported GDI feature on this device", 
                        ex);
                }
            }
 
            e.Graphics.DrawImage(backdrop, 0, 0);
        }
    }
 
    protected override void OnPaintBackground(PaintEventArgs e)
    {
    }
}

I implement the IControlBackground interface in a simple Form class and draw my background image on the Form. I can use this directly as a container or create inherited classes and re-use the code in several occasions.

class FormBase : Form, IControlBackground
{
    Bitmap background;
 
    public FormBase()
    {
        background = new Bitmap(
            Assembly.GetExecutingAssembly().GetManifestResourceStream(
            "SemiTransparentSample.background.jpg"));
    }
 
    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.DrawImage(background, 0, 0);
    }
 
    public Image BackgroundImage
    {
        get { return background; }
    }
}

To finish it up let’s create a Form that will contain the alpha buttons.

class MainForm : FormBase
{
    public MainForm()
    {
        Controls.Add(new AlphaButton
        {
            Font = new Font("Arial", 16f, FontStyle.Bold),
            ForeColor = Color.White,
            Text = "Alpha Button 1",
            Bounds = new Rectangle(20, 20, 200, 50)
        });
        Controls.Add(new AlphaButton
        {
            Font = new Font("Arial", 16f, FontStyle.Bold),
            ForeColor = Color.White,
            Text = "Alpha Button 2",
            Bounds = new Rectangle(20, 90, 200, 50)
        });
        Controls.Add(new AlphaButton
        {
            Font = new Font("Arial", 16f, FontStyle.Bold),
            ForeColor = Color.White,
            Text = "Alpha Button 3",
            Bounds = new Rectangle(20, 160, 200, 50)
        });
        Controls.Add(new AlphaButton
        {
            Font = new Font("Arial", 16f, FontStyle.Bold),
            ForeColor = Color.White,
            Text = "Alpha Button 4",
            Bounds = new Rectangle(20, 230, 200, 50)
        });
    }
}

I hope you found this interesting and insightful. If you’re interested in the Visual Studio solution then you can download it here.

Generic Bordered Control Base for .NETCF

For quite some time I’ve been using Google Chrome as my browser. The behavior of some of the controls are similar with Safari (probably because they use the same rendering engine). Anyway, I really liked the way that a border is drawn around the control whenever it receives focus. This inspired me to create a generic base control that draws a border around any control you use it for. The code I use is pretty much the same with my previous article, Extending the TextBox Control in .NETCF.

To accomplish this I created an abstract control that takes a generic parameter of type Control and has a default constructor. You might notice that in my override Font I check whether the control is in design time or run time. If in design time, we need to create a new Font object for the setter using the values passed. If we directly use the value passed the designer will crash, and at times Visual Studio will crash.

Here’s how it can look like:

And here’s the code for the base control:

[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class BorderedControlBase<T> : Control
    where T : Control, new()
{
    protected T innerControl;
 
    protected BorderedControlBase()
    {
        innerControl = new T();
        innerControl.GotFocus += delegate { OnGotFocus(EventArgs.Empty); };
        innerControl.LostFocus += delegate { OnLostFocus(EventArgs.Empty); };
        innerControl.TextChanged += delegate { OnTextChanged(EventArgs.Empty); };
        Controls.Add(innerControl);
    }
 
    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        innerControl.Bounds = new Rectangle(1, 1, ClientSize.Width - 2, ClientSize.Height - 2);
        Height = innerControl.Height + 2;
    }
 
    protected override void OnParentChanged(EventArgs e)
    {
        base.OnParentChanged(e);
        Invalidate();
    }
 
    protected override void OnGotFocus(EventArgs e)
    {
        base.OnGotFocus(e);
        Invalidate();
        innerControl.Focus();
    }
 
    protected override void OnLostFocus(EventArgs e)
    {
        base.OnLostFocus(e);
        Invalidate();
    }
 
    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.Clear(innerControl.Focused ? SystemColors.Highlight : BackColor);
    }
 
    protected override void OnPaintBackground(PaintEventArgs e)
    {
        if (Environment.OSVersion.Platform != PlatformID.WinCE)
            base.OnPaint(e);
    }
 
    public override Font Font
    {
        get { return base.Font; }
        set
        {
            if (Environment.OSVersion.Platform != PlatformID.WinCE)
            {
                var font = new Font(value.Name, value.Size, value.Style);
                base.Font = innerControl.Font = font;
            }
            else 
                base.Font = innerControl.Font = value;
        }
    }
 
    public override string Text
    {
        get { return innerControl.Text; }
        set { innerControl.Text = value; }
    }
 
    public override bool Focused
    {
        get { return innerControl.Focused; }
    }
}

Now that we have this base control we can easily add borders to any control. Here’s an example of how to use the the bordered control base:

public class BorderedTextBox : BorderedControlBase<TextBox> 
{
    ........
}
 
public class BorderedComboBox : BorderedControlBase<ComboBox> 
{
    ........
}

Of course you will still have to wrap all the members of the wrapped control you wish to expose to access them. Hope you find this useful. If you need the Visual Studio solution then you can grab it here.

Extending the TextBox Control in .NETCF

In this article I’m gonna extend the TextBox control. To accomplish this I will wrap the built in TextBox control in a composite control. Wrapping a control in this context means forwarding events and methods to the composite control so the parent of this control will treat it like a normal TextBox.

In this example I will demonstrate how how to display a border around the TextBox when it has focused and show how to add a property for disabling and enabling the caret.

To draw the border I make sure that there is 1 pixel of space around the actual TextBox control. In the OnResize event of the composite control I set the bounds of the inner TextBox the bounds of the composite control minus 1 pixel on each side. If the inner TextBox is not in Multiline mode then the height of the control is forced to match its font size, if so then I resize the composite control to be 1 pixel taller. And draw the border I just clear the drawing surface with the highlight system color if it is Focused, and with white if not.

To enable and disable the caret, we add a boolean property called EnableCaret. This property is checked every time the control receives or loses focus, to call the native HideCaret() and ShowCaret(). As I demonstrated in my previous article called How to hide the TextBox caret in .NETCF

Here’s how it looks like:

And here’s the code:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
 
namespace ExtendedTextBox
{
    public class TextBoxEx : Control
    {
        [DllImport("coredll.dll")]
        static extern bool HideCaret(IntPtr hwnd);
 
        [DllImport("coredll.dll")]
        static extern bool ShowCaret(IntPtr hwnd);
 
        private TextBox textBox;
 
        public TextBoxEx()
        {
            textBox = new TextBox();
            textBox.GotFocus += (sender, e) => OnGotFocus(EventArgs.Empty);
            textBox.LostFocus += (sender, e) => OnLostFocus(EventArgs.Empty);
            textBox.TextChanged += (sender, e) => OnTextChanged(EventArgs.Empty);
            textBox.KeyDown += (sender, e) => OnKeyDown(e);
            textBox.KeyPress += (sender, e) => OnKeyPress(e);
            textBox.KeyUp += (sender, e) => OnKeyUp(e);
            Controls.Add(textBox);
        }
 
        public bool EnabledCaret { get; set; }
 
        #region Wrapped Properties
 
        public override Font Font
        {
            get { return base.Font; }
            set
            {
                if (Environment.OSVersion.Platform != PlatformID.WinCE)
                {
                    var font = new Font(value.Name, value.Size, value.Style);
                    base.Font = textBox.Font = font;
                }
                else
                    base.Font = textBox.Font = value;
            }
        }
 
        public override string Text
        {
            get { return textBox.Text; }
            set { textBox.Text = value; }
        }
        public bool AcceptsReturn
        {
            get { return textBox.AcceptsReturn; }
            set { textBox.AcceptsReturn = value; }
        }
 
        public bool AcceptsTab
        {
            get { return textBox.AcceptsTab; }
            set { textBox.AcceptsTab = value; }
        }
 
        public bool CanUndo
        {
            get { return textBox.CanUndo; }
        }
 
        public bool Focused
        {
            get { return textBox.Focused; }
        }
 
        public new IntPtr Handle
        {
            get { return textBox.Handle; }
        }
 
        public bool HideSelection
        {
            get { return textBox.HideSelection; }
            set { textBox.HideSelection = value; }
        }
 
        public int MaxLength
        {
            get { return textBox.MaxLength; }
            set { textBox.MaxLength = value; }
        }
 
        public bool Modified
        {
            get { return textBox.Modified; }
            set { textBox.Modified = value; }
        }
 
        public bool Multiline
        {
            get { return textBox.Multiline; }
            set { textBox.Multiline = value; }
        }
 
        public char PasswordChar
        {
            get { return textBox.PasswordChar; }
            set { textBox.PasswordChar = value; }
        }
 
        public bool ReadOnly
        {
            get { return textBox.ReadOnly; }
            set { textBox.ReadOnly = value; }
        }
 
        public override Color BackColor
        {
            get { return textBox.BackColor; }
            set { textBox.BackColor = value; }
        }
 
        public ScrollBars ScrollBars
        {
            get { return textBox.ScrollBars; }
            set { textBox.ScrollBars = value; }
        }
 
        public string SelectedText
        {
            get { return textBox.SelectedText; }
            set { textBox.SelectedText = value; }
        }
 
        public int SelectionLength
        {
            get { return textBox.SelectionLength; }
            set { textBox.SelectionLength = value; }
        }
 
        public int SelectionStart
        {
            get { return textBox.SelectionStart; }
            set { textBox.SelectionStart = value; }
        }
 
        public HorizontalAlignment TextAlign
        {
            get { return textBox.TextAlign; }
            set { textBox.TextAlign = value; }
        }
 
        public int TextLength
        {
            get { return textBox.TextLength; }
        }
 
        public bool WordWrap
        {
            get { return textBox.WordWrap; }
            set { textBox.WordWrap = value; }
        }
 
        #endregion
 
        #region Wrapped Methods
 
        public void ScrollToCaret()
        {
            textBox.ScrollToCaret();
        }
 
        public void Select(int start, int length)
        {
            textBox.Select(start, length);
        }
 
        public void SelectAll()
        {
            textBox.SelectAll();
        }
 
        public void Undo()
        {
            textBox.Undo();
        }
 
        #endregion
 
        #region Overridden Methods
 
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            textBox.Bounds = new Rectangle(
                1,
                1,
                ClientSize.Width - 2,
                ClientSize.Height - 2);
            Height = textBox.Height + 2;
        }
 
        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            Invalidate();
 
            if (!EnabledCaret)
                HideCaret(Handle);
        }
 
        protected override void OnLostFocus(EventArgs e)
        {
            base.OnLostFocus(e);
            Invalidate();
 
            if (!EnabledCaret)
                ShowCaret(Handle);
        }
 
        protected override void OnPaint(PaintEventArgs e)
        {
            e.Graphics.Clear(textBox.Focused ? SystemColors.Highlight : Color.White);
        }
 
        protected override void OnPaintBackground(PaintEventArgs e)
        {
        }
 
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            textBox.Dispose();
        }
 
        #endregion
    }
}

Hope you find this useful. If you need the Visual Studio solution then you can grab it here.

How to hide the TextBox caret in .NETCF

I was trying to help a developer today in the smart device forums who wanted to hide the caret in the TextBox control. I started playing around with the Windows Mobile Platform SDK and I stumbled upon the methods HideCaret() and ShowCaret().

The outcome is this simple inherited TextBox control I decided to call TextBoxWithoutCaret 🙂

class TextBoxWithoutCaret : TextBox
{
    [DllImport("coredll.dll")]
    static extern bool HideCaret(IntPtr hwnd);
 
    [DllImport("coredll.dll")]
    static extern bool ShowCaret(IntPtr hwnd);
 
    protected override void OnGotFocus(EventArgs e)
    {
        base.OnGotFocus(e);
        HideCaret(Handle);
    }
 
    protected override void OnLostFocus(EventArgs e)
    {
        base.OnLostFocus(e);
        ShowCaret(Handle);
    }
}

Every time the TextBox control is focused I hide the caret and enable it again when focus is lost. This doesn’t really make much practical sense and the only reason I do this is because HideCaret() is described to perform a cumulative operation meaning ShowCaret() must be called the same number of times HideCaret() was called for the caret to be visible again.

ListView Background Image in .NETCF

In this short entry I’d like to demonstrate how to display a background image in the ListView control. For this we will send the LVM_SETBKIMAGE or the LVM_GETBKIMAGE message to the ListView control with the LVBKIMAGE struct as the LPARAM. Unfortunately, the Windows CE version of LVBKIMAGE does not support LVBKIF_SOURCE_URL flag which allows using an image file on the file system for the background image of the ListView.

The layout of the background image can be either tiled or specified by an offset percentage. The background image is not affected by custom drawing, unless of course you decide to fill each sub item rectangle. For setting the background image we use the LVBKIF_SOURCE_HBITMAP flag together with the layout which is either LVBKIF_STYLE_TILE or LVBKIF_STYLE_NORMAL. If we set the layout to LVBKIF_STYLE_NORMAL, then we have the option of setting where the image will be drawn by setting the value of xOffsetPercentage and yOffsetPercentage.

In this example I’d like to make use of extension methods to add the SetBackgroundImage() and GetBackgroundImage() methods to ListView. This can of course be easily used to in a property to an inherited ListView.

public static class ListViewExtensions
{
    [DllImport("coredll")]
    static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref LVBKIMAGE lParam);
 
    const int LVM_FIRST = 0x1000;
    const int LVM_SETBKIMAGE = (LVM_FIRST + 138);
    const int LVM_GETBKIMAGE = (LVM_FIRST + 139);
    const int LVBKIF_SOURCE_NONE = 0x00000000;
    const int LVBKIF_SOURCE_HBITMAP = 0x00000001;
    const int LVBKIF_STYLE_TILE = 0x00000010;
    const int LVBKIF_STYLE_NORMAL = 0x00000000;
 
    struct LVBKIMAGE
    {
        public int ulFlags;
        public IntPtr hbm;
        public IntPtr pszImage; // not supported
        public int cchImageMax;
        public int xOffsetPercent;
        public int yOffsetPercent;
    }
 
    public static void SetBackgroundImage(this ListView listView, Bitmap bitmap)
    {
        SetBackgroundImage(listView, bitmap, false);
    }
 
    public static void SetBackgroundImage(this ListView listView, Bitmap bitmap, bool tileLayout)
    {
        SetBackgroundImage(listView, bitmap, tileLayout, 0, 0);
    }
 
    public static void SetBackgroundImage(
        this ListView listView,
        Bitmap bitmap,
        bool tileLayout,
        int xOffsetPercent,
        int yOffsetPercent)
    {
        LVBKIMAGE lvBkImage = new LVBKIMAGE();
        if (bitmap == null)
            lvBkImage.ulFlags = LVBKIF_SOURCE_NONE;
        else
        {
            lvBkImage.ulFlags = LVBKIF_SOURCE_HBITMAP | (tileLayout ? LVBKIF_STYLE_TILE : LVBKIF_STYLE_NORMAL);
            lvBkImage.hbm = bitmap.GetHbitmap();
            lvBkImage.xOffsetPercent = xOffsetPercent;
            lvBkImage.yOffsetPercent = yOffsetPercent;
        }
 
        SendMessage(listView.Handle, LVM_SETBKIMAGE, 0, ref lvBkImage);
    }
 
    public static Bitmap GetBackgroundImage(this ListView listView)
    {
        LVBKIMAGE lvBkImage = new LVBKIMAGE();
        lvBkImage.ulFlags = LVBKIF_SOURCE_HBITMAP;
 
        SendMessage(listView.Handle, LVM_GETBKIMAGE, 0, ref lvBkImage);
 
        if (lvBkImage.hbm == IntPtr.Zero)
            return null;
        else
            return Bitmap.FromHbitmap(lvBkImage.hbm);
    }
}

Here’s an example of exposing the background image as a property in an inherited ListView by using the extension methods above.

class ListViewEx : ListView
{
    public Bitmap BackgroundImage
    {
        get { return this.GetBackgroundImage(); }
        set { this.SetBackgroundImage(value, BackgroundLayout == BackgroundImageLayout.Tile); }
    }
 
    public BackgroundImageLayout BackgroundLayout { get; set; }
 
    public enum BackgroundImageLayout
    {
        Tile,
        Center
    }
}

A small catch with the ListView background image is that it is only supported in Windows CE 5.0 and later. Hope you found this information useful.

ListView Custom Drawing in .NETCF

In this article I would like to demonstrate how to do custom drawing in the ListView control that the .NET Compact Framework provides. I’ll be extending the code I published last year in the article entitled ListView Extended Styles in .NETCF

This is normally a very tedious and frustrating task to do and to accomplish this task we’ll have to take advantage of the custom drawing service Windows CE provides for certain controls. A very good reference for custom drawing is an MSDN article called Customizing a Control’s Appearance using Custom Draw. Before going any further, I may have to warn you about the extensive interop code involved in this task.

We’ll have to handle the ListView windows messages ourselves, and we accomplish this by subclassing this ListView. Subclassing a window means that we assign a new window procedure for messages that are meant for the ListView. This can be done through the SetWindowLong() method with the GWL_WNDPROC parameter. When subclassing, the developer is responsible for choosing which messages they want to handle, which to ignore, and which they let operating system handle. To have the operating system handle the message, a call to CallWindowProc() is done using a pointer to original window procedure.

Before setting the new window procedure its important to get a pointer to the original one in case the developer wishes to let the operating system handle the message. This is done through GetWindowLong()

Let’s get started…

First we need to define the interop structures for custom drawing

    struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
 
    struct NMHDR
    {
        public IntPtr hwndFrom;
        public IntPtr idFrom;
        public int code;
    }
 
    struct NMCUSTOMDRAW
    {
        public NMHDR nmcd;
        public int dwDrawStage;
        public IntPtr hdc;
        public RECT rc;
        public int dwItemSpec;
        public int uItemState;
        public IntPtr lItemlParam;
    }
 
    struct NMLVCUSTOMDRAW
    {
        public NMCUSTOMDRAW nmcd;
        public int clrText;
        public int clrTextBk;
        public int iSubItem;
        public int dwItemType;
        public int clrFace;
        public int iIconEffect;
        public int iIconPhase;
        public int iPartId;
        public int iStateId;
        public RECT rcText;
        public uint uAlign;
    }

Note: In C# (and VB and C++), the StructLayout is Sequencial by default, hence I didn’t state it

The P/Invoke declarations we need are the following:

    [DllImport("coredll.dll")]
    static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
 
    [DllImport("coredll")]
    static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, ref RECT lParam);
 
    [DllImport("coredll.dll")]
    static extern uint SendMessage(IntPtr hwnd, uint msg, uint wparam, uint lparam);
 
    [DllImport("coredll.dll", SetLastError = true)]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, WndProcDelegate newProc);
 
    [DllImport("coredll.dll", SetLastError = true)]
    static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);

And to make life a bit easier, I created some extension methods to the RECT struct we just defined.

    static class RectangleExtensions
    {
        public static Rectangle ToRectangle(this RECT rectangle)
        {
            return Rectangle.FromLTRB(rectangle.left, rectangle.top, rectangle.right, rectangle.bottom);
        }
 
        public static RectangleF ToRectangleF(this RECT rectangle)
        {
            return new RectangleF(rectangle.left, rectangle.top, rectangle.right, rectangle.bottom);
        }
    }

We’ll need the following constants defined in the windows platform SDK

    const int GWL_WNDPROC = -4;
    const int WM_NOTIFY = 0x4E;
    const int NM_CUSTOMDRAW = (-12);
    const int CDRF_NOTIFYITEMDRAW = 0x00000020;
    const int CDRF_NOTIFYSUBITEMDRAW = CDRF_NOTIFYITEMDRAW;
    const int CDRF_NOTIFYPOSTPAINT = 0x00000010;
    const int CDRF_SKIPDEFAULT = 0x00000004;
    const int CDRF_DODEFAULT = 0x00000000;
    const int CDDS_PREPAINT = 0x00000001;
    const int CDDS_POSTPAINT = 0x00000002;
    const int CDDS_ITEM = 0x00010000;
    const int CDDS_ITEMPREPAINT = (CDDS_ITEM | CDDS_PREPAINT);
    const int CDDS_SUBITEM = 0x00020000;
    const int CDIS_SELECTED = 0x0001;
    const int LVM_GETSUBITEMRECT = (0x1000 + 56);

Custom drawing in the ListView will only work in the Details view mode. To ensure this, I set the View to View.Details in the constructor method. Since I’m extending my old ListViewEx (Enables ListView Extended Styles) I’m gonna enable Double buffering, Grid lines, and the Gradient background. I’m gonna enable subclassing on the ListView only when the parent is changed, this is because I need to receive messages sent to the parent control of the ListView. We also need a delegate for the new window procedure and a pointer to the original window procedure. And last but not the least we need the actual window procedure method.

    delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
    IntPtr lpPrevWndFunc;
 
    public ListViewCustomDraw()
    {
        View = View.Details;
        DoubleBuffering = true;
        GridLines = true;
        Gradient = true;
 
        ParentChanged += delegate
        {
            lpPrevWndFunc = GetWindowLong(Parent.Handle, GWL_WNDPROC);
            SetWindowLong(Parent.Handle, GWL_WNDPROC, WndProc);
        };
    }
 
    private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        if (msg == WM_NOTIFY)
        {
            var nmhdr = (NMHDR)Marshal.PtrToStructure(lParam, typeof(NMHDR));
            if (nmhdr.hwndFrom == Handle && nmhdr.code == NM_CUSTOMDRAW)
                return CustomDraw(hWnd, msg, wParam, lParam);
 
        }
 
        return CallWindowProc(lpPrevWndFunc, hWnd, msg, wParam, lParam);
    }

In the new window procedure, we are only really interested in the WM_NOTIFY message, because this is what the NM_CUSTOMDRAW message is sent through. The LPARAM parameter of the message will contain the NMHDR which then contains the NM_CUSTOMDRAW message. The LPARAM also contains the NMLVCUSTOMDRAW which provide state and information about the ListView.

The trickiest part in performing custom drawing in the ListView is handling the drawing stage. We create a method called CustomDraw to handle the different drawing stages of the ListView

    private IntPtr CustomDraw(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        int result;
        var nmlvcd = (NMLVCUSTOMDRAW)Marshal.PtrToStructure(lParam, typeof(NMLVCUSTOMDRAW));
        switch (nmlvcd.nmcd.dwDrawStage)
        {
            case CDDS_PREPAINT:
                result = CDRF_NOTIFYITEMDRAW;
                break;
 
            case CDDS_ITEMPREPAINT:
                var itemBounds = nmlvcd.nmcd.rc.ToRectangle();
                if ((nmlvcd.nmcd.uItemState & CDIS_SELECTED) != 0)
                {
                    using (var brush = new SolidBrush(SystemColors.Highlight))
                    using (var graphics = Graphics.FromHdc(nmlvcd.nmcd.hdc))
                        graphics.FillRectangle(brush, itemBounds);
                }
 
                result = CDRF_NOTIFYSUBITEMDRAW;
                break;
 
            case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
                var index = nmlvcd.nmcd.dwItemSpec;
                var rect = new RECT();
                rect.top = nmlvcd.iSubItem;
                SendMessage(Handle, LVM_GETSUBITEMRECT, index, ref rect);
                rect.left += 2;
 
                Color textColor;
                if ((nmlvcd.nmcd.uItemState & CDIS_SELECTED) != 0)
                    textColor = SystemColors.HighlightText;
                else
                    textColor = SystemColors.ControlText;
 
                using (var brush = new SolidBrush(textColor))
                using (var graphics = Graphics.FromHdc(nmlvcd.nmcd.hdc))
                    graphics.DrawString(Items[index].SubItems[nmlvcd.iSubItem].Text,
                                        Font,
                                        brush,
                                        rect.ToRectangleF());
 
                result = CDRF_SKIPDEFAULT | CDRF_NOTIFYSUBITEMDRAW;
                break;
 
            default:
                result = CDRF_DODEFAULT;
                break;
        }
 
        return (IntPtr)result;
    }

In the first stage we handle is the CDDS_PREPAINT. Here we return CDRF_NOTIFYITEMDRAW to tell that we want to handle drawing of the row ourselves. After this we receive the CDDS_ITEMPREPAINT where we can draw the entire row.

We check if the row is selected through the uItemState field of NMCUSTOMDRAW, if this field has the CDIS_SELECTED flag then it means the item is selected, hence we draw a fill rectangle. After handling the CDDS_ITEMPREPAINT, we return CDRF_NOTIFYSUBITEMDRAW to tell that we want to draw the sub items ourselves.

For drawing the sub items we need to handle CDDS_SUBITEM | CDDS_ITEMPREPAINT. We can get the position index of the item through the dwItemSpec field of NMCUSTOMDRAW. To get the bounds of the current sub item we send the LVM_GETSUBITEMRECT message to the ListView and pass a pointer to RECT as the LPARAM. Before sending this message, set the “top” field of the RECT to the index of the sub item (retrieved from iSubItem field of NMLVCUSTOMDRAW. After drawing the sub item we return CDRF_SKIPDEFAULT | CDRF_NOTIFYSUBITEMDRAW to tell that we only care about handling the next sub item.

Well I hope you guys find this interesting and helpful. To keep things simple, I only demonstrated displaying plan text and a plain rectangle for the selection.

If you’re interested in the full source code then you can grab it here.

ListView Extended Styles in .NETCF

In this article I would like to demonstrate how to extend the ListView control in the .NET Compact Framework. We will focus on enabling some of the ListView Extended Styles. If we take a look at the Windows Mobile 5.0 Pocket PC SDK we will see that there are certain features of ListView that aren’t provided by the .NET Compact Framework.

An example of the ListView extended styles is displaying gridlines around items and subitems, double buffering, and drawing a gradient background. These extended styles can be enabled in native code by using the ListView_SetExtendedListViewStyle macro or by sending LVM_SETEXTENDEDLISTVIEWSTYLE messages to the ListView.

Send Message

We will be using a lot of P/Invoking so let’s start with creating an internal static class called NativeMethods. We need a P/Invoke declaration for SendMessage(HWND, UINT, UINT, UINT).

internal static class NativeMethods
{
  [DllImport(“coredll.dll”)]
  public static extern uint SendMessage(IntPtr hwnd, uint msg, uint wparam, uint lparam);
}

Enabling and Disabling Extended Styles

Now that we have our SendMessage P/Invoke declaration in place, we can begin extending the ListView control. Let’s start off with creating a class called ListViewEx that inherits from ListView. We need to look into the native header files of the Pocket PC SDK to get the ListView Messages. For now we will only need LVM_[GET/SET]EXTENDEDLISTVIEWSTYLE message which will be the main focus of all the examples. I will declare my class as a partial class and create all the pieces one by one for each example. Let’s create a private method called SetStyle(), this method will enable/disable extended styles for the ListView

public partial class ListViewEx : ListView
{
  private const uint LVM_FIRST = 0x1000;
  private const uint LVM_SETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 54;
  private const uint LVM_GETEXTENDEDLISTVIEWSTYLE = LVM_FIRST + 55;

  private void SetStyle(uint style, bool enable)
  {
    uint currentStyle = NativeMethods.SendMessage(
      Handle,
      LVM_GETEXTENDEDLISTVIEWSTYLE,
      0,
      0);

    if (enable)
      NativeMethods.SendMessage(
        Handle,
        LVM_SETEXTENDEDLISTVIEWSTYLE,
        0,
        currentStyle | style);
    else
      NativeMethods.SendMessage(
        Handle,
        LVM_SETEXTENDEDLISTVIEWSTYLE,
        0,
        currentStyle & ~style);
  }
}

Grid Lines

For my first example, let’s enable GridLines in the ListView control. We can do this by using LVS_EX_GRIDLINES. This displays gridlines around items and sub-items and is available only in conjunction with the Details mode.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_GRIDLINES = 0x00000001;

  private bool gridLines = false;
  public bool GridLines
  {
    get { return gridLines; }
    set
    {
      gridLines = value;
      SetStyle(LVS_EX_GRIDLINES, gridLines);
    }
  }
}

What the code above did was add the LVS_EX_GRIDLINES style to the existing extended styles by using the SetStyle() helper method we first created.

An interesting discovery to this is that the Design Time attributes of the Compact Framework ListView control includes the GridLines property. Now that we created the property in the code, when we open the Visual Studio Properties Window for our ListViewEx we will notice that GridLines property we created falls immediately under the “Appearance” category and even includes a description 🙂

Double Buffering

Do you notice that when you populate a ListView control with a lot of items, the drawing flickers a lot when you scroll up and down the list? Although it is not in the Pocket PC documentation for Windows Mobile 5.0, the ListView actually has an extended style called LVS_EX_DOUBLEBUFFER. Enabling the LVS_EX_DOUBLEBUFFER solves the flickering issue and gives the user a more smooth scrolling experience.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_DOUBLEBUFFER = 0x00010000;

  private bool doubleBuffering = false;
  public bool DoubleBuffering
  {
    get { return doubleBuffering; }
    set
    {
      doubleBuffering = value;
      SetStyle(LVS_EX_DOUBLEBUFFER, doubleBuffering);
    }
  }
}

Gradient Background

Another cool extended style is the LVS_EX_GRADIENT. This extended style draws a gradient background similar to the one found in Pocket Outlook. It uses the system colors and fades from right to left. But what is really cool about this is that this is done by the OS. All we had to do was enable the style.

public partial class ListViewEx : ListView
{
  private const uint LVS_EX_GRADIENT = 0x20000000;

  private bool gradient = false;
  public bool Gradient
  {
    get { return gradient; }
    set
    {
      gradient = value;
      SetStyle(LVS_EX_GRADIENT, gradient);
    }
  }
}

If you want to look more into extended styles then I suggest you check out the Pocket PC Platform SDK documentation. There a few other extended styles that I did not discuss that might be useful for you. You can get the definitions in a file called commctrl.h in your Windows Mobile SDK “INCLUDE” directory.

Transparent Controls in .NETCF

I work a lot with a graphic artist for developing solutions. The better the graphic artist you work with is, the harder it is to implement their designs to your application. One thing that my solutions have in common is that they all require transparent controls. My graphic artist loves having a image buttons on top of fancy background.

Here’s some screen shots of what I’ve made with my graphic artist:

In these screen shots I heavily use a transparent label control over a Form that has a background image. I normally set my controls to be designer visible so I can drag and drop while designing. Visual Studio 2005 and 2008 will automatically load all Custom Controls and UserControls

Implementing Transparent Controls

For creating transparent controls, I need the following:

1) IControlBackground interface – contains BackgroundImage { get; }
2) TransparentControlBase – draws the BackgroundImage of an IControlBackground form
3) Transparent Control – Inherits from TransparentControlBase
4) FormBase Form – implements IControlBackground and draws the background image to the form

Let’s start off with the IControlBackground interface. Like I mentioned above, it only contains a property called BackgroundImage.

public interface IControlBackground
{
  Image BackgroundImage { get; }
}

Next we will need to create the TransparentControlBase. Let’s create a class that inherits from Control. We then need to override the OnPaintBackground() event to draw the IControlBackground.BackgroundImage of the Parent control. To do this, we create an instance of IControlBackground from the Parent. Once we have the BackgroundImage, we draw part of the BackgroundImage where the transparent control is lying on.

We also override the OnTextChanged() and OnParentChanged() events to force a re-draw whenever the text or parent of the control is changed.

public class TransparentControlBase : Control
{
  protected bool HasBackground = false;

  protected override void OnPaintBackground(PaintEventArgs e)
  {
    IControlBackground form = Parent as IControlBackground;
    if (form == null) {
      base.OnPaintBackground(e);
      return;
    } else {
      HasBackground = true;
    }

    e.Graphics.DrawImage(
      form.BackgroundImage,
      0,
      0,
      Bounds,
      GraphicsUnit.Pixel);
  }

  protected override void OnTextChanged(EventArgs e)
  {
    base.OnTextChanged(e);
    Invalidate();
  }

  protected override void OnParentChanged(EventArgs e)
  {
    base.OnParentChanged(e);
    Invalidate();
  }
}

Now we need to create a control that inherits from TransparentControlBase. I’ll create a simple TransparentLabel control for this example. The control will have the same behavior as the standard Label control, except that it can be transparent when used over a form or control that implements IControlBackground.

public class TransparentLabel : TransparentControlBase
{
  ContentAlignment alignment = ContentAlignment.TopLeft;
  StringFormat format = null;
  Bitmap off_screen = null;

  public TransparentLabel()
  {
    format = new StringFormat();
  }

  public ContentAlignment TextAlign
  {
    get { return alignment; }
    set
    {
      alignment = value;
      switch (alignment) {
        case ContentAlignment.TopCenter:
          format.Alignment = StringAlignment.Center;
          format.LineAlignment = StringAlignment.Center;
          break;
        case ContentAlignment.TopLeft:
          format.Alignment = StringAlignment.Near;
          format.LineAlignment = StringAlignment.Near;
          break;
        case ContentAlignment.TopRight:
          format.Alignment = StringAlignment.Far;
          format.LineAlignment = StringAlignment.Far;
          break;
      }
    }
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    if (!base.HasBackground) {
      if (off_screen == null) {
        off_screen = new Bitmap(ClientSize.Width, ClientSize.Height);
      }
      using (Graphics g = Graphics.FromImage(off_screen)) {
        using (SolidBrush brush = new SolidBrush(Parent.BackColor)) {
          g.Clear(BackColor);
          g.FillRectangle(brush, ClientRectangle);
        }
      }
    } else {
      using (SolidBrush brush = new SolidBrush(ForeColor)) {
        e.Graphics.DrawString(
          Text,
          Font,
          brush,
          new Rectangle(0, 0, Width, Height),
          format);
      }
    }
  }
}

Now that we have our transparent controls, we need to create a Form that will contain these controls. First we need to create a base class that will implement IControlBackground and inherit from Form.

In this example, I added a background image to the solution and as an embedded resource. My default namespace is called TransparentSample and my background image is located at the root folder with the filename background.jpg

public class FormBase : Form, IControlBackground
{
  Bitmap background;

  public FormBase()
  {
    background = new Bitmap(
      Assembly.GetExecutingAssembly().GetManifestResourceStream(
      “TransparentSample.background.jpg”));
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    e.Graphics.DrawImage(background, 0, 0);
  }

  public Image BackgroundImage
  {
    get { return background; }
  }
}

For the last step, we need to create a Form that will contain these transparent controls. To start, let’s add a new Form to our project and let it inherit from FormBase instead of Form.

Now we can add our transparent controls to the main form.

public class MainForm : FormBase
{
  TransparentLabel label;

  public MainForm()
  {
    label = new TransparentLabel();
    label.Font = new Font(“Arial”, 16f, FontStyle.Bold);
    label.ForeColor = Color.White;
    label.Text = “Transparent Label”;
    label.Bounds = new Rectangle(20, 60, 200, 50);
    Controls.Add(label);
  }
}

That wasn’t very complicated, was it? Having a nice and intuitive UI offers a very good user experience. Being creative, imaginative, and learning to work with a graphic artist can really pay off.

ButtonEx – Owner Drawn Button Control

Wow 2 posts in 1 day. I must have a lot of spare time today! Actually I’m down with a cold and I just feel sorry for myself when I stay in bed.

In this article I’d like to share another piece of code that I use quite often. Its a simple owner drawn button control that can have a nice 3D shadow. Drawing the 3D shadow effect was by complete accident though. I was trying to just draw a simple control that acts like a button. When I was drawing the borders the first time I drawn the rectangle a pixel bigger hence the control had a 1 pixel line of just black on the right and bottom side. Anyway, my graphic artist loved it and decided that we keep it.

The control has the following properties added:
  1) PushedColor (Color) – The color the control will be filled with once the MouseDown is fired
  2) DrawShadow (Boolean) – A flag whether to draw the 3D border

The default settings of the control is best on a form with a blue-ish background.

And here’s the code:

public class ButtonEx : Control
{
  private Bitmap off_screen;
  private SolidBrush back_brush;
  private SolidBrush selected_brush;
  private SolidBrush text_brush;
  private Pen border_pen;
  private bool pushed;
  private bool shadow;

  public ButtonEx()
  {
    BackColor = Color.FromArgb(2, 32, 154);
    ForeColor = Color.White;

    back_brush = new SolidBrush(Color.FromArgb(48, 88, 198));
    selected_brush = new SolidBrush(Color.FromArgb(15, 51, 190));
    border_pen = new Pen(Color.White);
    text_brush = new SolidBrush(Color.White);
  }

  public override Color BackColor
  {
    get { return base.BackColor; }
    set
    {
      base.BackColor = value;
      back_brush = new SolidBrush(value);
    }
  }

  public override Color ForeColor
  {
    get { return base.ForeColor; }
    set
    {
      base.ForeColor = value;
      border_pen = new Pen(value);
      text_brush = new SolidBrush(value);
    }
  }

  public Color PushedColor
  {
    get { return selected_brush.Color; }
    set { selected_brush = new SolidBrush(value); }
    }

  public bool DrawShadow
  {
    get { return shadow; }
    set { shadow = value; }
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    if (off_screen == null) {
      off_screen = new Bitmap(ClientSize.Width, ClientSize.Height);
    }

    using (Graphics gxOff = Graphics.FromImage(off_screen)) {
      gxOff.DrawRectangle(
        border_pen,
        0,
        0,
        ClientSize.Width – (shadow ? 0 : 1),
        ClientSize.Height – (shadow ? 0 : 1));

      Rectangle rect = new Rectangle(1, 1,
        ClientRectangle.Width – 2, ClientRectangle.Height – 2);

      gxOff.FillRectangle(
        pushed ? back_brush : selected_brush, rect);

      if (!string.IsNullOrEmpty(Text)) {
        SizeF size = gxOff.MeasureString(Text, Font);
        gxOff.DrawString(
          Text,
          Font,
          text_brush,
          (ClientSize.Width – size.Width) / 2,
          (ClientSize.Height – size.Height) / 2);
      }
    }

    e.Graphics.DrawImage(off_screen, 0, 0);
  }

  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 OnTextChanged(EventArgs e)
  {
    base.OnTextChanged(e);

    Invalidate();
  }
}