Thursday, October 22, 2009

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.

11 comments:

  1. Thanks for this great manual - it's an ease to implement it!

    Good bye from Austria

    ReplyDelete
  2. Hmmmm, if you add items to the combobox then select an index, it crashes?

    e.g:

    public MainForm()
    {
    InitializeComponent();
    borderedComboBox1.Items.Add("Test");
    borderedComboBox1.Items.Add("Test2");
    }

    ReplyDelete
  3. innerControl.SelectedValueChanged += delegate { SelectedValueChanged(this, EventArgs.Empty); };

    "NullReferenceException"

    " at BorderedControl.BorderedComboBox.<.ctor>b__3(Object , EventArgs )\r\n at System.Windows.Forms.ListControl.OnSelectedValueChanged(EventArgs e)\r\n at System.Windows.Forms.ListControl.OnSelectedIndexChanged(EventArgs e)\r\n at System.Windows.Forms.ComboBox.OnSelectedIndexChanged(EventArgs e)\r\n at System.Windows.Forms.ComboBox.WnProc(WM wm, Int32 wParam, Int32 lParam)\r\n at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)\r\n at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)\r\n at System.Windows.Forms.Application.Run(Form fm)\r\n at BorderedControl.Program.Main()\r\n"

    ReplyDelete
  4. Either I'm asking a stupid question you don't think is worthy of answering or you're really busy?!

    ReplyDelete
  5. My apologies, its actually the later part...

    I'll try look into it today

    ReplyDelete
  6. You're a busy guy, it can wait longer. Great blog BTW.

    ReplyDelete
  7. Hi Stuie,

    Thanks! I'm glad you like it.

    What caused the exception was that I didn't check if the SelectedValueChanged event had a subscriber.

    Normally, you would do:

    if (SomeEventChanged != null)
    SomeEventChanged.Invoke(this, EventArgs.Empty);

    Adding these checks in the anonymous method event handlers section should solve this.

    Sorry for the inconvenience... I'll update the source code I posted immediately!

    ReplyDelete
  8. Christian, thanks for this, you're a legend!

    ReplyDelete
  9. Christian,

    I have created some further bordered controls which are excellent, but I can't seem to get the border for a checkbox in the right location. If I fix the size of my checkboxes at 96dpi to 16x16 it's fine, however moving to 192dpi the resizing causes the border to draw incorrerctly. Any words of wisdom?!

    ReplyDelete