OutlookBar Control Implementation

function OpenWin(url, w, h) { if(!w) w = 400; if(!h) h = 300; window.open(url, "_new", "width=" + w + ",height=" + h + ",menubar=no,toobar=no,scrollbars=yes", true); }

OutlookBar Control Implementation

The implementation for the OutlookBar control is divided into three parts:

  • The OutlookBar control

  • Custom OutlookBarTab collection

  • OutlookBarDesigner

The OutlookBar control provides a custom event for notification when the selected OutlookBarTab changes. In addition, some basic properties are provided, such as the active tab, the index of the active tab, and a collection of OutlookBarTab objects.

The OutlookBarTab collection provides a means to add new tabs to the OutlookBar at design-time or runtime. This collection is discussed later in the chapter.

The OutlookBarDesigner adds support for drag-and-drop during design-time, as well as the capability to activate the hosted tabs within the control similar to the Windows Forms TabControl. This designer is discussed later in the chapter.

To keep the code to a minimum while still providing a useful control, the OutlookBar control will provide only a bare minimum set of properties and only a single event. Table 8.1 lists the properties exposed by the OutlookBar control.

Table 8.1. OutlookBar Control Properties and Descriptions
Property Description
Tabs Exposes a custom collection of OutlookBarTab components.
ActiveTabIndex Provides a quick index into the Tabs collection.
ActiveTab Provides access to the currently selected or active tab within the OutlookBar control. This property is read-only.

As stated previously, the OutlookBar control provides only a single event. In addition to the event, a custom event argument is defined as well. Table 8.2 lists the properties of the custom event: OutlookBarSelectedTabChangedEventArgs. (That name alone can cause carpal tunnel syndrome.)

Table 8.2. Properties of the OutlookBarSelectedTabChangedEventArgs Class
Property Description
ActiveTab The currently active OutlookBarTab component.
PreviousTab The formerly active OutlookBarTab component.

The custom event is exposed via the following delegate:

 public delegate void OutlookBarSelectedTabChangedEventHandler( object sender,  graphics/ccc.gifOutlookBarSelectedTabChangedEventArgs e ); 

Most of the OutlookBar control code, excluding the custom nested collection, deals with drawing and child control management. Listing 8.2 provides the core implementation of the OutlookBar control. The implementation is discussed in more detail following this code listing.

