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.