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.



Resizing an Image in .NETCF

I’ve seen some people ask how to resize an image or how to stretch an image in .NET Compact Framework in the community. Although the operation is pretty simple, I’ll share a code snippet anyway.

public static Image Resize(Image image, Size size)
{
    Image bmp = new Bitmap(size.Width, size.Height);
    using (var g = Graphics.FromImage(bmp))
    {
        g.DrawImage(
            image,
            new Rectangle(0, 0, size.Width, size.Height),
            new Rectangle(0, 0, image.Width, image.Height),
            GraphicsUnit.Pixel);
    }
    return bmp;
}

What the code above does is to create a new image with the specified new size and draw the source image to fit the new image.



Improve .NETCF Build Performance in Visual Studio

A lot of .NETCF developers are surprisingly not aware of the Platform Verification Task in Visual Studio. Disabling this in the build process will speed up the build of .NETCF projects. To make things quick and short, here’s what you need to do:

1) Open the file C:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.CompactFramework.Common.targets for editing.

2) Change the following in Line 99

<Target
  Name="PlatformVerificationTask">
  <PlatformVerificationTask
    PlatformFamilyName="$(PlatformFamilyName)"
    PlatformID="$(PlatformID)"
    SourceAssembly="@(IntermediateAssembly)"
    ReferencePath="@(ReferencePath)"
    TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
    PlatformVersion="$(TargetFrameworkVersion)"/>
</Target>

to

<Target
  Name="PlatformVerificationTask">
  <PlatformVerificationTask
    Condition="'$(DoPlatformVerificationTask)'=='true'"
    PlatformFamilyName="$(PlatformFamilyName)"
    PlatformID="$(PlatformID)"
    SourceAssembly="@(IntermediateAssembly)"
    ReferencePath="@(ReferencePath)"
    TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
    PlatformVersion="$(TargetFrameworkVersion)"/>
</Target>

The following configuration above was an excert from an article called Platform Verification Task leading to slow builds on compact framework projects



Generic Singleton Implementation

Here’s a helpful class that I’ve been using for implementing singleton objects through the years. This way I can use any class (as long as it has a default constructor) as a singleton object. Originally, I called this class the YetAnotherSingleton< T > but that was simply too unprofessional and I ended up renaming it.

public static class Singleton<T> where T : class, new()
{
    private static readonly object staticLock = new object();
    private static T instance;
 
    public static T GetInstance()
    {
        lock (staticLock)
        {
            if (instance == null)
                instance = new T();
            return instance;
        }
    }
 
    public static void Dispose()
    {
        if (instance == null)
            return;
        var disposable = instance as IDisposable;
        if (disposable != null)
            disposable.Dispose();
        instance = null;
    }
}

And here’s an example of how to use the class above. To improve performance of web service calls its a good idea to use a singleton instance of the web service proxy class.

public static class ServiceClientFactory
{
    public static Service GetService()
    {
        var ws = Singleton<Service>.GetInstance();
        ws.Url = ConfigurationManager.AppSettings["ServiceUrl"];
        ws.Credentials = GetCredentials();
        return ws;
    }
 
    private static ICredentials GetCredentials()
    {
        var username = ConfigurationManager.AppSettings["ServiceUsername"];
        var password = ConfigurationManager.AppSettings["ServicePassword"];
        var domain = ConfigurationManager.AppSettings["ServiceDomain"];
        return new NetworkCredential(username, password, domain);
    }
}

In my next few articles I’ll be sharing code from my design pattern framework that I’ve been using through the years.