LinkBox - Fun with Panels, LinkLabels and AutoScroll

Remark: This is a spin-off of my work on GhostDoc. As the control I wrote for GhostDoc is highly specific to the actual problem I wanted to solve, I’ll only explain the basic idea and provide the source for the “LinkBox” control which I use as a base class. For some basic use cases this control may already be sufficient, for other use cases you’ll have to add some code.

Situation: I need something like this in one of my dialogs:

(Note this is a mockup inspired by an Outlook dialog,
not related to my work on GhostDoc)

Clicking a link raises an event, the event arguments provide information about which link was clicked.

If necessary, scrollbars appear:


Question: How do I implement this?

First thought: Web browser control. Why? Because of the links. Hmm… Do I need any other feature of HTML? No, just the links.

Second thought: In this case, a web browser control smells like overkill. So what else gives me multi-line text, links and scrolling? A RichTextBox control. While using a RichTextBox for text input (its main purpose) can be a bit frustrating, I’ve had good success in the past using it for output (see this and this post). And you can have links in rich text. By default, only http and mailto links are supported, but that’s a limitation that can be circumvented (see this CodeProject article by “mav.northwind”). Unfortunately, the RichTextBox control’s LinkClicked event only provides the text of the link — which can be a problem when trying to identify which link was clicked.

Third thought: If I’m able to crank out a user control for my specific needs with little effort, I’d definitely prefer that to using a web browser control. So what’s the simplest thing that would work for me? Do I need multiple links in one line? No. OK, so I can use a LinkLabel control for each individual line (which makes identifying which link was clicked pretty easy).

The solution: A user control completely filled with a panel (to get the sunken borders), link labels are added dynamically (with auto-sizing enabled) and are docked to the top, so I don’t have to take care of positioning. Scrolling is taken care of by the panel (AutoScroll), the AutoScrollMinSize is easy to determine as the labels are auto-sized (so width = largest width and height = y-coordinate of the last label’s bottom border).

The code can be downloaded here, complete with a demo project:

linkBox1.AddText("This is some fixed text");
linkBox1.AddLink("The quick brown {0}", "fox");
linkBox1.AddLink("jumps over the lazy {0}", "dog");
linkBox1.AddLink("This is a {0} with a tag object", "test", "Some tag object");


As already mentioned, the LinkBox control may be sufficient for simple use-cases. For more complicated use-cases (where you may need updating/refreshing), the control offers a SetText and an UpdateAutoScrollSize method (which in the GhostDoc source are called by a derived control). If you use LinkBox purely as a base class, you should think about making the methods protected — in a derived, application-specific control, you typically specify some sort of “data source” object and the control takes care of keeping the display up-to-date.

Implementation detail: This control uses docking for its layout, so the z-order of the LinkLabel controls is important. In general, when you add controls docked to the top in the forms designer and look at the resulting code, you’ll notice that the controls are added in reverse order. This reversed order is necessary because of the z-order that influences the docking. To achieve the correct z-order without the adding the controls in reversed order (which would make things a bit more complicated when adding controls programmatically like LinkBox does), you have to set the child index of the control:

thePanel.Controls.SetChildIndex(theLinkLabel, 0);

In the case of the LinkBox control, this leads to the situation that thePanel.Controls[0] does not contain the LinkLabel control representing the first line. This is nothing you usually have to care about, I just mentioned it to avoid confusion in case you dig a little bit deeper in the debugger e.g. when running the demo.

Download the code

No Comments