Listing 8.2 OutlookBar Control
   1: using System;   2: using System.Drawing;   3: using System.Drawing.Drawing2D;   4: using System.ComponentModel;   5: using System.Windows.Forms;   6: using System.Collections;   7:   8: namespace SAMS.ToolKit.Controls   9: {  10:  11:     public enum HitTestType {  12:         TABS,  13:         CLIENT  14:     }  15:  16:     #region OutlookBar EventArgs  17:  18:     public class OutlookBarSelectedTabChangedEventArgs : EventArgs {  19:         private OutlookBarTab        activeTab;  20:         private OutlookBarTab        prevTab;  21:  22:  23:         public OutlookBarTab ActiveTab {  24:             get {  return activeTab; }  25:         }  26:  27:         public OutlookBarTab PreviousTab {  28:             get {  return prevTab; }  29:         }  30:  31:         public OutlookBarSelectedTabChangedEventArgs(OutlookBarTab active,  graphics/ccc.gifOutlookBarTab prev ) {  32:             activeTab    = active;  33:             prevTab        = prev;  34:         }  35:     }  36:  37:     public delegate void OutlookBarSelectedTabChangedEventHandler( object sender,  graphics/ccc.gifOutlookBarSelectedTabChangedEventArgs e );  38:     #endregion  39:  40:     #region OutlookBar Control  41:  42:     [  43:     Description( "The OutlookBar Control aka Shortcut Bar" ),  44:     DefaultEvent( "SelectedTabChanged" ),  45:     DefaultProperty( "Tabs" ),  46:     Designer( typeof( SAMS.ToolKit.Design.OutlookBarDesigner ) )  47:     ]  48:     public class OutlookBar : System.Windows.Forms.ContainerControl {  49:  50:         #region Instance Members, Properties and Events  51:  52:         private System.ComponentModel.Container     components    = null;  53:         private TabCollection                       tabCollection;  54:         private int                                 activeTabIndex;  55:         private int                                 hitTabIndex;  56:         private Rectangle                           activeClientRect;  57:         private int                                 tabHeight;  58:  59:  60:         [  61:         Description( "Raised when the active outlooktab changes" ),  62:         Category( "Behavior" )  63:         ]  64:         public event OutlookBarSelectedTabChangedEventHandler SelectedTabChanged;  65:  66:  67:         [  68:         Description( "The collection of OutlookTabs" ),  69:         Category( "Appearance" ),  70:         DesignerSerializationVisibility( DesignerSerializationVisibility.Content )  71:         ]  72:         public TabCollection Tabs {  73:             get {  return tabCollection; }  74:             set {  75:                 tabCollection.Clear( );  76:                 tabCollection = value;  77:             }  78:         }  79:  80:         [  81:  Browsable( false ),  82:         DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )  83:         ]  84:         public int ActiveTabIndex {  85:             get {  86:                 return activeTabIndex;  87:             }  88:             set {  89:                 ActivateTab( (int)value );  90:             }  91:         }  92:  93:         [  94:         Browsable( false ),  95:         DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden )  96:         ]  97:         public OutlookBarTab ActiveTab {  98:             get {  return tabCollection[ activeTabIndex ]; }  99:         } 100: 101:         [ DesignerSerializationVisibility( DesignerSerializationVisibility.Hidden ) ] 102:         public new ControlCollection Controls { 103:             get {  return base.Controls; } 104:         } 105: 106: 107:         #endregion 108: 109:  #region Construction, Initialization and Public Instance Methods 110: 111:         public OutlookBar( ) { 112:             InitializeComponent( ); 113: 114:             //Set default values 115:             tabCollection        = new TabCollection( this ); 116:             activeTabIndex        = -1; 117:             hitTabIndex            = -1; 118:             activeClientRect    = new Rectangle(0,0,0,0); 119:             tabHeight            = 24; 120:         } 121: 122:         //Required for Designer Support 123:         private void InitializeComponent( ) { 124:         } 125: 126:         public bool HitTest( HitTestType testType, Point pt, out int TabIndex ) { 127:             bool bResult = false; 128:             TabIndex = -1; 129: 130:             if( testType == HitTestType.CLIENT ) { 131:                 bResult = this.activeClientRect.Contains( pt ); 132:             }  else { 133:                 int hitTabCache = this.hitTabIndex; 134:                 bResult = HitTestTabs( pt ); 135:                 if( bResult ) 136:                     TabIndex = hitTabIndex; 137:  hitTabIndex = hitTabCache; 138:             } 139:             return bResult; 140:         } 141: 142:         #endregion 143: 144:         #region ContainerControl Overrides 145: 146:         protected override void Dispose( bool disposing ) { 147:             if( disposing ) { 148:                 if (components != null) { 149:                     components.Dispose(); 150:                 } 151:             } 152:             base.Dispose( disposing ); 153:         } 154: 155:         protected override void OnFontChanged( EventArgs e ) { 156:             base.OnFontChanged( e ); 157:             ReCalctabHeight( ); 158:             Invalidate( ); 159:         } 160: 161:         protected override void OnSizeChanged( EventArgs e ) { 162:             base.OnSizeChanged( e ); 163:             ReCalcClientRect( ); 164:             Invalidate( ); 165:         } 166: 167:         protected override void OnMouseDown( MouseEventArgs e ) { 168:             if( e.Button == MouseButtons.Left ) { 169:                 if( HitTestTabs( new Point( e.X, e.Y ) ) ) { 170:       Graphics g        = CreateGraphics( ); 171:                     DrawTab( g, hitTabIndex, ButtonState.Pushed ); 172:                     g.Dispose( ); 173:                 }  else 174:                     base.OnMouseDown( e ); 175:             }  else 176:                 base.OnMouseDown( e ); 177:         } 178: 179:         protected override void OnMouseUp( MouseEventArgs e ) { 180:             if( (e.Button == MouseButtons.Left) && (hitTabIndex != -1) ) { 181: 182:                 Graphics g = CreateGraphics( ); 183:                 DrawTab( g, hitTabIndex, ButtonState.Normal ); 184:                 g.Dispose( ); 185: 186:                 if( tabCollection[ hitTabIndex ].HitTest( new Point( e.X, e.Y ) ) ) { 187:                     int activeIndex = hitTabIndex; 188:                     hitTabIndex = -1; 189:                     ActivateTab( activeIndex ); 190:                 } 191: 192:             }  else 193:      base.OnMouseUp( e ); 194:         } 195: 196:         protected override void OnPaint( PaintEventArgs e ) { 197:             base.OnPaint( e ); 198:             DrawBorder( e.Graphics ); 199:             DrawTabs( e.Graphics ); 200:             ActivateChildControl( ); 201:         } 202:         #endregion 203: 204:         #region Protected/Internal/Private Instance Methods 205: 206:         protected virtual bool HitTestTabs( Point pt ) { 207: 208:             for( int tabIndex = 0; tabIndex < tabCollection.Count; tabIndex++ ) { 209:                 if( tabCollection[ tabIndex ].HitTest( pt ) ) { 210:                     hitTabIndex = tabIndex; 211:                     return true; 212:                 } 213:             } 214:             hitTabIndex = -1; 215:             return false; 216:         } 217: 218:         protected virtual void ActivateTab( int tabIndex ) { 219: 220:             if( (tabIndex == activeTabIndex) || (tabIndex >= tabCollection.Count)) 221:                 return; 222: 223:             OutlookBarTab prevTab    = tabCollection[ activeTabIndex ]; 224:             OutlookBarTab activeTab = tabCollection[ tabIndex ]; 225: 226:             DeActivateTab( activeTabIndex ); 227: 228:             activeTabIndex = tabIndex; 229: 230:             ReCalcClientRect( ); 231: 232:             Invalidate( ); 233:             Update( ); 234:             ActivateChildControl( ); 235: 236:             OnSelectedTabIndexChanged( new OutlookBarSelectedTabChangedEventArgs(  graphics/ccc.gifactiveTab, prevTab ) ); 237:         } 238: 239:         protected virtual void DeActivateTab( int tabIndex ) { 240:             OutlookBarTab T = tabCollection[ tabIndex ]; 241:             if( (T != null) && (T.Child != null) ) 242:   T.Child.Visible = false; 243:         } 244: 245:         protected virtual void ActivateChildControl( ) { 246:             OutlookBarTab T = tabCollection[ activeTabIndex ]; 247:             if( (T != null ) && (T.Child != null) ) { 248:                 T.Child.Visible        = true; 249:                 T.Child.Size        = new Size( activeClientRect.Width,  graphics/ccc.gifactiveClientRect.Height ); 250:                 T.Child.Location    = new Point( activeClientRect.Left,  graphics/ccc.gifactiveClientRect.Top ); 251:                 T.Child.BringToFront( ); 252:             } 253:         } 254: 255:         protected virtual void ReCalctabHeight( ) { 256:             Graphics g = CreateGraphics( ); 257:             tabHeight =    (int)(Font.GetHeight( g ) +  graphics/ccc.gif(float)(2*OutlookBarTab.EDGE_PADDING)); 258:             g.Dispose( ); 259:         } 260: 261:         protected virtual void ReCalcClientRect( ) { 262:             activeClientRect = new Rectangle( 1,1,Width - 2,Height - 2); 263:             activeClientRect.Y += tabHeight * ( activeTabIndex + 1 ); 264:   activeClientRect.Height -= tabCollection.Count * tabHeight; 265:         } 266: 267:         protected virtual void DrawBorder( Graphics g ) { 268:             ControlPaint.DrawBorder3D( g, new Rectangle(0,0,Width,Height),  graphics/ccc.gifBorder3DStyle.Etched, Border3DSide.All ); 269:         } 270: 271:         protected virtual void DrawTabs( Graphics g ) { 272: 273:             if( tabCollection.Count == 0 ) 274:                 return; 275: 276:             Rectangle tabRect = new Rectangle( 1, 1, Width - 2, tabHeight ); 277:             for( int tabIndex = 0; tabIndex < tabCollection.Count; tabIndex++ ) { 278:                 OutlookBarTab T = tabCollection[ tabIndex ]; 279:                 DrawTab( g, T, tabRect, (tabIndex == hitTabIndex ?  graphics/ccc.gifButtonState.Pushed : ButtonState.Normal) ); 280: 281:   if( tabIndex == activeTabIndex ) 282:                     tabRect.Y = activeClientRect.Bottom; 283:                 else 284:                     tabRect.Y += tabHeight; 285:             } 286:         } 287: 288:         protected virtual void DrawTab( Graphics g, int tabIndex ) { 289:             OutlookBarTab T = tabCollection[ tabIndex ]; 290:             DrawTab( g, T, T.TabRect, T.ButtonState ); 291:         } 292: 293:         protected virtual void DrawTab( Graphics g, int tabIndex, ButtonState  graphics/ccc.gifbuttonState ) { 294:             OutlookBarTab T = tabCollection[ tabIndex ]; 295:             DrawTab( g, T, T.TabRect, buttonState ); 296:         } 297:         protected virtual void DrawTab( Graphics g, int tabIndex, Rectangle tabRect,  graphics/ccc.gifButtonState buttonState ) { 298:             DrawTab( g, tabCollection[ tabIndex ], tabRect, buttonState ); 299:         } 300: 301:         protected virtual void DrawTab( Graphics g, OutlookBarTab outlookTab,  graphics/ccc.gifRectangle tabRect, ButtonState buttonState ) { 302:             outlookTab.Draw( g, tabRect, Font, buttonState ); 303:         } 304: 305:  protected virtual void SubscribeToTabEvents( OutlookBarTab tab ) { 306:             System.Reflection.EventInfo[] events = tab.GetType( ).GetEvents(  ); 307: 308:             foreach( System.Reflection.EventInfo ei in events ) 309:                 ei.AddEventHandler( tab, new EventHandler( this.OnTabPropertyChanged  graphics/ccc.gif) ); 310:         } 311: 312:         protected virtual void UnSubscribeToTabEvents( OutlookBarTab tab ) { 313:             System.Reflection.EventInfo[] events = tab.GetType( ).GetEvents( ); 314: 315:             foreach( System.Reflection.EventInfo ei in events ) 316:                 ei.RemoveEventHandler( tab, new EventHandler(  graphics/ccc.gifthis.OnTabPropertyChanged ) ); 317:         } 318: 319:         protected virtual void OnTabPropertyChanged( object sender, EventArgs e ) { 320:             Control ctrl = ((OutlookBarTab)sender).Child; 321: 322:            f( ctrl != null && !Controls.Conatins( ctrl ) ) { 323:                 ctrl.Visible = false; 324:                 this.Controls.Add( ctrl ); 325:             } 326:             Invalidate( ); 327:         } 328: 329:         protected virtual void OnSelectedTabIndexChanged(  graphics/ccc.gifOutlookBarSelectedTabChangedEventArgs e ) { 330:             if( SelectedTabChanged != null ) 331:                 SelectedTabChanged( this, e ); 332:         } 333: 334:         #endregion 335: 336: 337: 338: 339:         #region TabCollection 340:         public class TabCollection : ICollection, IEnumerable, IList { 341: 342:             private OutlookBar        owner                = null; 343:             private ArrayList        internalArrayList    = null; 344: 345: 346:             public TabCollection( OutlookBar parent ) { 347:                 owner = parent; 348:                 internalArrayList = new ArrayList( ); 349:             } 350: 351:             //ICollection Interface implementation 352:             public int Count { 353:                 get {  return internalArrayList.Count; } 354:             } 355:             public bool IsSynchronized { 356:                 get {  return internalArrayList.IsSynchronized; } 357:             } 358:             public object SyncRoot { 359:                 get {  return internalArrayList.SyncRoot; } 360:             } 361: 362:             public void CopyTo( Array array, int arrayIndex ) { 363:                 internalArrayList.CopyTo( array, arrayIndex ); 364:             } 365: 366:  //IList interface implementation 367:             public bool IsFixedSize { 368:                 get {  return false; } 369:             } 370: 371:             public bool IsReadOnly { 372:                 get {  return false; } 373:             } 374: 375:             //Only used when Interface IList is acquired. 376:             //IList iList = (IList)TabCollection; 377:             object IList.this[ int index ] { 378:                 get {  return this[ index ]; } 379:                 set {  this[ index ] = (OutlookBarTab)value; } 380:             } 381: 382:             int IList.Add( object o ) { 383:                 return this.Add( (OutlookBarTab)o ); 384:             } 385: 386:             void IList.Clear( ) { 387:                 this.Clear( ); 388:             } 389: 390:             public bool Contains( object o ) { 391:                 return internalArrayList.Contains( o ); 392:             } 393: 394:             public int IndexOf( object o ) { 395:  return internalArrayList.IndexOf( o ); 396:             } 397: 398:             void IList.Insert( int index, object o ) { 399:                 this.Insert( index, (OutlookBarTab)o ); 400:             } 401: 402:             void IList.Remove( object o ) { 403:                 this.Remove( (OutlookBarTab)o ); 404:             } 405: 406:             void IList.RemoveAt( int index ) { 407:                 this.RemoveAt( index ); 408:             } 409: 410:             //IEnumerable interface implementation 411:             public IEnumerator GetEnumerator( ) { 412:                 return internalArrayList.GetEnumerator( ); 413:             } 414: 415:             //TabCollection implementation 416:             public int Add( OutlookBarTab tab ) { 417:                 owner.SubscribeToTabEvents( tab ); 418:                 int idx = internalArrayList.Add( tab ); 419:                 if( tab.Child != null ) 420:       owner.Controls.Add( tab.Child ); 421:                 owner.ActivateTab( idx ); 422:                 return idx; 423:             } 424: 425:             public void AddRange( OutlookBarTab[] tabs ) { 426:                 foreach( OutlookBarTab T in tabs ) 427:                     Add( T ); 428:             } 429: 430:             public void Remove( OutlookBarTab tab ) { 431:                 RemoveAt( IndexOf( tab ) ); 432:             } 433: 434:             public void RemoveAt( int index ) { 435:                 OutlookBarTab T = this[index]; 436:                 owner.Controls.Remove( T.Child ); 437:                 internalArrayList.Remove( T ); 438:                 owner.UnSubscribeToTabEvents( T ); 439: 440:                 if( internalArrayList.Count > 0 ) { 441:                     //Was this the active Tab? 442:                     if( index == owner.activeTabIndex ) { 443:                         index++; index %=( Count+1 ); 444:                     } 445:                 } 446:      owner.Invalidate( ); 447:             } 448: 449:             public void Insert( int index, OutlookBarTab tab ) { 450:                 internalArrayList.Insert( index, tab ); 451:                 if( tab.Child != null ) 452:                     owner.Controls.Add( tab.Child ); 453:                 owner.SubscribeToTabEvents( tab ); 454:                 //is the new tab before or after the current tab? 455:                 if( index <= owner.activeTabIndex ) 456:                     owner.activeTabIndex++; 457:                 owner.Invalidate( ); 458:             } 459: 460:             public void Clear( ) { 461:                 foreach( OutlookBarTab tab in internalArrayList ) 462:                     owner.UnSubscribeToTabEvents( tab ); 463: 464:                 owner.Controls.Clear( ); 465:                 internalArrayList.Clear( ); 466:                 owner.activeTabIndex = -1; 467:                 owner.Invalidate( ); 468:             } 469: 470:             public OutlookBarTab this[ int index ] { 471:                 get { 472:                     // The CollectionEditor is famous for passing the 473:                     // index value of -1. For what reason, I have no idea. 474:                     try { 475:             return (OutlookBarTab)internalArrayList[index]; 476:                     }  catch( Exception e ) { 477:                         System.Diagnostics.Debug.WriteLine( e.Message ); 478:                         System.Diagnostics.Debug.WriteLine( string.Format(  graphics/ccc.gif"TabCollection[{ 0} ] invalid index", index ) ); 479:                     } 480:                     return null; 481:                 } 482:                 set { 483:                     internalArrayList[index] = value; 484:                 } 485:             } 486:         } 487:         #endregion 488: 489:     } 490:    //TabCollection Goes here } 

The code listing in 8.2 represents all the code for the OutlookBar control, including the implementation of the custom collection. The custom collection is covered later in the chapter.

The purpose of the OutlookBar control is to host other controls. Most of the code for the OutlookBar control deals with the management of the child controls. When an OutlookBar tab becomes active, the OutlookBar control must calculate the area that represents the child control's size and position. The purpose behind this calculation is so that the child control can be properly positioned within the active tab. When this is done, it appears that the child control is connected to the tab. When a different tab is activated, a new child control rectangle is calculated and the current tab's child control is repositioned and made visible.

Calculating the active child control's position requires knowing the number of tabs, the height of a tab, and the active tab index. The active tab represents the top of the child control's position, and the height of the child control is determined by subtracting the height of any tabs below the child control. The method ReCalcClientRect, on line 261 of Listing 8.2, provides the logic necessary to determine the position of an active child control.

Again, the main job or function of the OutlookBar control is the management of OutlookBarTabs and the associated child controls. Managing the various OutlookBarTabs requires tracking mouse events, just as in the test application from the preceding chapter, and providing a custom collection to track the OutlookBarTabs. Tracking mouse events occurs in the methods OnMouseDown and OnMouseUp. Each of these methods uses the HitTest method provided by the OutlookBarTab component to determine whether the mouse click has occurred on a tab component.

In addition, the base class Controls property required an attribute that prevents the ControlCollection from being persisted in generated code. By default, any exposed collection automatically has its contents saved, or generated, by the code generator during design-time. The reason for not wanting this collection to be generated is that child controls are added to this collection when new OutlookBarTab objects are added to the Tabs collection. If the OutlookBar control persisted both the Tabs collection and the Controls collection, this would cause double the references to controls contained with the OutlookBar control.

The design of the OutlookBar control was driven by the functional requirements and expected runtime behavior to be provided. Determining the base class from which they derived came down to two possible choices: ContainerControl or UserControl. Each of these base classes provides the necessary functionality needed by the OutlookBar control. Such functionality includes the capability to host child controls and provide focus management. The decision to use ContainerControl as the base class was the result of realizing that the UserControl class provides no extra functionality required by the OutlookBar control than does the ContainerControl. Therefore, the ContainerControl provided the proper level of support without any additional overhead, as would be introduced by the UserControl base class with its extra level of inheritance.

Among the more interesting implementation parts of the OutlookBar control are the methods SubscribeToTabEvents and UnSubscribeToTabEvents. Using the Reflection API makes it possible to subscribe to all available events without having to know all the events being exposed by the OutlookBarTab component. Whenever the OutlookBarTab raises an event, the OutlookBar control will invalidate itself to reflect the changes in its child components.

Custom Collections

Following the design of the Windows Forms Tab control, the OutlookBar provides a custom collection for the OutlookBarTab objects to be managed by the control. This custom collection provides a means to add new tabs to the OutlookBar at both design-time and runtime. The TabCollection is then serialized during design-time by the code generated. Providing a custom collection requires implementing the ICollection, IEnumerable, and IList interfaces. Tables 8.3 through 8.7 describe the properties and methods for these interfaces.

Table 8.3. Properties of ICollection
Property Description
Count Returns the number of objects within the collection.
IsSynchronized Used to determined whether access to the collection is thread-safe.
SyncRoot Object used to synchronize access to the collection.

Table 8.4. Methods of ICollection
Method Description
CopyTo Copies the current contents of the collection to an array starting at the supplied index.

Table 8.5. Method of IEnumerable
Method Description
GetEnumerator Returns an enumerator that can be used to iterate through the items within a collection.

Table 8.6. Properties of IList
Property Description
IsFixedSize Returns true if the collection size is static or fixed.
IsReadOnly Return true if the collection is read-only in nature.
Item (Indexer) Provides direct access to an item within the collection.

Table 8.7. Methods of IList
Method Description
Add Adds a new item to the collection.
Clear Removes all items from the collection.
Contains Determines whether the collection contains the specified item.
IndexOf Retrieves the index of a specified item.
Insert Inserts an item into the collection at a specified index.
Remove Removes the specified item from the collection.
RemoveAt Removes the item at the specified index.

In addition to the methods required by the implemented interfaces, the TabCollection also implements the method AddRange. The code generator, when adding child components to a parent container, uses the AddRange method. Also, the TabCollection is a nested class within the OutlookBar control itself, because it is dependant on the services provided by the OutlookBar control. As such, the TabCollection class cannot stand on its own because its implementation requires access to protected methods within the OutlookBar control. Listing 8.3 provides the source for the TabCollection implementation.

Listing 8.3 TabCollection
   1:   2:   3: public class TabCollection : ICollection, IEnumerable, IList {   4:   5:     private OutlookBar        owner                = null;   6:     private ArrayList        internalArrayList    = null;   7:   8:   9:     public TabCollection( OutlookBar parent ) {  10:         owner = parent;  11:         internalArrayList = new ArrayList( );  12:     }  13:  14:     //ICollection Interface implementation  15:     public int Count {  16:        get {  return internalArrayList.Count; }  17:     }  18:  19:     public bool IsSynchronized {  20:        get {  return internalArrayList.IsSynchronized; }  21:     }  22:     public object SyncRoot {  23:         get {  return internalArrayList.SyncRoot; }  24:     }  25:  26:     public void CopyTo( Array array, int arrayIndex ) {  27:         internalArrayList.CopyTo( array, arrayIndex );  28:     }  29:  30:     //IList interface implementation  31:     public bool IsFixedSize {  32:         get {  return false; }  33:     }  34:  35:     public bool IsReadOnly {  36:         get {  return false; }  37:     }  38:  39:     //Only used when Interface IList is acquired.  40:     //IList iList = (IList)TabCollection;  41:     object IList.this[ int index ] {  42:         get {  return this[ index ]; }  43:         set {  this[ index ] = (OutlookBarTab)value; }  44:     }  45:  46:     int IList.Add( object o ) {  47:         return this.Add( (OutlookBarTab)o );  48:     }  49:  50:     void IList.Clear( ) {  51:         this.Clear( );  52:     }  53:  54:     public bool Contains( object o ) {  55:         return internalArrayList.Contains( o );  56:     }  57:  58:     public int IndexOf( object o ) {  59:         return internalArrayList.IndexOf( o );  60:     }  61:  62:     void IList.Insert( int index, object o ) {  63:         this.Insert(index, (OutlookBarTab)o );  64:     }  65:  66:     void IList.Remove( object o ) {  67:         this.Remove( (OutlookBarTab)o );  68:     }  69:  70:     void IList.RemoveAt( int index ) {  71:         this.RemoveAt( index );  72:     }  73:  74:     //IEnumerable interface implementation  75:     public IEnumerator GetEnumerator( ) {  76:         return internalArrayList.GetEnumerator( );  77:     }  78:  79:     //TabCollection implementation  80:     public int Add( OutlookBarTab tab ) {  81:         owner.SubscribeToTabEvents( tab );  82:         int idx = internalArrayList.Add( tab );  83:         if( tab.Child != null )  84:            owner.Controls.Add( tab.Child );  85:         owner.ActivateTab( idx );  86:         return idx;  87:     }  88:  89:     public void AddRange( OutlookBarTab[] tabs ) {  90:         foreach( OutlookBarTab T in tabs )  91:            Add( T );  92:     }  93:  94:     public void Remove( OutlookBarTab tab ) {  95:        RemoveAt( IndexOf( tab ) );  96:     }  97:  98:     public void RemoveAt( int index ) {  99:         OutlookBarTab T = this[index]; 100:         owner.Controls.Remove( T.Child ); 101:         internalArrayList.Remove( T ); 102:         owner.UnSubscribeToTabEvents( T ); 103: 104:         if( internalArrayList.Count > 0 ) { 105:            //Was this the active Tab? 106:            if( index == owner.activeTabIndex ) { 107:                index++; index %=( Count+1 ); 108:            } 109:         } 110:         owner.Invalidate( ); 111:     } 112: 113:     public void Insert( int index, OutlookBarTab tab ) { 114:         internalArrayList.Insert( index, tab ); 115:         owner.SubscribeToTabEvents( tab ); 116:         //is the new tab before or after the current tab? 117: if( index <= owner.activeTabIndex ) 118:             owner.activeTabIndex++; 119:         owner.Invalidate( ); 120:      } 121: 122:      public void Clear( ) { 123:         foreach( OutlookBarTab tab in internalArrayList ) 124:             owner.UnSubscribeToTabEvents( tab ); 125: 126:       owner.Controls.Clear( ); 127:       internalArrayList.Clear( ); 128:       owner.activeTabIndex = -1; 129:       owner.Invalidate( ); 130:     } 131: 132:     public OutlookBarTab this[ int index ] { 133:         get { 134:             // The CollectionEditor is famous for passing 135:             // the index value of -1. For what reason, I have no idea. 136:             try { 137:                return (OutlookBarTab)internalArrayList[index]; 138:             }  catch( Exception e ) { 139:                 System.Diagnostics.Debug.WriteLine( e.Message ); 140:    System.Diagnostics.Debug.WriteLine( string.Format( 141:                            "TabCollection[{ 0} ] invalid index", index ) ); 142:             } 143:           return null; 144:         } 145:         set { 146:            internalArrayList[index] = value; 147:         } 148:     } 149: } 

The TabCollection represents a fairly standard collection implementation. All items for the collection are stored in an internal ArrayList. The reason behind developing a custom collection is that there's a need to interact with the OutlookBar control. When a new OutlookBarTab component is added to the collection, it is necessary to add the OutlookBarTabs child control to the Controls collection of the OutlookBar control. Again, the reason for using a nested class to provide the collection is that it allows the collection to access protected methods of the parent class.

Testing the Control

With the collection in place, testing the control is now possible. To test the OutlookBar control, comment out the Designer attribute on the OutlookBar class. Otherwise, VS .NET will not be able to support the control at design-time due to the incapability to locate the designer for it. After all, the designer has not yet been implemented. At this point, create a new Windows Forms application project and add the OutlookBar control to the toolbox. Draw an OutlookBar control on the main form and use the Tabs property to add three OutlookBarTab objects to the control, as shown in Figure 8.2.

Figure 8.2. Testing the OutlookBar control.

figure 8.2. testing the outlookbar control.

With the three tabs in place, compile and run the test application. The activation of tabs is fully working at this point. Of course, the goal of the OutlookBar control is to host other controls and associate those child controls with the various tabs within the control. Although child controls can be added by hand-coding the relationships between the controls and the tab that will host them, a custom designer is required to support this behavior during design-time. The next section discusses the design and implementation of a custom designer for the OutlookBar control.

OutlookBarDesigner

The goal of a control designer is to allow developers to customize the appearance of a control, add child controls, and link event code to the events of a control. The rule of thumb when creating designers is to keep the designer simple and to the point. If you complicate control designs, developers won't want to use your controls. The OutlookBar designer adds support for drag-and-drop during design-time, as well as the capability to activate the hosted tabs within the control similar to the Windows Forms TabControl.

The OutlookBarDesigner requires the use of four services provided by the VS .NET IDE. These services include IDesignerHost, IToolboxService, ISelectionService, and IComponentChangeService. Each of these interfaces was covered in Chapter 5, "Advanced Control Development"; only the relevant methods for these services are covered here.

The IDesignerHost interface is a gateway, so to speak. This interface provides access to the underlying services provided by the root designer. Again, the root designer is the main designer hosted by the VS .NET IDE. For the most part, the IDesignerHost interface is used to obtain services from the root designer. These services include IToolboxService and ISelectionService.

As with other RAD environments, the toolbox is a palette of controls that can be drawn or dragged onto a form. The IToolboxService provides methods for deserializing and creating the controls within the toolbox.

Changing the active selection the control currently selected in design mode requires the use of the ISelectionService interface. This service not only can be used to determine which control or controls are currently selected, but also provides the capability to change the current selection.

The OutlookBar designer will be responsible for responding to drag-and-drop notifications, allowing activation of the contained OutlookBarTabs, and removing unused and unwanted properties from the OutlookBar control. Listing 8.4 provides the source for the OutlookBar designer.

Listing 8.4 Implementing the OutlookBar Designer
   1: using System;   2: using System.Collections;   3: using System.ComponentModel;   4: using System.ComponentModel.Design;   5: using System.Drawing;   6: using System.Drawing.Design;   7: using System.Windows.Forms;   8: using System.Windows.Forms.Design;   9: using SAMS.ToolKit.Controls;  10:  11: namespace SAMS.ToolKit.Design  12: {  13:  14:     public class OutlookBarDesigner :  graphics/ccc.gifSystem.Windows.Forms.Design.ParentControlDesigner {  15:  16:         private IDesignerHost        designerHost         = null;  17:         private IToolboxService      toolboxService       = null;  18:         private ISelectionService    selectionService     = null;  19:  20:         public override ICollection AssociatedComponents {  21:             get {  22:                 if( base.Control is OutlookBar )  23:                     return ((OutlookBar)base.Control).Tabs;  24:                 else  25:                     return base.AssociatedComponents;  26:             }  27:         }  28:         public IDesignerHost DesignerHost {  29:             get {  30:                 if( designerHost == null )  31:                     designerHost = (IDesignerHost)GetService( typeof( IDesignerHost  graphics/ccc.gif) );  32:                 return designerHost;  33:             }  34:         }  35:  36:         public IToolboxService ToolboxService     {  37:             get {  38:                 if( toolboxService == null )  39:                     toolboxService = (IToolboxService)GetService( typeof(  graphics/ccc.gifIToolboxService ) );  40:                 return toolboxService;  41:             }  42:         }  43:  44:         public ISelectionService SelectionService {  45:             get {  46:                 if( selectionService == null )  47:                     selectionService = (ISelectionService)GetService( typeof(  graphics/ccc.gifISelectionService ) );  48:                 return selectionService;  49:             }  50:         }  51:  52:         protected override bool DrawGrid {  53:             get {  return false; }  54:         }  55:  56:         public override bool CanParent( System.Windows.Forms.Control control ) {  57:             return true;  58:         }  59:  60:         protected override void PostFilterProperties( IDictionary Properties ) {  61:  62:             base.PostFilterProperties( Properties );  63:  64:             string[] props = {  "BackColor", "AutoScroll",  65:                                "AutoScrollMargin", "AutoScrollMinSize",  66:                                "BackgroundImage" } ;  67:  68:             foreach( string property in props )  69:                 Properties.Remove( property );  70:  71:         }  72:  73:  74:         protected override void OnDragDrop( DragEventArgs de ) {  75:  76:             ToolboxItem toolboxItem = this.ToolboxService.DeserializeToolboxItem(  graphics/ccc.gifde.Data, this.DesignerHost );  77:  78:             IComponent[] components = toolboxItem.CreateComponents(  graphics/ccc.gifthis.DesignerHost );  79:  80:             OutlookBar outlookBar = (OutlookBar)this.Control;  81:  82:             Point pt = outlookBar.PointToClient( new Point( de.X, de.Y ) );  83:  84:             int TabIndex;  85:             if( outlookBar.HitTest( HitTestType.CLIENT, pt, out TabIndex ) ) {  86:                 //Replace the current control of the active tab  87:                 // with the newly dropped control  88:                 if( outlookBar.ActiveTab == null ) {  89:                     AddTab( outlookBar, (Control)components[0] );  90:                 }  else {  91:                     if( outlookBar.ActiveTab.Child != null )  92:                         this.DesignerHost.DestroyComponent(  graphics/ccc.gifoutlookBar.ActiveTab.Child );  93:  94:                     Control old = outlookBar.ActiveTab.Child;  95:                     outlookBar.ActiveTab.Child = (Control)components[0];  96:                     RaiseComponentChanged( TypeDescriptor.GetProperties(  graphics/ccc.gifoutlookBar.ActiveTab )["Child"], old, outlookBar.ActiveTab.Child );  97:                 }  98:             }  else {  99:                 AddTab( outlookBar, (Control)components[0] ); 100:             } 101: 102:             //Set the Selection to the newly dropped control 103:             this.SelectionService.SetSelectedComponents( components ); 104: 105:             //Inform the Toolbox that the selected item was used; this will reset  graphics/ccc.gifthe mouse to the pointer 106:             this.ToolboxService.SelectedToolboxItemUsed( ); 107:         } 108: 109:         protected virtual void AddTab( OutlookBar outlookBar, Control control ) { 110:             //The control was dropped on the Tabs, so 111:             // create a new Tab to host the control 112:             OutlookBar.TabCollection oldTabs = outlookBar.Tabs; 113:             OutlookBarTab T = (OutlookBarTab)this.DesignerHost. CreateComponent(  graphics/ccc.giftypeof( OutlookBarTab ) ); 114:             T.Text    = string.Format( "outlookBarTab{ 0} ",  oldTabs.Count + 1); 115:             T.Child = control; 116:             outlookBar.Tabs.Add( T ); 117: 118:             //Notify the system that the OutlookBar Component is 119:             // changing.  This is necessary for proper serialization of the Tabs  graphics/ccc.gifcollection 120:             this.RaiseComponentChanged( TypeDescriptor.GetProperties( outlookBar  graphics/ccc.gif)["Tabs"], outlookBar.Tabs, oldTabs ); 121:         } 122: 123: 124:         protected override void WndProc( ref Message msg ) { 125:             base.WndProc( ref msg ); 126:             if( msg.Msg == 0x00000201 ) {     //left mouse down 127:                 OutlookBar outlookBar = this.SelectionService.PrimarySelection as  graphics/ccc.gifOutlookBar; 128:                 if( outlookBar != null ) { 129:                     int tabIndex; 130:                     if( outlookBar.HitTest( HitTestType.TABS, new Point(  graphics/ccc.gifmsg.LParam.ToInt32( ) ), out tabIndex ) ) 131:  outlookBar.ActiveTabIndex = tabIndex; 132:                 } 133:             } 134:         } 135: 136:         private void OnComponentRemoving( object sender, ComponentEventArgs e ) { 137: 138:             //What is being removed? 139:             if( e.Component is OutlookBarTab ) { 140:                 OutlookBarTab tab = (OutlookBarTab)e.Component; 141: 142: 143:                 //Does the Tab belong to the Current Control? 144:                 if( ((OutlookBar)Control).Tabs.Contains( tab ) ) { 145:                     ((OutlookBar)Control).Tabs.Remove( tab ); 146: 147:                     if( tab.Child != null ) 148:                         DesignerHost.DestroyComponent( tab.Child ); 149:                 } 150: 151:             }  else if( e.Component is OutlookBar ) { 152:                 //Destroy all tabs 153:                 OutlookBar bar = (OutlookBar)e.Component; 154:                 int tabCount = bar.Tabs.Count; 155:                 for( int index = 0; index < tabCount; index++ ) 156:  DesignerHost.DestroyComponent( bar.Tabs[0] ); 157: 158:             }  else if( e.Component is Control ) { 159:                 Control control = (Control)e.Component; 160:                 OutlookBar bar = (OutlookBar)Control; 161:                 //Do we own the Control? 162:                 if( bar.Controls.Contains( control ) ) { 163:                     //Find the Tab that owns the control 164:                     foreach( OutlookBarTab tab in bar.Tabs ) { 165:                         if( tab.Child == control ) { 166:                             //Found IT! 167:                             tab.Child = null; 168:                             bar.Controls.Remove( control ); 169:                             break; 170:                         } 171:                     } 172:                 } 173:             } 174:         } 175: 176:         public override void Initialize( IComponent component ) { 177:             base.Initialize( component ); 178:             IComponentChangeService changeService =  graphics/ccc.gif(IComponentChangeService)GetService( typeof(IComponentChangeService) ); 179:             changeService.ComponentRemoving += new ComponentEventHandler(  graphics/ccc.gifOnComponentRemoving ); 180:         } 181: 182:  public override void Dispose( ) { 183:             IComponentChangeService changeService =  graphics/ccc.gif(IComponentChangeService)GetService( typeof(IComponentChangeService) ); 184:             changeService.ComponentRemoving -= new ComponentEventHandler(  graphics/ccc.gifOnComponentRemoving ); 185:             base.Dispose( ); 186:         } 187: 188:         public OutlookBarDesigner() { 189:         } 190:  } 191: } 

Designers represent a critical element of the control development process. Not only do designers provide for the design-time experience, but they also are part of the serialization process for the code generated for the control.

Dissecting the designer requires looking at the various interfaces and services that the designer uses. Each of the next sections covers a particular aspect of the designers' implementation and services used.

IComponentChangeService

When a control changes during design-time, the code generated, as well as the root designer, needs to be informed of the changes to the control. This allows the designer to make any necessary modifications to the control it is responsible for. In the case of the OutlookBarDesigner, when a child control is added or removed, the designer needs to know if the control belonged to the OutlookBar control, and if so, it updates the OutlookBar control. This is also the case when an OutlookBarTab component is removed. When an OutlookBarTab is removed, the designer needs to remove the child control of the OutlookBarTab from both the root designer and the OutlookBar control itself.

The IComponentChangeService facilitates this notification process. Table 8.8 shows the events provided by the IComponentChangeService interface.

Table 8.8. IComponentChangeService Events
Event Description
ComponentAdded Raised when a component is added to the designer during design-time.
ComponentAdding Raised when a component is in the process of being added.
ComponentChanged Raised when a component has changed.
ComponentChanging Raised when a component is in the process of being changed.
ComponentRemoved Raised when a component has been removed using the IDesignerHost.DestroyComponent method.
ComponentRemoving Raised when a component is in the process of being removed.

To ensure proper serialization of the OutlookBar control and its associated tabs and child controls, it is necessary to respond when a component is removed from the OutlookBar control. In addition, if the component being removed is the OutlookBar control being designed, all child components need to be destroyed to avoid having orphan components and controls. By subscribing to the events provided by the IComponentChangeService, a designer can properly track the changes within the design-time environment and update the control it is responsible for.

Drag-and-Drop

Support for drag-and-drop functionality is a snap, thanks to the services provided by the .NET base class library. Line 74 of Listing 8.4 provides the overridden method OnDragDrop. The DragEventArgs provides all the necessary information to determine the location of the drop and the currently selected toolbox item. Using this information, the toolbox item can be created using the IToolboxService interface and the control added to the OutlookBar control.

During the processing of the drop notification, the TabCollection of the OutlookBar control is modified, and it is necessary to inform the root-level designer that a property of the currently selected component has been modified. Notification is necessary to ensure that the control will be properly serialized in code during code generation.

The RaiseComponentChanged method is used to notify the root-level designer and code generator about the changes taking place.

Overriding WndProc

To allow for design-time activation of the hosted tabs, the WndProc method is overridden so that processing of the mouse messages can take place. Notice the use of the constant value 0x00000201 for testing the Windows message. This constant value is the raw value for the WM_LBUTTONDOWN message. The class NativeMethods found in the System.Windows.Forms namespace provides a static member name for this message; however, the class is private! Not to worry, however; using ILDASM, these constant values can be retrieved. Figure 8.3 shows ILDASM with the NativeMethods class selected and the value for WM_LBUTTONDOWN being displayed.

Figure 8.3. Spying with ILDASM.

figure 8.3. spying with ildasm.



    .NET Windows Forms Custom Controls
    User Interfaces in VB .NET: Windows Forms and Custom Controls
    ISBN: 1590590449
    EAN: 2147483647
    Year: 2002
    Pages: 74

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net