Creating Custom Controls with the .NET Compact Framework
November 18, 2002
Summary: Larry Roof provides a step-by-step tutorial on building custom controls, what he calls mutant controls, within the .NET Compact Framework. Controls in this article include variations on the TextBox control, adding the Locked property, and creating an InputPanel property. (12 printed pages)
I know, it has been a couple of months since my last column. How's this for a reason—I have found that there are just too many cool things in the .NET Compact Framework on which to write. You've heard of writer's block? I have the complete opposite.
This whole problem came about because I've been busy working on my next book, The Definitive Guide to the .NET Compact Framework, which will be published by Apress early next year. As I have been going through topics for the book, I keep saying to myself "that would be a great article for Two for the Road." I have even started a couple of the articles, only to be distracted by the next cool feature or capability of the .NET Compact Framework that I stumbled upon.
I cannot tell you how stoked I am about this product. I even briefly considered having "I (heart symbol) NETCF" tattooed on my forearm, but realized that it really didn't fit with my two existing tattoos, "If found, please return to the beach" and "You will have to pry my Pocket PC out of my cold dead hands."
That brings me to the topic of this article—creating custom controls. As you might guess, right now I'm working on the next chapter for my book, which is on Creating Controls with the .NET Compact Framework. As has been the case with every chapter before it, I think that this is just the coolest feature found in the .NET Compact Framework. So, before anything else distracts me, here is a step-by-step tutorial on custom controls.
Building a Custom Control
One of the first things that I hear developers complain about in the .NET Compact Framework is that something crucial to their development style is missing. That something could be any number of things, from an item that is seriously significant, meaning it affects me as well, to the mundane, which is another way of saying some feature that I had never intended to use in the first place. Now, it's not as if I can't fake sympathy for whining individuals, after all I have had to deal with users for years. It's just that in this case, I think they (Microsoft) have done a good job of taking a 400 pound gorilla of a product (the .NET Framework) and shrinking it to a mere Chihuahua in size (the .NET Compact Framework).
One of the coolest features about the .NET Compact Framework is how it enables you to code around limitations. Take controls, for example. All of the controls delivered with the .NET Compact Framework are limited in comparison to their full-framework counterparts. They are missing properties, methods, and events. Now you could act like a user and whine about these shortcomings, or you could do something about it. With the .NET Compact Framework, you can create what I affectionately refer to as mutant controls, which are standard controls that have been tweaked and altered to fit just your needs. It's a "Frankenstein meets the Programmer" type of thing. In the remainder of this article, I'll walk you through, step-by-step, the process of altering a control in order to enhance its functionality.
Not surprisingly, one of the controls used most frequently by mobile developers is the simple TextBox. The TextBox control, in its .NET Compact Framework implementation, is for my needs missing a couple of key features. First, I would like to be able to lock the control, prohibiting the user from entering any data. Second, I would like to have the Soft Input Panel, or SIP, displayed automatically when a TextBox control receives focus. It was with these two features in mind that I sat down and created the TextBoxPlus control.
TextBoxPlus is a mutant control. I started with a standard TextBox, tweaked a few things here, mutated a few things there, and next thing you know I had a Locked property and an auto-SIP display feature. Now, I could fall into some serious, please stop-before-my-head-explodes diatribe of a discussion on the theory behind controls, but that would be way too boring to write, not to mention read. Instead, I'm going to walk you through the process of building this control, pointing out key items along the way.
The Foundation of TextBoxPlus
The TextBoxPlus control is built using a standard Class module. Now, in the example application included with this article, I simply added a Class module to a Pocket PC application project. At this point, there is nothing to our "new" control but the basic Class structure shown in Listing 1. The key point to note here is that the Class name has been set to TextBoxPro, which defines the name of our new control.
Listing 1. An empty class structure
Public Class TextBoxPro End Class
As I stated earlier, the TextBoxPlus control is a mutant control, which is my colorful way of saying that it is derived from an existing control. This is accomplished by adding a single line of code to our Class module, where we specify that we are inheriting our new control from a TextBox. Listing 2 shows the updated module.
Listing 2. Inheriting from the TextBox control
Public Class TextBoxPlus Inherits TextBox End Class
At this point, if you were to do absolutely nothing else, you would have an exact replica of a TextBox control. Your new and not at all improved control would act and respond just like a standard TextBox control. It would have the same properties, methods, and events.
That is the beauty behind developing mutant controls; you get all of the functionality of the original control along with any additional features you want to throw into the mix.
Adding the Locked Property
Now we're ready to begin with the customization of our control. The first feature that I want to add is a Locked property. The standard .NET Compact Framework TextBox control doesn't include this property.
There are two steps to adding the Locked property. First, you need to add the property itself. Second, you need to provide the functionality behind the property, which prohibits the user from modifying the contents of a TextBox.
Properties are added to a control by defining a Property procedure within the Class module. Listing 3 shows the code required for this step. There are four parts of this definition that I want to point out.
First, the private variable, mLocked, is defined. This variable is used to store the value of the Locked property internal to our control. The variable is defined as private because I do not want it to appear to the application using our control.
Second, the Locked property is defined through a Property procedure. This procedure specifies the property name, Locked, and the property type, Boolean. Notice that there are two parts to this procedure, the Get and Set sections. The Get section of this procedure is called when the calling application requests the value of a property. The Set section runs when the calling application sets the value of a property.
Third, under the Get section is a single line of code where we return the current value of the Locked property. The internal variable mLocked contains this value, so all that is required is to return its contents.
Fourth, under the Set section is another single line of code where we save the new value provided by the hosting application. Notice that the Set structure receives a single argument, Value, which is the new property setting. All that we need to do is store that away in our internal variable.
Listing 3. Adding the Locked property
Public Class TextBoxPlus Inherits TextBox ' Implement the Locked property. Private mLocked As Boolean Public Property Locked() As Boolean Get Locked = mLocked End Get Set(ByVal Value As Boolean) mLocked = Value End Set End Property End Class
Adding the Locked Functionality
At this point, your TextBoxPlus control has a new Locked property. You could set and retrieve the value of this property from within an application. The only problem is that the control still would not prohibit the user from modifying the contents of the control.
To restrict user input, we need to add some code to our control. What we are going to do is circumvent three event procedures that are part of the underlying TextBox control.
When a user enters a character into a TextBox, three events fire:
It is through these events that we are going to implement the functionality for the Locked property.
Each of these events receives a value representing the character being entered as an argument . The trick here is to make the control think that nothing was entered. This is accomplished by the code shown in Listing 4.
Three procedures have been added; one for each of the key events. The OnKeyDown procedure corresponds to the KeyPress event, OnKeyPress matches the KeyPress event, and OnKeyUp handles the KeyUp event.
Listing 4. Adding the Locked functionality
' Tweak the OkKeyDown event of the underlying control, to circumvent the ' event when the control is locked. Protected Overrides Sub OnKeyDown(ByVal e As System.Windows.Forms.KeyEventArgs) If (mLocked = False) Then MyBase.OnKeyDown(e) Else End If End Sub ' Tweak the OkKeyPress event of the underlying control, to circumvent the ' event and throw away the key value when the control is locked. Protected Overrides Sub OnKeyPress(ByVal e As System.Windows.Forms.KeyPressEventArgs) If (mLocked = False) Then MyBase.OnKeyPress(e) Else e.Handled = True End If End Sub ' Tweak the OkKeyUp event of the underlying control, to circumvent the ' event when the control is locked. Protected Overrides Sub OnKeyUp(ByVal e As System.Windows.Forms.KeyEventArgs) If (mLocked = False) Then MyBase.OnKeyUp(e) Else End If End Sub
In each of these events, we check the value of the Locked property. Remember, the current value of this property is stored in the variable mLocked. If the Locked property is set to the value of False, we simply pass the event on to the underlying TextBox control, where it will be handled. This is accomplished with the following line of code:
This line of code does not get executed when the value of the Locked property is True, effectively causing the event not to fire.
The last thing that we need to do is to add a single line of code in the OnKeyPress procedure, which says to the operating system, "ignore what the user just entered, I'm taking care of it." This task is accomplished by setting the Handled property to True, as shown in the following line of code:
e.Handled = True
That's it. At this point, your mutant control now includes a Locked property. When set, the user will not be able to enter any characters through the Soft Input Panel.
Adding the InputPanel Property
The second feature that I wanted my enhanced control to have was to automatically display the Soft Input Panel, or SIP, whenever the control received focus. As with the Locked property, there are two parts to adding the InputPanel property. First, you need to add the property itself. Then, you need to add the functionality that goes with the property.
Listing 5 shows the code required to define the InputPanel property. The approach used to implement this property is similar to that used with the Locked property with a few key differences. Most notably is the fact that where the Locked property type was Boolean, this property type is a Microsoft.WindowsCE.Forms.InputPanel. What I have chosen to do is to create my InputPanel property in a fashion similar to the ImageList property of the Toolbar control. Each of these properties, InputPanel and ImageList, are used to hold a specific type of control, an InputPanel and ImageList control, respectively.
There are two private variables used with this procedure. The first, mInputPanel, will hold the InputPanel control. The second, mSipDefined, flags whether the InputPanel property has been set.
Note the use of data types match throughout the procedure. The private variable mInputPanel, the argument to the Property procedure, and the argument to the Set procedure are all Microsoft.WindowsCE.Forms.InputPanel.
Listing 5. Adding the InputPanel property
' Implement the InputPanel property. Private mInputPanel As Microsoft.WindowsCE.Forms.InputPanel Private mSipDefined As Boolean Public Property InputPanel() As Microsoft.WindowsCE.Forms.InputPanel Get InputPanel = mInputPanel End Get Set(ByVal Value As Microsoft.WindowsCE.Forms.InputPanel) mInputPanel = Value mSipDefined = True End Set End Property
Adding the InputPanel Functionality
As was with the Locked property, at this point your TextBoxPlus control has a new InputPanel property. Also like the early version of the Locked property, it offers no actual functionality. The SIP is not displayed when you move to control, as we would like.
To implement this functionality, we need to add a bit more code to our control. For the Locked property we circumvented three event procedures, and here we are instead going to augment two event procedures.
When the user moves to a TextBox control, the GotFocus event fires. When the user moves away from that control, the LostFocus event fires. We are going to modify these two controls to add our desired functionality.
Listing 6 shows the two procedures used to augment the functionality, OnGotFocus and OnLostFocus. These two procedures will fire as the user moves to and from our TextBoxPlus control.
In the OnGotFocus procedure, I first check to make sure that the TextBoxPlus control is not locked. After all, there is no sense displaying the SIP if the user is prohibited from entering data. If the control is not locked, I simply have to set the Enabled property of the InputPanel control to True, which causes the SIP to be displayed.
The OnLostFocus procedure is similar. It uses the Enabled property of the InputPanel control to hide the SIP.
One key point to note is that both procedures finish by calling the corresponding event in the underlying control, assuring that the GotFocus and LostFocus events will still fire. This enables the application developer using our TextBoxPlus control to write code for the events.
Listing 6. Adding the InputPanel functionality
' Augment the OnGotFocus event to include showing the SIP, if 1) the ' control is not locked and 2) an InputPanel control has been defined. Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs) If (mLocked = False) Then If (mSipDefined) Then mInputPanel.Enabled = True End If End If MyBase.OnGotFocus(e) End Sub ' Augment the OnLostFocus event to include hiding the SIP, if 1) the ' control is not locked and 2) an InputPanel control has been defined. Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs) If (mLocked = False) Then If (mSipDefined) Then mInputPanel.Enabled = False End If End If MyBase.OnLostFocus(e) End Sub
Testing the TextBoxPlus Control
Now that we have the control completed, it is time to put it to the test. I have created a simple Pocket PC application for this purpose. An example of its interface is shown in Figure 1.
Figure 1. The interface to the test application
There are two key items to note in this design. First, there is no TextBoxPlus control presently visible on the form. While we have created a functional hybrid control, we have not created a design time version of this control. This simply means that we will not be able to add the control to our form at design time. Instead, we'll create an instance of the control from code.
Second, note the InputPanel control shown in the panel below the form. The InputPanel control allows you to control the SIP. Remember, the InputPanel property of our TextBoxPlus control is expecting an InputPanel.
Creating an Instance of the TextBoxPlus Control
The first feature to test on our control is to create it and add it to a form. Listing 7 shows the code used for this purposes.
The Dim statement defines a new instance of our TextBoxPlus control. Note the use of the keyword WithEvents, which enables the underlying object to raise events.
Within the click event of the Create button is the code used to configure the control. Note that a number of properties are set, including our Locked property. Where did all of these other properties come from? The answer is the TextBox control from which our enhanced control was built. Remember I said that all of the properties, methods, and events are available, just as if you were working with the native control.
Finally, the new instance of the control is added to our form through the Controls collection.
Listing 7. Adding a TextBoxPlus control to the form
Dim WithEvents txtDemo As New TextBoxPlus Private Sub btnCreate_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCreate.Click ' Create an instance of the TextBoxPlus control. txtDemo.Left = 10 txtDemo.Top = 10 txtDemo.Width = 100 txtDemo.Height = 20 txtDemo.Locked = False Me.Controls.Add(txtDemo) ' AddHandler txtDemo.KeyDown, AddressOf KeyDownHandler ' Display the initial status of the Locked property. lblStatus.Text = "locked - " & txtDemo.Locked.ToString ' Disable the button so that the user can't tap it again. btnCreate.Enabled = False End Sub
Testing the Locked Property
When the control is first created, the Locked property is set to False, which means you can enter data into the control. By toggling this property on and off, we should be able to enable and restrict input to the TextBoxPlus control. I have added a button to the test application for this purpose. Listing 8 shows the simple code behind this button.
As this procedure demonstrates, our new Locked property acts like any other property. This single line of code enables us to test both the Get and Set components of the property.
Listing 8. Toggling the Locked property
Private Sub btnLocked_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnLocked.Click ' Toggle the TextBoxPlus control's Locked property. txtDemo.Locked = Not txtDemo.Locked ' Update the display. lblStatus.Text = "locked - " & txtDemo.Locked.ToString txtDemo.Focus() End Sub
Testing the InputPanel Property
Before we can test this property, we must first set the property value. If you remember, the InputPanel property requires an InputPanel control. It is exactly for this purpose that we added the InputPanel control to our test form.
The code used to set the InputPanel property is shown in Listing 9.
Listing 9. Configuring the InputPanel property
Private Sub btnAutoSip_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnAutoSip.Click txtDemo.InputPanel = InputPanel1 btnAutoSip.Enabled = False End Sub
At this point, when you move to and from the TextBoxPlus control you should see the SIP first displayed and then hidden. Try toggling the Locked property on and then repeating your test. The SIP should not be displayed when the TextBoxPlus control has the focus.
Final Words on the TextBoxPlus Control
One obvious enhancement to make to this control would be to add a design-time interface. After all, creating your interface completely from code is not as exciting as one might think. The problem is that Visual Basic®.NET cannot be used to create design time versions of .NET Compact Framework controls. You will have to turn to C# and Visual Studio® .NET for this task.
If you want to use the TextBoxPlus control with your application, simply add the TextBoxPlus Class module to your project, then create it and configure it as I demonstrated in this article.
The following links provide additional details on creating custom controls for use with the .NET Compact Framework:
- Creating Custom Controls for the .NET Compact Framework
- Developing Custom Controls in C# with Smart Device Extensions
- Design-time Functionality for Custom Controls in Compact Framework and SDE on the IntelProg Web site
Jump Start Your .NET Compact Framework Development
Want to get up to speed with the .NET Compact Framework in a week? My training can get you there. I offer a five-day NETCF class that provides you with everything you need to get up and running quickly. I show you how to create robust mobile solutions using the .NET Compact Framework, SQL Server CE, and XML. A detailed outline is available at www.larryroof.com.
Back on the Road
That's it for another month. Winter is setting in here in the Midwest. In case I've never mentioned it before, I hate winter. No, correct that, I despise winter. For those that have been hoodwinked by those teary-eyed Hallmark moments, those Currier and Ives snow campaigns of deceit, let me tell you firsthand that there is nothing good or happy about winter. Please, for the love of all things good, if you are reading this, live in a warm climate (that is within a couple hundred yards of a decent surf), and you could use a visitor for say, I don't know, four or five months, don't hesitate to write me at firstname.lastname@example.org. Until then, I'll be huddled up against the exhaust fan of my PC trying to stay warm.John Kennedy is a Technical Writer/Programmer in the Visual C++ group by day and leads a secret life as a Pocket PC developer by night.
Larry Roof heads up larryroof.com, a firm that specializes in consulting on mobile projects and training on eMbedded Visual Basic, Smart Device Extensions, and SQL Server CE.