Click here to Skip to main content
12,996,768 members (93,654 online)
Click here to Skip to main content
Add your own
alternative version


158 bookmarked
Posted 28 Sep 2004

Automatic Runtime Tab Order Management for Windows Forms

, 28 Oct 2004
Rate this:
Please Sign up or sign in to vote.
This article presents the TabOrderManager, which is a class that automatically adjusts the tab order on a Windows form based on different high-level schemes.


Good tab orders on your forms are important. They allow experienced users to more rapidly interact with your application, and they may even be necessary to enable your application for users that cannot manipulate the mouse. You may sometimes find it desirable to set the tab order at runtime. For example, you may allow your users to customize the visibility or position of form controls, and you'd like a professional tab order even when you don't know exactly how the final form will look. Or you may not want to have to worry about maintaining the tab order for complex forms at design-time as the design changes. The TabOrderManager class will help you easily automate the process of applying a nice tab order at runtime. And the TabSchemeProvider component makes it simple to set up your tabbing strategies in the Windows Forms Designer.


This article assumes a basic familiarity with Windows Forms programming in the .NET Framework using C# or Visual Basic .NET.

Using the code

The TabOrderManager class is currently available in both C# and Visual Basic .NET. It's quite simple: Given a container control (probably a Form, but possibly a GroupBox, TabControl, Panel, etc.), calling the SetTabOrder method will automatically adjust the TabIndex properties on child controls to implement a tab order that is either primarily "across the container, then down" or "down the container, then across". By default, this strategy is inherited by child container controls. However, using the SetSchemeForControl method, you can override the strategy for specific containers. One situation where this is useful is when you would like users to tab through a series of GroupBoxes across-first, but tab through set of enclosed TextBoxes down-first.

Here is an example invocation of the TabOrderManager to set an across-first tabbing strategy in C#:

// In constructor after InitializeComponent (or whatever other 
// code might set  controls' TabIndex properties).
(new TabOrderManager(this)).SetTabOrder(TabOrderManager.TabScheme.AcrossFirst);

And in Visual Basic.NET:

'// In constructor after InitializeComponent (or whatever 
'// other code might set controls' TabIndex  properties).
Dim tom As TabOrderManager = New TabOrderManager(Me)


To implement a given tab scheme strategy, we need to sort the controls and then set their tab order. The key is how the controls are sorted. If the primary scheme is "across the container, then down", our primary sorting priority is by the controls' Top property values. If and only if the Top properties are the same, then we fall back to sorting on the Left property values. The converse is true for the "down the container, then across" tabbing scheme. We use the .NET Framework's built-in ability to sort collections based on a custom IComparer implementation. Our IComparer is called TabSchemeComparer, and its Compare method implements the sorting strategy described above.

If a given control is itself a container (that is, it has child controls), we need to recurse. If the scheme override functionality is being used, we need to check whether or not we need to change the tab scheme at each level of the recursion. Tab scheme overrides for individual containers are tracked using a HashTable that lives in the TabOrderManager that the user creates. When we recurse, we create a new TabOrderManager and pass the overrides down. The process of creating auxiliary TabOrderManagers is invisible to the client code. To see a tab scheme override in action, you may choose to add a down-first override to the group box inside the tab control of the demo application.

The TabSchemeProvider Component

You may also want to configure your tab schemes in the Windows Forms designer without having to write any code. The TabSchemeProvider component is a thin wrapper around the TabOrderManager class to allow you to do just that. The TabSchemeProvider implements the IExtenderProvider interface to dynamically add a TabScheme property to your container controls. A complete discussion of IExtenderProvider is beyond the scope of this article. Here, I will simply explain how you can take advantage of the TabSchemeProvider functionality and discuss the interesting implementation details.

TabSchemeProvider Usage

In the included solution, the TabOrder class library project contains both the TabOrderManager class and the TabSchemeProvider component. After you build this project, you can then add the TabSchemeProvider component to your Windows Forms toolbox by right-clicking, choosing Add/Remove Items, and browsing to the TabOrder.dll assembly that you compiled. Once it's on the toolbox, you may drag and drop it onto a Windows Form. There it sits in the component tray. But if you now examine the Properties window for your Form, GroupBoxes, Panels, or UserControls, you will see a new TabScheme property which you can set to None (the default), AcrossFirst, or DownFirst. At runtime, during the Form Load event, the selected tab scheme for each container will propagate down through its child controls as though you had TabOrderManager.SetTabOrder on the containers in question.

