Introduction
Sometimes, you might need to provide a small window for the user to input a small amount of data with a few text boxes. For example, you want to provide an edit box for defining keyword of search, and the edit box will be sitting at the Windows task bar. It might be more elegant that a label indicating the purpose of the text box will not occupy more space of the task bar. An easy approach is just to use a hint property of the text box. However, you will need to have the mouse pointer over the text box to show hint. It will be handy to show the hint all the time. A good existing instance of this example is Google Desktop's Deskbar.
When you don't have keywords defined, "Google" inside the text box tells you to Google(search). After you focus on the text box, or the text box has some content, the "Google" hint inside the text box will disappear.
From a certain point to view, within a small window with a few text boxes, you will always know the purposes of the boxes after a few instances of running. In some scenario, it is good to keep the window small. To save space for data input, the UI design like Google Deskbar will be handy and economic.
While there can be various approaches of implementing this feature, I am not going to develop a new derived class from class TextBox
, rather, I would just plug these features to a text box. The in-box label is shown when you are not using the box.
If the box has content or is being focused, the in-box label will disappear.
Using the Code
The code was constructed with Visual Studio 2005. This project in the above download link contains two example classes: Flashing
and InTextBoxLabel
. The second one will be discussed in the next article.
public class InTextboxLabel
{
protected TextBoxBase box;
protected string hint;
protected Label label;
public InTextboxLabel(TextBoxBase box, string hint)
{
this.box = box;
this.hint = hint;
box.Enter += new EventHandler(box_Enter);
box.Leave += new EventHandler(box_Leave);
box.TextChanged += new EventHandler(box_TextChanged);
label = new Label();
label.Text = hint;
label.ForeColor = SystemColors.ActiveBorder;
box.Controls.Add(label);
label.Dock = DockStyle.Fill;
label.Click += new EventHandler(panel_Click);
if (String.IsNullOrEmpty(box.Text))
label.Show();
}
void box_TextChanged(object sender, EventArgs e)
{
if (!String.IsNullOrEmpty(box.Text))
label.Hide();
else if (! box.Focused)
label.Show();
}
void panel_Click(object sender, EventArgs e)
{
label.Hide();
box.Select();
}
void box_Leave(object sender, EventArgs e)
{
if (String.IsNullOrEmpty(box.Text))
label.Show();
}
void box_Enter(object sender, EventArgs e)
{
label.Hide();
}
}
In the client code of a Form, you may plug class InTextboxLabel
to TextBox
objects. When the form is loaded, we have labels embedded in text boxes.
private void Form1_Load(object sender, EventArgs e)
{
new InTextboxLabel(textBox1, "User Name");
new InTextboxLabel(maskedTextBox1, "Password");
}
Will those InTextboxLabel
objects be disposed after Form1_Load
is finished? No.
InTextboxLabel
provides visual effects to the TextBox
object through subscribing to some events of the TextBox
object, thus the TextBox
object has references to the InTextboxLabel
object.
In the attached source code, you may also find a class named InTextBoxGraphic
, which can give some visual effects like the one in the Google Taskbar.
What's This Design Pattern?
Rather than developing something like the TextboxWithEmbeddedLabel
class derived from the Textbox
class, we have InTextboxLable
being plugged into a TextBox
object. This approach is light-weight and flexible. It can work on derived classes of Textbox
like class MaskedTextBox
, and in addition, you may merge features introduced by the same design pattern, for example, if you have codes like this:
new InTextboxLabel(textBox1, "User Name");
new Flashing(textBox1);
Now we have a flashing text box with an embedded label.
As you can see, this design pattern is based on the builder pattern with extension. The director subscribes to some events of the builder, thus the builder now has a reference to the director. Shall we continue to call this builder pattern? or helper? Just looks similar.
Somehow I came up with names like Agent
and Parasite
.
As we knew, a pattern is named after a logical structure, call sequences and purposes. I haven't yet known the "official" name that might exist in the programming world. Maybe you can tell me.
Anyway, for the time being, I would call it the Agent
pattern, and the class being developed is called the Agent
class.
Points of Interest
You might be thinking of introducing an in-box label to other Windows controls like ComboBox
or DatetimePicker
. Moreover, you might consider evolving this InTextboxLabel
into something like InBoxLabel
to work on any derived class of the Control
class. While it is easy to change the above code, you need to be aware that in different derived classes of the Control
class, the Text
property has different meanings, for example, property Text
in class DateTimePicker
and class Panel
is inappropriate.
Where Is the IDisposable Interface?
You might be wondering why class InTextboxLabel
does not implement the Idisposable
interface, as the class contains a Label
object which implements the IDisposable
interface. Microsoft FxCop will cry out for this.
When the form or the text box with InTextboxLable
is disposed, will the Label
object created inside InTextboxLabel
be safely disposed? The answer is yes. Though the Label
object is created inside InTextboxLabel
, it is then assigned to the Controls
property of the TextBox
object which implement IDisposable
. When the TextBox
object is disposed, the Controls
of the TextBox
will be disposed, and the Label
object attached will go, then the sequence reaches InTextboxLabel
. As the Textbox
object is the only one having the reference to InTextboxLabel
, GAC reached the end and will dispose InTextboxLabel
. I have tested with Spy+ to monitor the Windows resources allocated. Spy+ showed that the window handle to the Label
was gone when the TextBox
was disposed.
Yes, it is more robust to have IDisposable
implemented if you are going to evolve this class for flexibility of adapting different use cases. The current implementation works for these scenarios:
- The agent (parasite) always lives with the host
TextBox
. It is a long live object with the host. - Nobody else but the host has reference to the agent.
I had some very thoughtful discussions with a colleague who has in-depth knowledge and experience of .NET. He pointed out many weaknesses of the implementation regarding a greater vision. The following works will make the class more healthy and more robust for different scenarios:
Implement IDisposable
interface
Hide the constructors, and provide some static
functions delivering the instantiated class. So, we will have something like:
public static InTextboxLabel AttachLabelToTextBox(TextBox textBox, string hint)
{return new InTextboxLabel(textbox, hint); }
I agree this will make the interfaces of the class more meaningful and more friendly. Actually in .NET 2, there are a lot of classes delivering objects this way.
After all, I will just remind you that the discussion around IDisposable
in this section is not part of this design pattern, as this pattern does not necessarily create disposable objects inside.
Agile Approach vs. Component Suits
I have been using this design pattern for years, with Delphi coding, when I felt that developing a derived class or using a component suit is overkill or too expensive.
Generally I don't like a very fancy UI, skinny things, strange shape buttons, etc. which I consider are too disturbing and confusing. I generally just need a little bit extra from existing generic Windows controls provided by the development tools like Delphi and Visual Studio. There are many high quality 3rd party components delivering powerful and consistent UI experiences, and I do use them, such as Jedi Libraries and Turbo Power suits. They are open sources, and it is quite safe to stay with them. I did sometimes use some commercial packages like Info Power, mostly only when I needed some powerful UI features urgently. Though I could purchase the source code, I did not have the interest or resources to maintain these heavy codes.
As a matter of fact, I gradually had removed visual components of Info Power suit from my legacy projects when I had time to do so, replaced the codes with the agent pattern described above. The codes are short and easy to maintain. Just let me outline all benefits of this agile approach.
- To implement a similar set of visual effects, the code of the agent pattern is shorter than that in component suits.
- Because the agent class talks to an existing class, when the existing classes get upgraded, the agent class can be compiled with these upgraded classes seamlessly. If I used component suits, it could be common that I had to get XX suit for Delphi 5, and then XX suit for Delphi 6, 10, ...because the source codes of XX suit for Delphi 5 might mostly not be compiled with Delphi 7.
- Comparing with the approach of deriving from an existing class, the agent class can work with any derived classes of the existing class. If you use the approach of deriving, you will then have to have a base class with the special features in order to deliver the features to derived classes. However, if the derived classes are from different vendors, you will have trouble.
- You may merge features of agent classes into the same client object, without changing the class structure. You just need to plug the agent object into the client object in client code.
Of course, the Agent pattern is not a silver bullet. It has its own use cases and limitations.
- You need spend some time to plan, design and implement. You won't do it when you have a closing deadline and there exist free or commercial packages around.
- These agent classes might be hard for those junior programmers to use, who only get used to drag-dropping components from tool palettes to design time forms.
Although previous and latter examples are with visual controls, this design pattern is not limited to visual controls. Later, I will provide some links to some examples codes which were for TDataset in Delphi.
I started my IT career in programming on different embedded devices since 1992, such as credit card readers, smart card readers and Palm Pilot.
Since 2000, I have mostly been developing business applications on Windows platforms while also developing some tools for myself and developers around the world, so we developers could focus more on delivering business values rather than repetitive tasks of handling technical details.
Beside technical works, I enjoy reading literatures, playing balls, cooking and gardening.