Click here to Skip to main content
15,885,914 members
Articles / Programming Languages / Visual Basic

Automatic Runtime Tab Order Management for Windows Forms

Rate me:
Please Sign up or sign in to vote.
4.93/5 (78 votes)
28 Oct 20047 min read 226.7K   5.4K   164  
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.
////////////////////////////////////////////////////////////////////////////////////
// TabSchemeProvider.cs
//
// By Scott McMaster (smcmaste@hotmail.com)
// 10/10/2004
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
////////////////////////////////////////////////////////////////////////////////////
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Diagnostics;
using System.Collections;
using System.Drawing;

namespace SMcMaster
{
	/// <summary>
	/// Wrap the TabOrderManager class and supply extendee controls with a custom tab scheme.
	/// </summary>
	[ProvideProperty( "TabScheme", typeof(Control) ) ]
	[Description( "Wrap the TabOrderManager class and supply extendee controls with a custom tab scheme" )]
	[ToolboxBitmap( typeof(TabSchemeProvider), "TabSchemeProvider" )]
	public class TabSchemeProvider : Component, IExtenderProvider
	{

		#region MEMBER VARIABLES

		/// <summary>
		/// Hashtable to store the controls that use our extender property.
		/// </summary>
		private Hashtable extendees = new Hashtable();

		/// <summary>
		/// The form we're hosted on, which will be calculated by watching the extendees entering the control hierarchy.
		/// </summary>
		private Form topLevelForm = null;

		#endregion

		#region PUBLIC PROPERTIES
		#endregion

		public TabSchemeProvider()
		{
			InitializeComponent();
		}

		private void InitializeComponent()
		{

		}

		/// <summary>
		/// Get whether or not we're managing a given control.
		/// </summary>
		/// <param name="c"></param>
		/// <returns></returns>
		[DefaultValue( TabOrderManager.TabScheme.None )]
		public TabOrderManager.TabScheme GetTabScheme( Control c )
		{
			if( !extendees.Contains( c ) )
			{
				return TabOrderManager.TabScheme.None;
			}

			return (TabOrderManager.TabScheme) extendees[ c ];
		}

		/// <summary>
		/// Hook up to the form load event and indicate that we've done so.
		/// </summary>
		private void HookFormLoad()
		{
			if( topLevelForm != null )
			{
				topLevelForm.Load += new EventHandler(TopLevelForm_Load);
			}
		}

		/// <summary>
		/// Unhook from the form load event and indicate that we need to do so again before applying tab schemes.
		/// </summary>
		private void UnhookFormLoad()
		{
			if( topLevelForm != null )
			{
				topLevelForm.Load -= new EventHandler(TopLevelForm_Load);
				topLevelForm = null;
			}
		}

		/// <summary>
		/// Hook up to all of the parent changed events for this control and its ancestors so that we are informed
		/// if and when they are added to the top-level form (whose load event we need).
		/// It's not adequate to look at just the control, because it may have been added to its parent, but the parent
		/// may not be descendent of the form -yet-.
		/// </summary>
		/// <param name="c"></param>
		private void HookParentChangedEvents( Control c )
		{
			while( c != null )
			{
				c.ParentChanged += new EventHandler( Extendee_ParentChanged );
				c = c.Parent;
			}
		}

		/// <summary>
		/// Set the tab scheme to use on a given control
		/// </summary>
		/// <param name="c"></param>
		/// <returns></returns>
		public void SetTabScheme( Control c, TabOrderManager.TabScheme val )
		{
			if( val != TabOrderManager.TabScheme.None )
			{
				extendees[ c ] = val;

				if( topLevelForm == null )
				{
					if( c.TopLevelControl != null )
					{
						// We're in luck.
						// This is the form, or this control knows about it, so take the opportunity to grab it and wire up to its Load event.
						topLevelForm = c.TopLevelControl as Form;
						HookFormLoad();
					}
					else
					{
						// Set up to wait around until this control or one of its ancestors is added to the form's control hierarchy.
						HookParentChangedEvents( c );
					}
				}
			}
			else if( extendees.Contains(c) )
			{
				extendees.Remove( c );

				// If we no longer have any extendees, we don't need to be wired up to the form load event.
				if( extendees.Count == 0 )
				{
					UnhookFormLoad();
				}
			}
		}

		#region IExtenderProvider Members

		public bool CanExtend(object extendee)
		{
			return (extendee is Form || extendee is Panel || extendee is GroupBox || extendee is UserControl);
		}

		#endregion

		private void TopLevelForm_Load(object sender, EventArgs e)
		{
			Form f = sender as Form;

			Debug.Assert( f != null, "We should never wire up to anything that's not a form." );

			TabOrderManager tom = new TabOrderManager( f );

			// Add an override for everything with a tab scheme set EXCEPT for the form, which
			// serves as the root of the whole process.
			TabOrderManager.TabScheme formScheme = TabOrderManager.TabScheme.None;
			IDictionaryEnumerator extendeeEnumerator = extendees.GetEnumerator();
			while( extendeeEnumerator.MoveNext() )
			{
				Control c = (Control) extendeeEnumerator.Key;
				TabOrderManager.TabScheme scheme = (TabOrderManager.TabScheme ) extendeeEnumerator.Value;
				if( c == f )
				{
					formScheme = scheme;
				}
				else
				{
					tom.SetSchemeForControl( c, scheme );
				}
			}

			tom.SetTabOrder( formScheme );
		}

		/// <summary>
		/// We track when each extendee's parent is changed, and also when their parents are changed, until
		/// SOMEBODY finally changes their parent to the form, at which point we can hook the load to apply
		/// the tab schemes.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void Extendee_ParentChanged(object sender, EventArgs e)
		{
			if( topLevelForm != null )
			{
				// We've already found the form and attached a load event handler, so there's nothing left to do.
				return;
			}

			Control c = sender as Control;
			if( c.TopLevelControl != null && c.TopLevelControl is Form )
			{
				// We found the form, so we're done.
				topLevelForm = c.TopLevelControl as Form;
				HookFormLoad();
			}
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

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


Written By
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.

Comments and Discussions