TabSchemeProvider Implementation

There are some implementation details for the TabSchemeProvider control that are worth noting. As I stated above, the TabSchemeProvider gets its interesting functionality from the TabOrderManager class. Thus, getting it to work is just a matter of creating a TabOrderManager instance for the top-level form, adding overrides for all other container controls with the TabScheme property set, and calling TabOrderManager.SetTabOrder in the Form Load event (which occurs after the form-designer-generated code has positioned all of the controls in the Form's control hierarchy).

The surprisingly difficult part is getting a reference to the top-level Form whose Load event we need. Components like the TabSchemeProvider are not sited on the Form at runtime, and therefore do not automatically have access to the control hierarchy. This is in contrast to Controls, which have properties like Control.Parent or Control.TopLevelControl. For the purposes of the TabSchemeProvider component, when the Form itself is one of the controls whose tab order needs to be set, this is not a problem. In that case, the designer generates a line of code like the following:

// DemoForm

The TabSchemeProvider can detect that the TabScheme of the Form is being set and use the given Form instance to wire up a Load event handler. Case closed. But what happens when the Form has the default tab scheme of TabOrderManager.TabScheme.None? In that case, no such line of code is generated by the designer, and it is no longer clear from where the TabSchemeProvider can obtain its Form instance. One might think that when SetTabScheme is called for non-Form Controls, you could just grab the Form reference out of the Control.TopLevelControl property. Unfortunately, this does not work, because in general, SetTabScheme may get called before the Control and/or its parents have been added to the Form, and Control.TopLevelControl is null in that situation.

One solution to this problem would have been to remove the DefaultValue attribute from the extender's TabScheme property, effectively forcing the Windows Forms Designer to generate a SetTabScheme call for each and every supported container control including the top-level form. This isn't very satisfying. So, I worked until I came up with an alternative.

The final implementation recognizes that there are two possibilities when the TabSchemeProvider component is notified via a call to SetTabScheme that a container wants its tab order managed:

  1. The control is already a part of the Form's control hierarchy (which includes the case where the control is the form). Then we can directly and immediately obtain a reference to the Form and hook its Load event.
  2. The control is not already a part of the Form's control hierarchy. Here, we hook the ParentChanged event for the control and for all of its ancestors. Eventually, the control or one of its ancestors will be added to the Form, and we can hook the Form's Load event in the resulting ParentChanged handler.

When we finally do discover the form reference via the handling of either of the two possibilities, we have the information we need and can short-circuit further processing of ParentChanged events.

Finally, if you use more than one TabSchemeProvider on a given form, the resulting tab order will depend on the order in which they process the Form Load event. In other words, the behavior is undefined, so you should avoid this scenario.


  • Initial release: 09/28/2004
  • Added TabSchemeProvider component: 10/28/2004


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Scott McMaster
Web Developer
United States United States
I have over 10 years of full-lifecycle software development experience on a variety of large projects. I have a B.S. in Math from the University of Nebraska-Lincoln, a Masters in Software Engineering from Seattle University, and a Masters in Computer Science from the University of Maryland. I specialize in building and installing tools, frameworks, and processes that improve developer productivity and product quality. I am currently based in the Seattle area.

You may also be interested in...

Comments and Discussions

QuestionLabels? Pin
Member 355091623-Jan-17 7:21
memberMember 355091623-Jan-17 7:21 
QuestionAwesome Pin
jp135716-Sep-16 8:23
memberjp135716-Sep-16 8:23 
PraiseCongratulations Pin
Member 1150453313-May-16 4:41
memberMember 1150453313-May-16 4:41 
QuestionThanks Pin
honwen2-Jul-14 17:19
memberhonwen2-Jul-14 17:19 
BugRTL form problem! Pin
ir_programmer5-Aug-13 9:40
memberir_programmer5-Aug-13 9:40 
QuestionThanks Pin
dariush_hk5-Mar-13 10:05
memberdariush_hk5-Mar-13 10:05 
GeneralMy vote of 5 Pin
Pankaj Sinai Nagarsekar11-Oct-12 0:40
memberPankaj Sinai Nagarsekar11-Oct-12 0:40 
QuestionThanks Pin
deepban74@live.com21-May-12 7:28
memberdeepban74@live.com21-May-12 7:28 
QuestionExcellent Job! Pin
Giulliano Rodrigues25-Aug-11 12:35
memberGiulliano Rodrigues25-Aug-11 12:35 
Generalform accessing problem Pin
nur hossain15-Jan-10 3:51
membernur hossain15-Jan-10 3:51 
QuestionHow can i move the tab accros different tabs? Pin
Yasir Chaudhry27-Jul-09 11:59
memberYasir Chaudhry27-Jul-09 11:59 
AnswerRe: How can i move the tab accros different tabs? Pin
Hmitosh15-Feb-10 5:18
memberHmitosh15-Feb-10 5:18 
GeneralThanks Pin
aman.tur22-Jan-09 17:16
memberaman.tur22-Jan-09 17:16 
GeneralUsing with TableLayoutPanel Pin
Shindigo29-Sep-08 3:06
memberShindigo29-Sep-08 3:06 
QuestionAny plans to support Web Forms (.aspx) as well ? Pin
Alexandru Matei13-Jun-08 23:09
memberAlexandru Matei13-Jun-08 23:09 
GeneralMore accurate results by implementing a small gap into compare method Pin
Aki9995-Jun-08 20:01
memberAki9995-Jun-08 20:01 
I had the problem that when the controls top position where only a a few pixels higher than another control that was left from this control the tab order was wrong calculated.

I could solve this problem by adding a small gap to the calulation of the TabOrderManager.Compare(...) method:

private const int MIN_GAP = 5;
if ( comparisonScheme == TabScheme.AcrossFirst )
// The primary direction to sort is the y direction (using the Top property).
// If two controls have the same y coordination, then we sort them by their x's.
if ( control1.Top < control2.Top - MIN_GAP )
return -1;
else if ( control1.Top > control2.Top + MIN_GAP )
return 1;
return ( control1.Left.CompareTo( control2.Left ) );
else // comparisonScheme = TabScheme.DownFirst
// The primary direction to sort is the x direction (using the Left property).
// If two controls have the same x coordination, then we sort them by their y's.
if ( control1.Left < control2.Left - MIN_GAP )
return -1;
else if ( control1.Left > control2.Left + MIN_GAP )
return 1;
return ( control1.Top.CompareTo( control2.Top ) );
GeneralRe: More accurate results by implementing a small gap into compare method Pin
dariush_hk5-Mar-13 10:05
memberdariush_hk5-Mar-13 10:05 
GeneralGreat Job! Pin
betopu16-Aug-07 10:26
memberbetopu16-Aug-07 10:26 
GeneralI don't see any new tab order Pin
igor-l19-Jun-06 3:13
memberigor-l19-Jun-06 3:13 
GeneralRe: I don't see any new tab order Pin
kirstyannep20-Aug-07 2:47
memberkirstyannep20-Aug-07 2:47 
NewsGeneric implementation in .Net 2.0 [modified] Pin
Ruizzie23-May-06 5:16
memberRuizzie23-May-06 5:16 
GeneralUpdated vertical comparison Pin
Rory Primrose12-Feb-06 15:36
memberRory Primrose12-Feb-06 15:36 
GeneralRe: Updated vertical comparison Pin
Rainer Halanek16-Feb-06 2:28
memberRainer Halanek16-Feb-06 2:28 
GeneralAdd-In Pin
Laurent Muller21-Dec-05 22:43
memberLaurent Muller21-Dec-05 22:43 
GeneralRe: Add-In Pin
Scott McMaster2-Jan-06 14:13
memberScott McMaster2-Jan-06 14:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170622.1 | Last Updated 29 Oct 2004
Article Copyright 2004 by Scott McMaster
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid