1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2005 Novell, Inc.
23 // Jackson Harper (jackson@ximian.com)
27 using System.Collections;
28 using System.ComponentModel;
29 using System.ComponentModel.Design;
31 using System.Runtime.InteropServices;
32 using System.Windows.Forms.Theming;
33 using System.Windows.Forms.VisualStyles;
35 namespace System.Windows.Forms {
37 [ComVisibleAttribute (true)]
38 [ClassInterfaceAttribute (ClassInterfaceType.AutoDispatch)]
40 [DefaultEvent("SelectedIndexChanged")]
41 [DefaultProperty("TabPages")]
42 [Designer("System.Windows.Forms.Design.TabControlDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
43 public class TabControl : Control {
45 private int selected_index = -1;
46 private TabAlignment alignment;
47 private TabAppearance appearance;
48 private TabDrawMode draw_mode;
49 private bool multiline;
50 private ImageList image_list;
51 private Size item_size = Size.Empty;
52 private Point padding;
53 private int row_count = 0;
54 private bool hottrack;
55 private TabPageCollection tab_pages;
56 private bool show_tool_tips;
57 private TabSizeMode size_mode;
58 private bool show_slider = false;
59 private PushButtonState right_slider_state = PushButtonState.Normal;
60 private PushButtonState left_slider_state = PushButtonState.Normal;
61 private int slider_pos = 0;
62 TabPage entered_tab_page;
63 bool mouse_down_on_a_tab_page;
65 private bool rightToLeftLayout;
69 #region UIA Framework Events
71 static object UIAHorizontallyScrollableChangedEvent = new object ();
73 internal event EventHandler UIAHorizontallyScrollableChanged {
74 add { Events.AddHandler (UIAHorizontallyScrollableChangedEvent, value); }
75 remove { Events.RemoveHandler (UIAHorizontallyScrollableChangedEvent, value); }
78 internal void OnUIAHorizontallyScrollableChanged (EventArgs e)
81 = (EventHandler) Events [UIAHorizontallyScrollableChangedEvent];
86 static object UIAHorizontallyScrolledEvent = new object ();
88 internal event EventHandler UIAHorizontallyScrolled {
89 add { Events.AddHandler (UIAHorizontallyScrolledEvent, value); }
90 remove { Events.RemoveHandler (UIAHorizontallyScrolledEvent, value); }
93 internal void OnUIAHorizontallyScrolled (EventArgs e)
96 = (EventHandler) Events [UIAHorizontallyScrolledEvent];
103 #region UIA Framework Property
105 internal double UIAHorizontalViewSize {
106 get { return LeftScrollButtonArea.Left * 100 / TabPages [TabCount - 1].TabBounds.Right; }
111 #region Public Constructors
114 tab_pages = new TabPageCollection (this);
115 SetStyle (ControlStyles.UserPaint, false);
116 padding = ThemeEngine.Current.TabControlDefaultPadding;
117 item_size = ThemeEngine.Current.TabControlDefaultItemSize;
119 MouseDown += new MouseEventHandler (MouseDownHandler);
120 MouseLeave += new EventHandler (OnMouseLeave);
121 MouseMove += new MouseEventHandler (OnMouseMove);
122 MouseUp += new MouseEventHandler (MouseUpHandler);
123 SizeChanged += new EventHandler (SizeChangedHandler);
126 #endregion // Public Constructors
128 #region Public Instance Properties
129 [DefaultValue(TabAlignment.Top)]
131 [RefreshProperties(RefreshProperties.All)]
132 public TabAlignment Alignment {
133 get { return alignment; }
135 if (alignment == value)
138 if (alignment == TabAlignment.Left || alignment == TabAlignment.Right)
144 [DefaultValue(TabAppearance.Normal)]
146 public TabAppearance Appearance {
147 get { return appearance; }
149 if (appearance == value)
157 [EditorBrowsable(EditorBrowsableState.Never)]
158 public override Color BackColor {
159 get { return ThemeEngine.Current.ColorControl; }
160 set { /* nothing happens on set on MS */ }
164 [EditorBrowsable(EditorBrowsableState.Never)]
165 public override Image BackgroundImage {
166 get { return base.BackgroundImage; }
167 set { base.BackgroundImage = value; }
172 [EditorBrowsable (EditorBrowsableState.Never)]
173 public override ImageLayout BackgroundImageLayout {
174 get { return base.BackgroundImageLayout; }
175 set { base.BackgroundImageLayout = value; }
179 public override Rectangle DisplayRectangle {
181 return ThemeEngine.Current.TabControlGetDisplayRectangle (this);
186 [EditorBrowsable (EditorBrowsableState.Never)]
187 protected override bool DoubleBuffered {
188 get { return base.DoubleBuffered; }
189 set { base.DoubleBuffered = value; }
193 [DefaultValue(TabDrawMode.Normal)]
194 public TabDrawMode DrawMode {
195 get { return draw_mode; }
197 if (draw_mode == value)
205 [EditorBrowsable(EditorBrowsableState.Never)]
206 public override Color ForeColor {
207 get { return base.ForeColor; }
208 set { base.ForeColor = value; }
211 [DefaultValue(false)]
212 public bool HotTrack {
213 get { return hottrack; }
215 if (hottrack == value)
223 [RefreshProperties (RefreshProperties.Repaint)]
226 public ImageList ImageList {
227 get { return image_list; }
228 set { image_list = value; }
232 public Size ItemSize {
237 if (value.Height < 0 || value.Width < 0)
238 throw new ArgumentException ("'" + value + "' is not a valid value for 'ItemSize'.");
244 [DefaultValue(false)]
245 public bool Multiline {
246 get { return multiline; }
248 if (multiline == value)
251 if (!multiline && alignment == TabAlignment.Left || alignment == TabAlignment.Right)
252 alignment = TabAlignment.Top;
263 get { return padding; }
265 if (value.X < 0 || value.Y < 0)
266 throw new ArgumentException ("'" + value + "' is not a valid value for 'Padding'.");
267 if (padding == value)
276 [MonoTODO ("RTL not supported")]
278 [DefaultValue (false)]
279 public virtual bool RightToLeftLayout {
280 get { return this.rightToLeftLayout; }
282 if (value != this.rightToLeftLayout) {
283 this.rightToLeftLayout = value;
284 this.OnRightToLeftLayoutChanged (EventArgs.Empty);
291 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
292 public int RowCount {
293 get { return row_count; }
298 public int SelectedIndex {
299 get { return selected_index; }
304 throw new ArgumentOutOfRangeException ("SelectedIndex", "Value of '" + value + "' is valid for 'SelectedIndex'. " +
305 "'SelectedIndex' must be greater than or equal to -1.");
307 throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
308 "'value' must be greater than or equal to -1.");
311 if (!this.IsHandleCreated) {
312 if (selected_index != value) {
313 selected_index = value;
315 OnSelectedIndexChanged (EventArgs.Empty);
321 if (value >= TabCount) {
322 if (value != selected_index)
323 OnSelectedIndexChanged (EventArgs.Empty);
327 if (value == selected_index) {
328 if (selected_index > -1)
329 Invalidate(GetTabRect (selected_index));
334 TabControlCancelEventArgs ret = new TabControlCancelEventArgs (SelectedTab, selected_index, false, TabControlAction.Deselecting);
341 int old_index = selected_index;
342 int new_index = value;
344 selected_index = new_index;
347 ret = new TabControlCancelEventArgs (SelectedTab, selected_index, false, TabControlAction.Selecting);
350 selected_index = old_index;
357 Rectangle invalid = Rectangle.Empty;
358 bool refresh = false;
360 if (new_index != -1 && show_slider && new_index < slider_pos) {
361 slider_pos = new_index;
365 if (new_index != -1) {
366 int le = TabPages[new_index].TabBounds.Right;
367 int re = LeftScrollButtonArea.Left;
368 if (show_slider && le > re) {
370 for (i = 0; i < new_index - 1; i++) {
371 if (TabPages [i].TabBounds.Left < 0) // tab scrolled off the visible area, ignore
374 if (TabPages [new_index].TabBounds.Right - TabPages[i].TabBounds.Right < re) {
384 if (old_index != -1 && new_index != -1) {
386 invalid = GetTabRect (old_index);
387 ((TabPage) Controls[old_index]).SetVisible (false);
390 TabPage selected = null;
392 if (new_index != -1) {
393 selected = (TabPage) Controls[new_index];
394 invalid = Rectangle.Union (invalid, GetTabRect (new_index));
395 selected.SetVisible (true);
398 OnSelectedIndexChanged (EventArgs.Empty);
405 } else if (new_index != -1 && selected.Row != BottomRow) {
406 DropRow (TabPages[new_index].Row);
407 // calculating what to invalidate here seems to be slower then just
408 // refreshing the whole thing
413 // The lines are drawn on the edges of the tabs so the invalid area should
414 // needs to include the extra pixels of line width (but should not
415 // overflow the control bounds).
416 if (appearance == TabAppearance.Normal) {
417 invalid.Inflate (6, 4);
418 invalid.Intersect (ClientRectangle);
420 Invalidate (invalid);
426 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
427 public TabPage SelectedTab {
429 if (selected_index == -1)
431 return tab_pages [selected_index];
434 int index = IndexForTabPage (value);
435 if (index == selected_index)
437 SelectedIndex = index;
441 [DefaultValue(false)]
443 public bool ShowToolTips {
444 get { return show_tool_tips; }
446 if (show_tool_tips == value)
448 show_tool_tips = value;
453 [DefaultValue(TabSizeMode.Normal)]
454 [RefreshProperties(RefreshProperties.Repaint)]
455 public TabSizeMode SizeMode {
456 get { return size_mode; }
458 if (size_mode == value)
466 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
467 public int TabCount {
469 return tab_pages.Count;
474 [Editor ("System.Windows.Forms.Design.TabPageCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
478 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
479 [MergableProperty(false)]
480 public TabPageCollection TabPages {
481 get { return tab_pages; }
486 [EditorBrowsable(EditorBrowsableState.Never)]
487 public override string Text {
488 get { return base.Text; }
489 set { base.Text = value; }
491 #endregion // Public Instance Properties
493 #region Internal Properties
494 internal bool ShowSlider {
495 get { return show_slider; }
500 // UIA Framework Event: HorizontallyScrollable Changed
501 OnUIAHorizontallyScrollableChanged (EventArgs.Empty);
506 internal int SliderPos {
507 get { return slider_pos; }
510 internal PushButtonState RightSliderState {
511 get { return right_slider_state; }
513 if (right_slider_state == value)
515 PushButtonState old_value = right_slider_state;
516 right_slider_state = value;
517 if (NeedsToInvalidateScrollButton (old_value, value))
518 Invalidate (RightScrollButtonArea);
522 internal PushButtonState LeftSliderState {
523 get { return left_slider_state; }
525 if (left_slider_state == value)
527 PushButtonState old_value = left_slider_state;
528 left_slider_state = value;
529 if (NeedsToInvalidateScrollButton (old_value, value))
530 Invalidate (LeftScrollButtonArea);
534 bool NeedsToInvalidateScrollButton (PushButtonState oldState, PushButtonState newState)
536 if ((oldState == PushButtonState.Hot && newState == PushButtonState.Normal) ||
537 (oldState == PushButtonState.Normal && newState == PushButtonState.Hot))
538 return HasHotElementStyles;
542 internal TabPage EnteredTabPage {
543 get { return entered_tab_page; }
545 if (entered_tab_page == value)
547 if (HasHotElementStyles) {
548 Region area_to_invalidate = new Region ();
549 area_to_invalidate.MakeEmpty ();
550 if (entered_tab_page != null)
551 area_to_invalidate.Union (entered_tab_page.TabBounds);
552 entered_tab_page = value;
553 if (entered_tab_page != null)
554 area_to_invalidate.Union (entered_tab_page.TabBounds);
555 Invalidate (area_to_invalidate);
556 area_to_invalidate.Dispose ();
558 entered_tab_page = value;
561 #endregion // Internal Properties
563 #region Protected Instance Properties
564 protected override CreateParams CreateParams {
566 CreateParams c = base.CreateParams;
571 protected override Size DefaultSize {
572 get { return new Size (200, 100); }
575 #endregion // Protected Instance Properties
577 #region Public Instance Methods
578 public Rectangle GetTabRect (int index)
580 TabPage page = GetTab (index);
581 return page.TabBounds;
584 public Control GetControl (int index)
586 return GetTab (index);
590 public void SelectTab (TabPage tabPage)
593 throw new ArgumentNullException ("tabPage");
595 SelectTab (this.tab_pages [tabPage]);
598 public void SelectTab (string tabPageName)
600 if (tabPageName == null)
601 throw new ArgumentNullException ("tabPageName");
603 SelectTab (this.tab_pages [tabPageName]);
606 public void SelectTab (int index)
608 if (index < 0 || index > this.tab_pages.Count - 1)
609 throw new ArgumentOutOfRangeException ("index");
611 SelectedIndex = index;
614 public void DeselectTab (TabPage tabPage)
617 throw new ArgumentNullException ("tabPage");
619 DeselectTab (this.tab_pages [tabPage]);
622 public void DeselectTab (string tabPageName)
624 if (tabPageName == null)
625 throw new ArgumentNullException ("tabPageName");
627 DeselectTab (this.tab_pages [tabPageName]);
630 public void DeselectTab (int index)
632 if (index == SelectedIndex) {
633 if (index >= 0 && index < this.tab_pages.Count - 1)
634 SelectedIndex = ++index;
642 public override string ToString ()
644 string res = String.Concat (base.ToString (),
645 ", TabPages.Count: ",
648 res = String.Concat (res, ", TabPages[0]: ",
653 #endregion // Public Instance Methods
655 #region Protected Instance Methods
658 protected override Control.ControlCollection CreateControlsInstance ()
660 return new TabControl.ControlCollection (this);
663 protected override void CreateHandle ()
665 base.CreateHandle ();
666 selected_index = (selected_index >= TabCount ? (TabCount > 0 ? 0 : -1) : selected_index);
669 if (selected_index > -1)
670 this.SelectedTab.SetVisible(true);
672 tab_pages[0].SetVisible(true);
677 protected override void OnHandleCreated (EventArgs e)
679 base.OnHandleCreated (e);
682 protected override void OnHandleDestroyed (EventArgs e)
684 base.OnHandleDestroyed (e);
687 protected override void Dispose (bool disposing)
689 base.Dispose (disposing);
695 protected virtual void OnDrawItem (DrawItemEventArgs e)
697 if (DrawMode != TabDrawMode.OwnerDrawFixed)
700 DrawItemEventHandler eh = (DrawItemEventHandler)(Events [DrawItemEvent]);
705 internal void OnDrawItemInternal (DrawItemEventArgs e)
710 protected override void OnFontChanged (EventArgs e)
712 base.OnFontChanged (e);
716 protected override void OnResize (EventArgs e)
721 protected override void OnStyleChanged (EventArgs e)
723 base.OnStyleChanged (e);
726 protected virtual void OnSelectedIndexChanged (EventArgs e)
728 EventHandler eh = (EventHandler) (Events[SelectedIndexChangedEvent]);
733 internal override void OnPaintInternal (PaintEventArgs pe)
735 if (GetStyle (ControlStyles.UserPaint))
738 Draw (pe.Graphics, pe.ClipRectangle);
743 protected override void OnEnter (EventArgs e)
746 if (SelectedTab != null)
747 SelectedTab.FireEnter ();
750 protected override void OnLeave (EventArgs e)
752 if (SelectedTab != null)
753 SelectedTab.FireLeave ();
757 [EditorBrowsable (EditorBrowsableState.Advanced)]
758 protected virtual void OnRightToLeftLayoutChanged (EventArgs e)
760 EventHandler eh = (EventHandler) (Events[RightToLeftLayoutChangedEvent]);
765 [EditorBrowsable (EditorBrowsableState.Never)]
766 protected override void ScaleCore (float dx, float dy)
768 base.ScaleCore (dx, dy);
771 protected virtual void OnDeselecting (TabControlCancelEventArgs e)
773 TabControlCancelEventHandler eh = (TabControlCancelEventHandler) (Events[DeselectingEvent]);
778 OnDeselected (new TabControlEventArgs (SelectedTab, selected_index, TabControlAction.Deselected));
781 protected virtual void OnDeselected (TabControlEventArgs e)
783 TabControlEventHandler eh = (TabControlEventHandler) (Events[DeselectedEvent]);
787 if (this.SelectedTab != null)
788 this.SelectedTab.FireLeave ();
791 protected virtual void OnSelecting (TabControlCancelEventArgs e)
793 TabControlCancelEventHandler eh = (TabControlCancelEventHandler) (Events[SelectingEvent]);
798 OnSelected (new TabControlEventArgs (SelectedTab, selected_index, TabControlAction.Selected));
801 protected virtual void OnSelected (TabControlEventArgs e)
803 TabControlEventHandler eh = (TabControlEventHandler) (Events[SelectedEvent]);
807 if (this.SelectedTab != null)
808 this.SelectedTab.FireEnter ();
815 protected override bool ProcessKeyPreview (ref Message m)
817 return base.ProcessKeyPreview (ref m);
820 protected override void OnKeyDown (KeyEventArgs ke)
822 if (ke.KeyCode == Keys.Tab && (ke.KeyData & Keys.Control) != 0) {
823 if ((ke.KeyData & Keys.Shift) == 0)
824 SelectedIndex = (SelectedIndex + 1) % TabCount;
826 SelectedIndex = (SelectedIndex + TabCount - 1) % TabCount;
828 } else if (ke.KeyCode == Keys.Home) {
831 } else if (ke.KeyCode == Keys.End) {
832 SelectedIndex = TabCount - 1;
834 } else if (NavigateTabs (ke.KeyCode))
840 protected override bool IsInputKey (Keys keyData)
842 switch (keyData & Keys.KeyCode) {
851 return base.IsInputKey (keyData);
854 private bool NavigateTabs (Keys keycode)
856 bool move_left = false;
857 bool move_right = false;
859 if (alignment == TabAlignment.Bottom || alignment == TabAlignment.Top) {
860 if (keycode == Keys.Left)
862 else if (keycode == Keys.Right)
865 if (keycode == Keys.Up)
867 else if (keycode == Keys.Down)
872 if (SelectedIndex > 0) {
879 if (SelectedIndex < TabCount - 1) {
889 #region Pages Collection
890 protected void RemoveAll ()
895 protected virtual object [] GetItems ()
897 TabPage [] pages = new TabPage [Controls.Count];
898 Controls.CopyTo (pages, 0);
902 protected virtual object [] GetItems (Type baseType)
904 object[] pages = (object[])Array.CreateInstance (baseType, Controls.Count);
905 Controls.CopyTo (pages, 0);
911 protected void UpdateTabSelection (bool updateFocus)
913 protected void UpdateTabSelection (bool uiselected)
919 protected string GetToolTipText (object item)
921 TabPage page = (TabPage) item;
922 return page.ToolTipText;
925 protected override void WndProc (ref Message m)
927 switch ((Msg)m.Msg) {
928 case Msg.WM_SETFOCUS:
929 if (selected_index == -1 && this.TabCount > 0)
930 this.SelectedIndex = 0;
931 if (selected_index != -1)
932 Invalidate(GetTabRect(selected_index));
933 base.WndProc (ref m);
935 case Msg.WM_KILLFOCUS:
936 if (selected_index != -1)
937 Invalidate(GetTabRect(selected_index));
938 base.WndProc (ref m);
941 base.WndProc (ref m);
946 #endregion // Protected Instance Methods
948 #region Internal & Private Methods
949 private bool CanScrollRight {
951 return (slider_pos < TabCount - 1);
955 private bool CanScrollLeft {
956 get { return slider_pos > 0; }
959 private void MouseDownHandler (object sender, MouseEventArgs e)
961 if ((e.Button & MouseButtons.Left) == 0)
965 Rectangle right = RightScrollButtonArea;
966 Rectangle left = LeftScrollButtonArea;
967 if (right.Contains (e.X, e.Y)) {
968 right_slider_state = PushButtonState.Pressed;
969 if (CanScrollRight) {
974 // UIA Framework Event: Horizontally Scrolled
975 OnUIAHorizontallyScrolled (EventArgs.Empty);
978 switch (this.Alignment) {
979 case TabAlignment.Top:
980 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
982 case TabAlignment.Bottom:
983 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
985 case TabAlignment.Left:
986 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
988 case TabAlignment.Right:
989 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
997 } else if (left.Contains (e.X, e.Y)) {
998 left_slider_state = PushButtonState.Pressed;
1004 // UIA Framework Event: Horizontally Scrolled
1005 OnUIAHorizontallyScrolled (EventArgs.Empty);
1008 switch (this.Alignment) {
1009 case TabAlignment.Top:
1010 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
1012 case TabAlignment.Bottom:
1013 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
1015 case TabAlignment.Left:
1016 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
1018 case TabAlignment.Right:
1019 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
1029 int count = Controls.Count;
1030 for (int i = SliderPos; i < count; i++) {
1031 if (!GetTabRect (i).Contains (e.X, e.Y))
1034 mouse_down_on_a_tab_page = true;
1039 private void MouseUpHandler (object sender, MouseEventArgs e)
1041 mouse_down_on_a_tab_page = false;
1042 if (ShowSlider && (left_slider_state == PushButtonState.Pressed || right_slider_state == PushButtonState.Pressed)) {
1044 if (left_slider_state == PushButtonState.Pressed) {
1045 invalid = LeftScrollButtonArea;
1046 left_slider_state = GetScrollButtonState (invalid, e.Location);
1048 invalid = RightScrollButtonArea;
1049 right_slider_state = GetScrollButtonState (invalid, e.Location);
1051 Invalidate (invalid);
1055 bool HasHotElementStyles {
1057 return ThemeElements.CurrentTheme.TabControlPainter.HasHotElementStyles (this);
1061 Rectangle LeftScrollButtonArea {
1063 return ThemeElements.CurrentTheme.TabControlPainter.GetLeftScrollRect (this);
1067 Rectangle RightScrollButtonArea {
1069 return ThemeElements.CurrentTheme.TabControlPainter.GetRightScrollRect (this);
1073 static PushButtonState GetScrollButtonState (Rectangle scrollButtonArea, Point cursorLocation)
1075 return scrollButtonArea.Contains (cursorLocation) ? PushButtonState.Hot : PushButtonState.Normal;
1078 private void SizeChangedHandler (object sender, EventArgs e)
1083 internal int IndexForTabPage (TabPage page)
1085 for (int i = 0; i < tab_pages.Count; i++) {
1086 if (page == tab_pages [i])
1092 private void ResizeTabPages ()
1096 Rectangle r = DisplayRectangle;
1097 foreach (TabPage page in Controls) {
1102 private int MinimumTabWidth {
1104 return ThemeEngine.Current.TabControlMinimumTabWidth;
1108 private Size TabSpacing {
1110 return ThemeEngine.Current.TabControlGetSpacing (this);
1114 private void CalcTabRows ()
1116 switch (Alignment) {
1117 case TabAlignment.Right:
1118 case TabAlignment.Left:
1119 CalcTabRows (Height);
1122 CalcTabRows (Width);
1127 private void CalcTabRows (int row_width)
1131 Size spacing = TabSpacing;
1133 if (TabPages.Count > 0)
1135 show_slider = false;
1137 for (int i = 0; i < TabPages.Count; i++) {
1138 TabPage page = TabPages [i];
1140 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, 0, ref aux, true);
1143 if (SelectedIndex != -1 && TabPages.Count > SelectedIndex && TabPages[SelectedIndex].Row != BottomRow)
1144 DropRow (TabPages [SelectedIndex].Row);
1147 private int BottomRow {
1151 private int Direction
1158 private void DropRow (int row)
1160 if (Appearance != TabAppearance.Normal)
1163 int bottom = BottomRow;
1164 int direction = Direction;
1166 foreach (TabPage page in TabPages) {
1167 if (page.Row == row) {
1169 } else if (direction == 1 && page.Row < row) {
1170 page.Row += direction;
1171 } else if (direction == -1 && page.Row > row) {
1172 page.Row += direction;
1177 private int CalcYPos ()
1179 if (Alignment == TabAlignment.Bottom || Alignment == TabAlignment.Left)
1180 return ThemeEngine.Current.TabControlGetPanelRect (this).Bottom;
1182 if (Appearance == TabAppearance.Normal)
1183 return this.ClientRectangle.Y + ThemeEngine.Current.TabControlSelectedDelta.Y;
1185 return this.ClientRectangle.Y;
1189 private int CalcXPos ()
1191 if (Alignment == TabAlignment.Right)
1192 return ThemeEngine.Current.TabControlGetPanelRect (this).Right;
1194 if (Appearance == TabAppearance.Normal)
1195 return this.ClientRectangle.X + ThemeEngine.Current.TabControlSelectedDelta.X;
1197 return this.ClientRectangle.X;
1200 private void SizeTabs ()
1202 switch (Alignment) {
1203 case TabAlignment.Right:
1204 case TabAlignment.Left:
1205 SizeTabs (Height, true);
1208 SizeTabs (Width, false);
1213 private void SizeTabs (int row_width, bool vertical)
1218 Size spacing = TabSpacing;
1221 if (TabPages.Count == 0)
1224 prev_row = TabPages [0].Row;
1226 // Reset the slider position if the slider isn't needed
1227 // anymore (ie window size was increased so all tabs are visible)
1231 // set X = -1 for marking tabs that are not visible due to scrolling
1232 for (int i = 0; i < slider_pos; i++) {
1233 TabPage page = TabPages[i];
1234 Rectangle x = page.TabBounds;
1240 for (int i = slider_pos; i < TabPages.Count; i++) {
1241 TabPage page = TabPages[i];
1242 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, prev_row, ref begin_prev, false);
1243 prev_row = page.Row;
1246 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1247 FillRow (begin_prev, TabPages.Count - 1,
1248 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)),
1252 if (SelectedIndex != -1) {
1253 ExpandSelected (TabPages [SelectedIndex], 0, row_width - 1);
1257 private void SizeTab (TabPage page, int i, int row_width, ref int xpos, ref int ypos,
1258 Size spacing, int prev_row, ref int begin_prev, bool widthOnly)
1260 int width, height = 0;
1262 if (SizeMode == TabSizeMode.Fixed) {
1263 width = item_size.Width;
1265 width = MeasureStringWidth (DeviceContext, page.Text, Font);
1266 width += (Padding.X * 2) + 2;
1268 if (ImageList != null && page.ImageIndex >= 0 && page.ImageIndex < ImageList.Images.Count) {
1269 width += ImageList.ImageSize.Width + ThemeEngine.Current.TabControlImagePadding.X;
1271 int image_size = ImageList.ImageSize.Height + ThemeEngine.Current.TabControlImagePadding.Y;
1272 if (item_size.Height < image_size)
1273 item_size.Height = image_size;
1277 height = item_size.Height - ThemeEngine.Current.TabControlSelectedDelta.Height; // full height only for selected tab
1279 if (width < MinimumTabWidth)
1280 width = MinimumTabWidth;
1282 if (i == SelectedIndex)
1283 width += ThemeEngine.Current.TabControlSelectedSpacing;
1286 page.TabBounds = new Rectangle (xpos, 0, width, 0);
1287 page.Row = row_count;
1288 if (xpos + width > row_width && multiline) {
1291 } else if (xpos + width > row_width) {
1294 if (i == selected_index && show_slider) {
1295 for (int j = i-1; j >= 0; j--) {
1296 if (TabPages [j].TabBounds.Left < xpos + width - row_width) {
1303 if (page.Row != prev_row) {
1307 switch (Alignment) {
1308 case TabAlignment.Top:
1309 page.TabBounds = new Rectangle (
1311 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (),
1315 case TabAlignment.Bottom:
1316 page.TabBounds = new Rectangle (
1318 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (),
1322 case TabAlignment.Left:
1323 if (Appearance == TabAppearance.Normal) {
1324 // tab rows are positioned right to left
1325 page.TabBounds = new Rectangle (
1326 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1331 // tab rows are positioned left to right
1332 page.TabBounds = new Rectangle (
1333 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1340 case TabAlignment.Right:
1341 if (Appearance == TabAppearance.Normal) {
1342 // tab rows are positioned left to right
1343 page.TabBounds = new Rectangle (
1344 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1349 // tab rows are positioned right to left
1350 page.TabBounds = new Rectangle (
1351 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1360 if (page.Row != prev_row) {
1361 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1362 bool vertical = alignment == TabAlignment.Right || alignment == TabAlignment.Left;
1363 int offset = vertical ? TabPages [i - 1].TabBounds.Bottom : TabPages [i - 1].TabBounds.Right;
1364 FillRow (begin_prev, i - 1, ((row_width - offset) / (i - begin_prev)), spacing,
1371 xpos += width + spacing.Width + ThemeEngine.Current.TabControlColSpacing;
1374 private void FillRow (int start, int end, int amount, Size spacing, bool vertical)
1377 FillRowV (start, end, amount, spacing);
1379 FillRow (start, end, amount, spacing);
1382 private void FillRow (int start, int end, int amount, Size spacing)
1384 int xpos = TabPages [start].TabBounds.Left;
1385 for (int i = start; i <= end; i++) {
1386 TabPage page = TabPages [i];
1388 int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount);
1390 page.TabBounds = new Rectangle (left, page.TabBounds.Top,
1391 width, page.TabBounds.Height);
1392 xpos = page.TabBounds.Right + 1 + spacing.Width;
1396 private void FillRowV (int start, int end, int amount, Size spacing)
1398 int ypos = TabPages [start].TabBounds.Top;
1399 for (int i = start; i <= end; i++) {
1400 TabPage page = TabPages [i];
1402 int height = (i == end ? Height - top - 5 : page.TabBounds.Height + amount);
1404 page.TabBounds = new Rectangle (page.TabBounds.Left, top,
1405 page.TabBounds.Width, height);
1406 ypos = page.TabBounds.Bottom + 1;
1410 private void ExpandSelected (TabPage page, int left_edge, int right_edge)
1412 if (Appearance != TabAppearance.Normal)
1415 Rectangle r = page.TabBounds;
1416 switch (Alignment) {
1417 case TabAlignment.Top:
1418 case TabAlignment.Left:
1419 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1420 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1422 case TabAlignment.Bottom:
1423 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1424 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1426 case TabAlignment.Right:
1427 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1428 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1432 r.Width += ThemeEngine.Current.TabControlSelectedDelta.Width;
1433 r.Height += ThemeEngine.Current.TabControlSelectedDelta.Height;
1434 if (r.Left < left_edge)
1436 // Adjustment can't be used for right alignment, since it is
1437 // the only one that has a different X origin than 0
1438 if (r.Right > right_edge && SizeMode != TabSizeMode.Normal &&
1439 alignment != TabAlignment.Right)
1440 r.Width = right_edge - r.X;
1444 private void Draw (Graphics dc, Rectangle clip)
1446 ThemeEngine.Current.DrawTabControl (dc, clip, this);
1449 private TabPage GetTab (int index)
1451 return Controls [index] as TabPage;
1454 private void SetTab (int index, TabPage value)
1456 if (!tab_pages.Contains (value)) {
1457 this.Controls.Add (value);
1459 this.Controls.RemoveAt (index);
1460 this.Controls.SetChildIndex (value, index);
1464 private void InsertTab (int index, TabPage value)
1466 if (!tab_pages.Contains (value)) {
1467 this.Controls.Add (value);
1469 this.Controls.SetChildIndex (value, index);
1473 internal void Redraw ()
1475 if (!IsHandleCreated)
1482 private int MeasureStringWidth (Graphics graphics, string text, Font font)
1484 if (text == String.Empty)
1486 StringFormat format = new StringFormat();
1487 RectangleF rect = new RectangleF(0, 0, 1000, 1000);
1488 CharacterRange[] ranges = { new CharacterRange(0, text.Length) };
1489 Region[] regions = new Region[1];
1491 format.SetMeasurableCharacterRanges(ranges);
1492 format.FormatFlags = StringFormatFlags.NoClip;
1493 format.FormatFlags |= StringFormatFlags.NoWrap;
1494 regions = graphics.MeasureCharacterRanges(text + "I", font, rect, format);
1495 rect = regions[0].GetBounds(graphics);
1497 return (int)(rect.Width);
1500 void OnMouseMove (object sender, MouseEventArgs e)
1502 if (!mouse_down_on_a_tab_page && ShowSlider) {
1503 if (LeftSliderState == PushButtonState.Pressed ||
1504 RightSliderState == PushButtonState.Pressed)
1506 if (LeftScrollButtonArea.Contains (e.Location)) {
1507 LeftSliderState = PushButtonState.Hot;
1508 RightSliderState = PushButtonState.Normal;
1509 EnteredTabPage = null;
1512 if (RightScrollButtonArea.Contains (e.Location)) {
1513 RightSliderState = PushButtonState.Hot;
1514 LeftSliderState = PushButtonState.Normal;
1515 EnteredTabPage = null;
1518 LeftSliderState = PushButtonState.Normal;
1519 RightSliderState = PushButtonState.Normal;
1521 if (EnteredTabPage != null && EnteredTabPage.TabBounds.Contains (e.Location))
1523 for (int index = 0; index < TabCount; index++) {
1524 TabPage tab_page = TabPages[index];
1525 if (tab_page.TabBounds.Contains (e.Location)) {
1526 EnteredTabPage = tab_page;
1530 EnteredTabPage = null;
1533 void OnMouseLeave (object sender, EventArgs e)
1536 LeftSliderState = PushButtonState.Normal;
1537 RightSliderState = PushButtonState.Normal;
1539 EnteredTabPage = null;
1541 #endregion // Internal & Private Methods
1545 [EditorBrowsable(EditorBrowsableState.Never)]
1546 public new event EventHandler BackColorChanged {
1547 add { base.BackColorChanged += value; }
1548 remove { base.BackColorChanged -= value; }
1552 [EditorBrowsable(EditorBrowsableState.Never)]
1553 public new event EventHandler BackgroundImageChanged {
1554 add { base.BackgroundImageChanged += value; }
1555 remove { base.BackgroundImageChanged -= value; }
1560 [EditorBrowsable (EditorBrowsableState.Never)]
1561 public new event EventHandler BackgroundImageLayoutChanged
1563 add { base.BackgroundImageLayoutChanged += value; }
1564 remove { base.BackgroundImageLayoutChanged -= value; }
1569 [EditorBrowsable(EditorBrowsableState.Never)]
1570 public new event EventHandler ForeColorChanged {
1571 add { base.ForeColorChanged += value; }
1572 remove { base.ForeColorChanged -= value; }
1576 [EditorBrowsable(EditorBrowsableState.Never)]
1577 public new event PaintEventHandler Paint {
1578 add { base.Paint += value; }
1579 remove { base.Paint -= value; }
1583 [EditorBrowsable(EditorBrowsableState.Never)]
1584 public new event EventHandler TextChanged {
1585 add { base.TextChanged += value; }
1586 remove { base.TextChanged -= value; }
1589 static object DrawItemEvent = new object ();
1590 static object SelectedIndexChangedEvent = new object ();
1592 public event DrawItemEventHandler DrawItem {
1593 add { Events.AddHandler (DrawItemEvent, value); }
1594 remove { Events.RemoveHandler (DrawItemEvent, value); }
1597 public event EventHandler SelectedIndexChanged {
1598 add { Events.AddHandler (SelectedIndexChangedEvent, value); }
1599 remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); }
1603 static object SelectedEvent = new object ();
1605 public event TabControlEventHandler Selected {
1606 add { Events.AddHandler (SelectedEvent, value); }
1607 remove { Events.RemoveHandler (SelectedEvent, value); }
1610 static object DeselectedEvent = new object ();
1612 public event TabControlEventHandler Deselected
1614 add { Events.AddHandler (DeselectedEvent, value); }
1615 remove { Events.RemoveHandler (DeselectedEvent, value); }
1618 static object SelectingEvent = new object ();
1620 public event TabControlCancelEventHandler Selecting
1622 add { Events.AddHandler (SelectingEvent, value); }
1623 remove { Events.RemoveHandler (SelectingEvent, value); }
1626 static object DeselectingEvent = new object ();
1628 public event TabControlCancelEventHandler Deselecting
1630 add { Events.AddHandler (DeselectingEvent, value); }
1631 remove { Events.RemoveHandler (DeselectingEvent, value); }
1634 static object RightToLeftLayoutChangedEvent = new object ();
1635 public event EventHandler RightToLeftLayoutChanged
1637 add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); }
1638 remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); }
1641 #endregion // Events
1644 #region Class TaControl.ControlCollection
1646 [ComVisible (false)]
1648 public new class ControlCollection : System.Windows.Forms.Control.ControlCollection {
1650 private TabControl owner;
1652 public ControlCollection (TabControl owner) : base (owner)
1657 public override void Add (Control value)
1659 TabPage page = value as TabPage;
1661 throw new ArgumentException ("Cannot add " +
1662 value.GetType ().Name + " to TabControl. " +
1663 "Only TabPages can be directly added to TabControls.");
1665 page.SetVisible (false);
1667 if (owner.TabCount == 1 && owner.selected_index < 0)
1668 owner.SelectedIndex = 0;
1672 public override void Remove (Control value)
1674 bool change_index = false;
1676 TabPage page = value as TabPage;
1677 if (page != null && owner.Controls.Contains (page)) {
1678 int index = owner.IndexForTabPage (page);
1679 if (index < owner.SelectedIndex || owner.SelectedIndex == Count - 1)
1680 change_index = true;
1683 base.Remove (value);
1685 // We don't want to raise SelectedIndexChanged until after we
1686 // have removed from the collection, so TabCount will be
1687 // correct for the user.
1688 if (change_index && Count > 0) {
1689 // Clear the selected index internally, to avoid trying to access the previous
1690 // selected tab when setting the new one - this is what .net seems to do
1691 int prev_selected_index = owner.SelectedIndex;
1692 owner.selected_index = -1;
1694 owner.SelectedIndex = --prev_selected_index;
1695 } else if (change_index) {
1696 owner.selected_index = -1;
1697 owner.OnSelectedIndexChanged (EventArgs.Empty);
1702 #endregion // Class TabControl.ControlCollection
1704 #region Class TabPage.TabPageCollection
1705 public class TabPageCollection : IList, ICollection, IEnumerable {
1707 private TabControl owner;
1709 public TabPageCollection (TabControl owner)
1712 throw new ArgumentNullException ("Value cannot be null.");
1718 get { return owner.Controls.Count; }
1721 public bool IsReadOnly {
1722 get { return false; }
1725 public virtual TabPage this [int index] {
1727 return owner.GetTab (index);
1730 owner.SetTab (index, value);
1734 public virtual TabPage this [string key] {
1736 if (string.IsNullOrEmpty (key))
1739 int index = this.IndexOfKey (key);
1740 if (index < 0 || index >= this.Count)
1748 internal int this[TabPage tabPage] {
1750 if (tabPage == null)
1753 for (int i = 0; i < this.Count; i++)
1754 if (this[i].Equals (tabPage))
1761 bool ICollection.IsSynchronized {
1762 get { return false; }
1765 object ICollection.SyncRoot {
1766 get { return this; }
1769 bool IList.IsFixedSize {
1770 get { return false; }
1773 object IList.this [int index] {
1775 return owner.GetTab (index);
1778 owner.SetTab (index, (TabPage) value);
1782 public void Add (TabPage value)
1785 throw new ArgumentNullException ("Value cannot be null.");
1786 owner.Controls.Add (value);
1790 public void Add (string text)
1792 TabPage page = new TabPage (text);
1796 public void Add (string key, string text)
1798 TabPage page = new TabPage (text);
1803 public void Add (string key, string text, int imageIndex)
1805 TabPage page = new TabPage (text);
1807 page.ImageIndex = imageIndex;
1811 // .Net sets the ImageKey, but does not show the image when this is used
1812 public void Add (string key, string text, string imageKey)
1814 TabPage page = new TabPage (text);
1816 page.ImageKey = imageKey;
1821 public void AddRange (TabPage [] pages)
1824 throw new ArgumentNullException ("Value cannot be null.");
1825 owner.Controls.AddRange (pages);
1828 public virtual void Clear ()
1830 owner.Controls.Clear ();
1831 owner.Invalidate ();
1834 public bool Contains (TabPage page)
1837 throw new ArgumentNullException ("Value cannot be null.");
1838 return owner.Controls.Contains (page);
1842 public virtual bool ContainsKey (string key)
1844 int index = this.IndexOfKey (key);
1845 return (index >= 0 && index < this.Count);
1849 public IEnumerator GetEnumerator ()
1851 return owner.Controls.GetEnumerator ();
1854 public int IndexOf (TabPage page)
1856 return owner.Controls.IndexOf (page);
1860 public virtual int IndexOfKey(string key)
1862 if (string.IsNullOrEmpty (key))
1865 for (int i = 0; i < this.Count; i++) {
1866 if (string.Compare (this[i].Name, key, true,
1867 System.Globalization.CultureInfo.InvariantCulture) == 0) {
1876 public void Remove (TabPage value)
1878 owner.Controls.Remove (value);
1879 owner.Invalidate ();
1882 public void RemoveAt (int index)
1884 owner.Controls.RemoveAt (index);
1885 owner.Invalidate ();
1889 public virtual void RemoveByKey (string key)
1891 int index = this.IndexOfKey (key);
1892 if (index >= 0 && index < this.Count)
1893 this.RemoveAt (index);
1897 void ICollection.CopyTo (Array dest, int index)
1899 owner.Controls.CopyTo (dest, index);
1902 int IList.Add (object value)
1904 TabPage page = value as TabPage;
1906 throw new ArgumentException ("value");
1907 owner.Controls.Add (page);
1908 return owner.Controls.IndexOf (page);
1911 bool IList.Contains (object page)
1913 TabPage tabPage = page as TabPage;
1914 if (tabPage == null)
1916 return Contains (tabPage);
1919 int IList.IndexOf (object page)
1921 TabPage tabPage = page as TabPage;
1922 if (tabPage == null)
1924 return IndexOf (tabPage);
1928 void IList.Insert (int index, object tabPage)
1930 void IList.Insert (int index, object value)
1933 throw new NotSupportedException ();
1937 public void Insert (int index, string text)
1939 owner.InsertTab (index, new TabPage (text));
1942 public void Insert (int index, TabPage tabPage)
1944 owner.InsertTab (index, tabPage);
1947 public void Insert (int index, string key, string text)
1949 TabPage page = new TabPage(text);
1951 owner.InsertTab (index, page);
1954 public void Insert (int index, string key, string text, int imageIndex)
1956 TabPage page = new TabPage(text);
1958 owner.InsertTab (index, page);
1959 page.ImageIndex = imageIndex;
1962 public void Insert (int index, string key, string text, string imageKey)
1964 TabPage page = new TabPage(text);
1966 owner.InsertTab (index, page);
1967 page.ImageKey = imageKey;
1970 void IList.Remove (object value)
1972 TabPage page = value as TabPage;
1975 Remove ((TabPage) value);
1978 #endregion // Class TabPage.TabPageCollection