Introduction
Do you want to impress your boss and create professional looking hyperlink entry forms for $0 in only 10 minutes? Who wouldn't? With regular .NET TextBox
and HyperLink
controls combined, that is really easy. Just follow a few simple steps in this article and you'll have your own full featured LinkTextBox
control.
Background
Remember how Email and Web Page textbox in Microsoft Outlook work as a hyperlink when they are not focused, and as a regular textbox when they have focus? If you don't, just open your Outlook and then come back to this article...
Using the code
Let's figure out how it works first. The control is actually a combination of TextBox
and LinkLabel
controls. HyperLink
control is positioned so that it overlaps the TextBox
's text and intercepts the clicks. But if a user clicks in the TextBox
and not on the LinkLabel
, the TextBox
gets the focus and LinkLabel
hides. LinkTextBox
acts and looks exactly as a regular TextBox
until it loses focus. Then the LinkLabel
is made visible again and the user can click on the link.
What you want to do is extend the regular TextBox
. You can do that in your existing application, in any of your libraries, or create a new project and add a Class Object file and extend the System.Windows.Forms.TextBox
. You could also use the Add Inherited Control... wizard, but I usually don't. I can type inheritance declaration faster than click through the wizard. In the constructor, let's create an instance of LinkLabel
and position it so that it exactly overlaps the TextBox
's text. We also need to show it and copy the TextBox.Text
to the LinkLabel
so it looks OK at design time. We also need to wire up a Clicked
event in here.
namespace DavidVidmar.Windows.Forms {
public class LinkTextBox : TextBox {
private LinkLabel llLinkLabel;
public LinkTextBox() {
llLinkLabel = new LinkLabel();
this.Controls.Add(llLinkLabel);
llLinkLabel.AutoSize = true;
llLinkLabel.Left = -1;
llLinkLabel.Top = 1;
llLinkLabel.Visible = true;
llLinkLabel.Text = this.Text;
llLinkLabel.LinkClicked +=
new LinkLabelLinkClickedEventHandler(ll_LinkClicked);
}
}
}
Next, let's prepare an enum
for various types of links. We need one that tells the user the link feature is disabled, one for email links, one for FTP links and one for web links or secure web links.
public enum LinkTypes {
None,
Http,
Ftp,
Email
}
Now, we can add a LinkType
property to LinkTextBox
. It tells what kind of link the Text
property represents and if it is a link at all. By default, the LinkTextBox
will work as a regular TextBox
and we need to hide a LinkLabel
. We'll call this mode edit mode and we'll use a SwitchToEditMode()
method that we will create later. But if it is a real LinkTextBox
, we need to switch to clickable mode, where LinkLabel
is visible, again with SwitchToEditMode()
just with a different parameter value. We also need to copy the information from TextBox
to LinkLabel
. Let's assume for a moment FillLinkData()
method does just that.
private LinkTypes ltLinkType = LinkTypes.None;
[DefaultValue(LinkTypes.None)]
public LinkTypes LinkType {
set {
this.ltLinkType = value;
if (value == LinkTypes.None) {
SwitchToEditMode(true);
} else {
SwitchToEditMode(false);
FillLinkData();
}
}
get { return this.ltLinkType; }
}
The SwitchToEditMode()
method only needs to show or hide the LinkLabel
. We can easily do that in just one line of code:
protected void SwitchToEditMode(bool _bEditMode) {
llLinkLabel.Visible = !_bEditMode;
}
The FillLinkData()
method we used in the property setter is a little more complicated. We need to copy the value of Text
property from TextBox
to LinkLabel
, try to figure out what protocol to use in our link, and update the Links
array of LinkLabel
. The easiest way to do that is to create a new entry each time around.
private void FillLinkData() {
llLinkLabel.Text = this.Text;
string sLinkType = "";
switch (ltLinkType) {
case LinkTypes.Http:
if (this.Text.ToLower().IndexOf(@"http://") < 0 &&
this.Text.ToLower().IndexOf(@"https://") < 0) {
sLinkType = @"http://";
}
break;
case LinkTypes.Ftp:
if (this.Text.ToLower().IndexOf(@"ftp://") < 0) {
sLinkType = @"ftp://";
}
break;
case LinkTypes.Email:
if (this.Text.ToLower().IndexOf("mailto:") < 0) {
sLinkType = "mailto:";
}
break;
}
llLinkLabel.Links.Clear();
llLinkLabel.Links.Add(0, llLinkLabel.Text.Length, sLinkType + this.Text);
}
We could figure out when exactly to copy the text from TextBox
to LinkLabel
, but the easy way out is every time the Text
property on TextBox
changes. It's not optimal, but I don't think anybody will notice that.
protected override void OnTextChanged(EventArgs e) {
base.OnTextChanged (e);
if (ltLinkType != LinkTypes.None) {
FillLinkData();
}
}
Now, let's cover the switching from editable to clickable mode. If TextBox
control gets the focus, user must have clicked to the right of the LinkLabel
in TextBox
, so we need to switch to edit mode. If control loses focus, we need to switch to clickable mode. We already created SwitchToEditMode()
method that will do everything. All we need to do is override the OnGetFocus()
and OnLostFocus()
methods.
protected override void OnGotFocus(EventArgs e) {
base.OnGotFocus(e);
if (ltLinkType != LinkTypes.None) this.SwitchToEditMode(true);
}
protected override void OnLostFocus(System.EventArgs e) {
base.OnLostFocus(e);
if (ltLinkType != LinkTypes.None) this.SwitchToEditMode(false);
}
It's not very obvious but later on you will realize that some strange focus related issue will surface. If you give focus to a control through tab it will be different than if you shift-tab to the control. With some tricky focus handling, the issue is resolved.
private void llLinkLabel_GotFocus(object sender, EventArgs e) {
if (!bLinkClicked) {
this.Focus();
bLinkClicked = false;
}
}
private void llLinkLabel_MouseDown(object sender, MouseEventArgs e) {
bLinkClicked = true;
}
Now, let's make this thing clickable! In the constructor, we already wired the event, now let's code it. We need to check if we should use a link at all and then call a UserHyperlink()
method that will actually open the link.
private void ll_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) {
if (ltLinkType != LinkTypes.None) UseHyperlink();
}
Now, let's figure out how to open a link. Don't even start thinking about directly interacting with IE, Outlook, or any other browser or mail application. Let's use the OS to figure out what to do with the link. This way, we just execute the link, and Windows will open either a default mail application or default browser. This is the right way to do it. Just create a new Process
class, call Start()
method with the link, and keep your fingers crossed. The bad part is that you don't really know if it will work. Just catch an exception and throw a new one. In an application, just catch this exception, show a MessageBox
, and give the user a chance to figure out what's wrong with the link.
private void UseHyperlink() {
try {
if (llLinkLabel.Links.Count > 0) {
string sLink = llLinkLabel.Links[0].LinkData.ToString();
System.Diagnostics.Process.Start(sLink);
}
} catch (Exception ex) {
throw new ArgumentException("Link error!", ex);
}
}
We can easily build in one more feature. If the control is in the edit mode (LinkLabel
is hidden), we can help the user test the link. If the user clicks in the control while the Ctrl key is pressed, he can launch the link.
protected override void OnMouseDown(MouseEventArgs e) {
if (ltLinkType != LinkTypes.None) {
if (e.Button == MouseButtons.Left &&
(Control.ModifierKeys & Keys.Control) == Keys.Control) {
UseHyperlink();
} else {
base.OnMouseDown(e);
}
} else {
base.OnMouseDown(e);
}
}
And that's it. The control is ready for use. Just set the LinkType
property and the control is ready for some serious clickin'! Or you could even let the user decide if the text in the LinkTextBox
is a link with a ComboBox
next to it!
I hope you realized how you can create quite powerful controls that could be easily a part of an expensive UI library, with a simple combination of two basic controls and some simple UI processing.
So, before you start Googlin' or even throwing couple of bucks out the window for a commercial control, think about writing your own control. It's the best in terms of control, and when it's this simple, it's really fun.
History
- v1.0 - Initial release.
- v1.1 - Added support for https:// and ftp:// links, issues related to different ways of giving focus to the control is resolved.
Keep checking this page out to see if anyone has found a bug or got an idea of how to extend the LinkTextBox
.
You can also come over to my blog and see if there is anything new cooking.