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)
826 if (ke.KeyCode == Keys.Tab && (ke.KeyData & Keys.Control) != 0) {
827 if ((ke.KeyData & Keys.Shift) == 0)
828 SelectedIndex = (SelectedIndex + 1) % TabCount;
830 SelectedIndex = (SelectedIndex + TabCount - 1) % TabCount;
832 } else if (ke.KeyCode == Keys.Home) {
835 } else if (ke.KeyCode == Keys.End) {
836 SelectedIndex = TabCount - 1;
838 } else if (NavigateTabs (ke.KeyCode))
842 protected override bool IsInputKey (Keys keyData)
844 switch (keyData & Keys.KeyCode) {
853 return base.IsInputKey (keyData);
856 private bool NavigateTabs (Keys keycode)
858 bool move_left = false;
859 bool move_right = false;
861 if (alignment == TabAlignment.Bottom || alignment == TabAlignment.Top) {
862 if (keycode == Keys.Left)
864 else if (keycode == Keys.Right)
867 if (keycode == Keys.Up)
869 else if (keycode == Keys.Down)
874 if (SelectedIndex > 0) {
881 if (SelectedIndex < TabCount - 1) {
891 #region Pages Collection
892 protected void RemoveAll ()
897 protected virtual object [] GetItems ()
899 TabPage [] pages = new TabPage [Controls.Count];
900 Controls.CopyTo (pages, 0);
904 protected virtual object [] GetItems (Type baseType)
906 object[] pages = (object[])Array.CreateInstance (baseType, Controls.Count);
907 Controls.CopyTo (pages, 0);
913 protected void UpdateTabSelection (bool updateFocus)
915 protected void UpdateTabSelection (bool uiselected)
921 protected string GetToolTipText (object item)
923 TabPage page = (TabPage) item;
924 return page.ToolTipText;
927 protected override void WndProc (ref Message m)
929 switch ((Msg)m.Msg) {
930 case Msg.WM_SETFOCUS:
931 if (selected_index == -1 && this.TabCount > 0)
932 this.SelectedIndex = 0;
933 if (selected_index != -1)
934 Invalidate(GetTabRect(selected_index));
935 base.WndProc (ref m);
937 case Msg.WM_KILLFOCUS:
938 if (selected_index != -1)
939 Invalidate(GetTabRect(selected_index));
940 base.WndProc (ref m);
943 base.WndProc (ref m);
948 #endregion // Protected Instance Methods
950 #region Internal & Private Methods
951 private bool CanScrollRight {
953 return (slider_pos < TabCount - 1);
957 private bool CanScrollLeft {
958 get { return slider_pos > 0; }
961 private void MouseDownHandler (object sender, MouseEventArgs e)
963 if ((e.Button & MouseButtons.Left) == 0)
967 Rectangle right = RightScrollButtonArea;
968 Rectangle left = LeftScrollButtonArea;
969 if (right.Contains (e.X, e.Y)) {
970 right_slider_state = PushButtonState.Pressed;
971 if (CanScrollRight) {
976 // UIA Framework Event: Horizontally Scrolled
977 OnUIAHorizontallyScrolled (EventArgs.Empty);
980 switch (this.Alignment) {
981 case TabAlignment.Top:
982 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
984 case TabAlignment.Bottom:
985 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
987 case TabAlignment.Left:
988 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
990 case TabAlignment.Right:
991 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
999 } else if (left.Contains (e.X, e.Y)) {
1000 left_slider_state = PushButtonState.Pressed;
1001 if (CanScrollLeft) {
1006 // UIA Framework Event: Horizontally Scrolled
1007 OnUIAHorizontallyScrolled (EventArgs.Empty);
1010 switch (this.Alignment) {
1011 case TabAlignment.Top:
1012 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
1014 case TabAlignment.Bottom:
1015 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
1017 case TabAlignment.Left:
1018 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
1020 case TabAlignment.Right:
1021 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
1031 int count = Controls.Count;
1032 for (int i = SliderPos; i < count; i++) {
1033 if (!GetTabRect (i).Contains (e.X, e.Y))
1036 mouse_down_on_a_tab_page = true;
1041 private void MouseUpHandler (object sender, MouseEventArgs e)
1043 mouse_down_on_a_tab_page = false;
1044 if (ShowSlider && (left_slider_state == PushButtonState.Pressed || right_slider_state == PushButtonState.Pressed)) {
1046 if (left_slider_state == PushButtonState.Pressed) {
1047 invalid = LeftScrollButtonArea;
1048 left_slider_state = GetScrollButtonState (invalid, e.Location);
1050 invalid = RightScrollButtonArea;
1051 right_slider_state = GetScrollButtonState (invalid, e.Location);
1053 Invalidate (invalid);
1057 bool HasHotElementStyles {
1059 return ThemeElements.CurrentTheme.TabControlPainter.HasHotElementStyles (this);
1063 Rectangle LeftScrollButtonArea {
1065 return ThemeElements.CurrentTheme.TabControlPainter.GetLeftScrollRect (this);
1069 Rectangle RightScrollButtonArea {
1071 return ThemeElements.CurrentTheme.TabControlPainter.GetRightScrollRect (this);
1075 static PushButtonState GetScrollButtonState (Rectangle scrollButtonArea, Point cursorLocation)
1077 return scrollButtonArea.Contains (cursorLocation) ? PushButtonState.Hot : PushButtonState.Normal;
1080 private void SizeChangedHandler (object sender, EventArgs e)
1085 internal int IndexForTabPage (TabPage page)
1087 for (int i = 0; i < tab_pages.Count; i++) {
1088 if (page == tab_pages [i])
1094 private void ResizeTabPages ()
1098 Rectangle r = DisplayRectangle;
1099 foreach (TabPage page in Controls) {
1104 private int MinimumTabWidth {
1106 return ThemeEngine.Current.TabControlMinimumTabWidth;
1110 private Size TabSpacing {
1112 return ThemeEngine.Current.TabControlGetSpacing (this);
1116 private void CalcTabRows ()
1118 switch (Alignment) {
1119 case TabAlignment.Right:
1120 case TabAlignment.Left:
1121 CalcTabRows (Height);
1124 CalcTabRows (Width);
1129 private void CalcTabRows (int row_width)
1133 Size spacing = TabSpacing;
1135 if (TabPages.Count > 0)
1137 show_slider = false;
1139 for (int i = 0; i < TabPages.Count; i++) {
1140 TabPage page = TabPages [i];
1142 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, 0, ref aux, true);
1145 if (SelectedIndex != -1 && TabPages.Count > SelectedIndex && TabPages[SelectedIndex].Row != BottomRow)
1146 DropRow (TabPages [SelectedIndex].Row);
1149 private int BottomRow {
1153 private int Direction
1160 private void DropRow (int row)
1162 if (Appearance != TabAppearance.Normal)
1165 int bottom = BottomRow;
1166 int direction = Direction;
1168 foreach (TabPage page in TabPages) {
1169 if (page.Row == row) {
1171 } else if (direction == 1 && page.Row < row) {
1172 page.Row += direction;
1173 } else if (direction == -1 && page.Row > row) {
1174 page.Row += direction;
1179 private int CalcYPos ()
1181 if (Alignment == TabAlignment.Bottom || Alignment == TabAlignment.Left)
1182 return ThemeEngine.Current.TabControlGetPanelRect (this).Bottom;
1184 if (Appearance == TabAppearance.Normal)
1185 return this.ClientRectangle.Y + ThemeEngine.Current.TabControlSelectedDelta.Y;
1187 return this.ClientRectangle.Y;
1191 private int CalcXPos ()
1193 if (Alignment == TabAlignment.Right)
1194 return ThemeEngine.Current.TabControlGetPanelRect (this).Right;
1196 if (Appearance == TabAppearance.Normal)
1197 return this.ClientRectangle.X + ThemeEngine.Current.TabControlSelectedDelta.X;
1199 return this.ClientRectangle.X;
1202 private void SizeTabs ()
1204 switch (Alignment) {
1205 case TabAlignment.Right:
1206 case TabAlignment.Left:
1207 SizeTabs (Height, true);
1210 SizeTabs (Width, false);
1215 private void SizeTabs (int row_width, bool vertical)
1220 Size spacing = TabSpacing;
1223 if (TabPages.Count == 0)
1226 prev_row = TabPages [0].Row;
1228 // Reset the slider position if the slider isn't needed
1229 // anymore (ie window size was increased so all tabs are visible)
1233 // set X = -1 for marking tabs that are not visible due to scrolling
1234 for (int i = 0; i < slider_pos; i++) {
1235 TabPage page = TabPages[i];
1236 Rectangle x = page.TabBounds;
1242 for (int i = slider_pos; i < TabPages.Count; i++) {
1243 TabPage page = TabPages[i];
1244 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, prev_row, ref begin_prev, false);
1245 prev_row = page.Row;
1248 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1249 FillRow (begin_prev, TabPages.Count - 1,
1250 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)),
1254 if (SelectedIndex != -1) {
1255 ExpandSelected (TabPages [SelectedIndex], 0, row_width - 1);
1259 private void SizeTab (TabPage page, int i, int row_width, ref int xpos, ref int ypos,
1260 Size spacing, int prev_row, ref int begin_prev, bool widthOnly)
1262 int width, height = 0;
1264 if (SizeMode == TabSizeMode.Fixed) {
1265 width = item_size.Width;
1267 width = MeasureStringWidth (DeviceContext, page.Text, page.Font);
1268 width += (Padding.X * 2) + 2;
1270 if (ImageList != null && page.ImageIndex >= 0 && page.ImageIndex < ImageList.Images.Count) {
1271 width += ImageList.ImageSize.Width + ThemeEngine.Current.TabControlImagePadding.X;
1273 int image_size = ImageList.ImageSize.Height + ThemeEngine.Current.TabControlImagePadding.Y;
1274 if (item_size.Height < image_size)
1275 item_size.Height = image_size;
1279 height = item_size.Height - ThemeEngine.Current.TabControlSelectedDelta.Height; // full height only for selected tab
1281 if (width < MinimumTabWidth)
1282 width = MinimumTabWidth;
1284 if (i == SelectedIndex)
1285 width += ThemeEngine.Current.TabControlSelectedSpacing;
1288 page.TabBounds = new Rectangle (xpos, 0, width, 0);
1289 page.Row = row_count;
1290 if (xpos + width > row_width && multiline) {
1293 } else if (xpos + width > row_width) {
1296 if (i == selected_index && show_slider) {
1297 for (int j = i-1; j >= 0; j--) {
1298 if (TabPages [j].TabBounds.Left < xpos + width - row_width) {
1305 if (page.Row != prev_row) {
1309 switch (Alignment) {
1310 case TabAlignment.Top:
1311 page.TabBounds = new Rectangle (
1313 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (),
1317 case TabAlignment.Bottom:
1318 page.TabBounds = new Rectangle (
1320 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (),
1324 case TabAlignment.Left:
1325 if (Appearance == TabAppearance.Normal) {
1326 // tab rows are positioned right to left
1327 page.TabBounds = new Rectangle (
1328 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1333 // tab rows are positioned left to right
1334 page.TabBounds = new Rectangle (
1335 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1342 case TabAlignment.Right:
1343 if (Appearance == TabAppearance.Normal) {
1344 // tab rows are positioned left to right
1345 page.TabBounds = new Rectangle (
1346 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1351 // tab rows are positioned right to left
1352 page.TabBounds = new Rectangle (
1353 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1362 if (page.Row != prev_row) {
1363 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1364 bool vertical = alignment == TabAlignment.Right || alignment == TabAlignment.Left;
1365 int offset = vertical ? TabPages [i - 1].TabBounds.Bottom : TabPages [i - 1].TabBounds.Right;
1366 FillRow (begin_prev, i - 1, ((row_width - offset) / (i - begin_prev)), spacing,
1373 xpos += width + spacing.Width + ThemeEngine.Current.TabControlColSpacing;
1376 private void FillRow (int start, int end, int amount, Size spacing, bool vertical)
1379 FillRowV (start, end, amount, spacing);
1381 FillRow (start, end, amount, spacing);
1384 private void FillRow (int start, int end, int amount, Size spacing)
1386 int xpos = TabPages [start].TabBounds.Left;
1387 for (int i = start; i <= end; i++) {
1388 TabPage page = TabPages [i];
1390 int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount);
1392 page.TabBounds = new Rectangle (left, page.TabBounds.Top,
1393 width, page.TabBounds.Height);
1394 xpos = page.TabBounds.Right + 1 + spacing.Width;
1398 private void FillRowV (int start, int end, int amount, Size spacing)
1400 int ypos = TabPages [start].TabBounds.Top;
1401 for (int i = start; i <= end; i++) {
1402 TabPage page = TabPages [i];
1404 int height = (i == end ? Height - top - 5 : page.TabBounds.Height + amount);
1406 page.TabBounds = new Rectangle (page.TabBounds.Left, top,
1407 page.TabBounds.Width, height);
1408 ypos = page.TabBounds.Bottom + 1;
1412 private void ExpandSelected (TabPage page, int left_edge, int right_edge)
1414 if (Appearance != TabAppearance.Normal)
1417 Rectangle r = page.TabBounds;
1418 switch (Alignment) {
1419 case TabAlignment.Top:
1420 case TabAlignment.Left:
1421 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1422 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1424 case TabAlignment.Bottom:
1425 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1426 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1428 case TabAlignment.Right:
1429 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1430 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1434 r.Width += ThemeEngine.Current.TabControlSelectedDelta.Width;
1435 r.Height += ThemeEngine.Current.TabControlSelectedDelta.Height;
1436 if (r.Left < left_edge)
1438 // Adjustment can't be used for right alignment, since it is
1439 // the only one that has a different X origin than 0
1440 if (r.Right > right_edge && SizeMode != TabSizeMode.Normal &&
1441 alignment != TabAlignment.Right)
1442 r.Width = right_edge - r.X;
1446 private void Draw (Graphics dc, Rectangle clip)
1448 ThemeEngine.Current.DrawTabControl (dc, clip, this);
1451 private TabPage GetTab (int index)
1453 return Controls [index] as TabPage;
1456 private void SetTab (int index, TabPage value)
1458 if (!tab_pages.Contains (value)) {
1459 this.Controls.Add (value);
1461 this.Controls.RemoveAt (index);
1462 this.Controls.SetChildIndex (value, index);
1466 private void InsertTab (int index, TabPage value)
1468 if (!tab_pages.Contains (value)) {
1469 this.Controls.Add (value);
1471 this.Controls.SetChildIndex (value, index);
1475 internal void Redraw ()
1477 if (!IsHandleCreated)
1484 private int MeasureStringWidth (Graphics graphics, string text, Font font)
1486 if (text == String.Empty)
1488 StringFormat format = new StringFormat();
1489 RectangleF rect = new RectangleF(0, 0, 1000, 1000);
1490 CharacterRange[] ranges = { new CharacterRange(0, text.Length) };
1491 Region[] regions = new Region[1];
1493 format.SetMeasurableCharacterRanges(ranges);
1494 format.FormatFlags = StringFormatFlags.NoClip;
1495 format.FormatFlags |= StringFormatFlags.NoWrap;
1496 regions = graphics.MeasureCharacterRanges(text + "I", font, rect, format);
1497 rect = regions[0].GetBounds(graphics);
1499 return (int)(rect.Width);
1502 void OnMouseMove (object sender, MouseEventArgs e)
1504 if (!mouse_down_on_a_tab_page && ShowSlider) {
1505 if (LeftSliderState == PushButtonState.Pressed ||
1506 RightSliderState == PushButtonState.Pressed)
1508 if (LeftScrollButtonArea.Contains (e.Location)) {
1509 LeftSliderState = PushButtonState.Hot;
1510 RightSliderState = PushButtonState.Normal;
1511 EnteredTabPage = null;
1514 if (RightScrollButtonArea.Contains (e.Location)) {
1515 RightSliderState = PushButtonState.Hot;
1516 LeftSliderState = PushButtonState.Normal;
1517 EnteredTabPage = null;
1520 LeftSliderState = PushButtonState.Normal;
1521 RightSliderState = PushButtonState.Normal;
1523 if (EnteredTabPage != null && EnteredTabPage.TabBounds.Contains (e.Location))
1525 for (int index = 0; index < TabCount; index++) {
1526 TabPage tab_page = TabPages[index];
1527 if (tab_page.TabBounds.Contains (e.Location)) {
1528 EnteredTabPage = tab_page;
1532 EnteredTabPage = null;
1535 void OnMouseLeave (object sender, EventArgs e)
1538 LeftSliderState = PushButtonState.Normal;
1539 RightSliderState = PushButtonState.Normal;
1541 EnteredTabPage = null;
1543 #endregion // Internal & Private Methods
1547 [EditorBrowsable(EditorBrowsableState.Never)]
1548 public new event EventHandler BackColorChanged {
1549 add { base.BackColorChanged += value; }
1550 remove { base.BackColorChanged -= value; }
1554 [EditorBrowsable(EditorBrowsableState.Never)]
1555 public new event EventHandler BackgroundImageChanged {
1556 add { base.BackgroundImageChanged += value; }
1557 remove { base.BackgroundImageChanged -= value; }
1562 [EditorBrowsable (EditorBrowsableState.Never)]
1563 public new event EventHandler BackgroundImageLayoutChanged
1565 add { base.BackgroundImageLayoutChanged += value; }
1566 remove { base.BackgroundImageLayoutChanged -= value; }
1571 [EditorBrowsable(EditorBrowsableState.Never)]
1572 public new event EventHandler ForeColorChanged {
1573 add { base.ForeColorChanged += value; }
1574 remove { base.ForeColorChanged -= value; }
1578 [EditorBrowsable(EditorBrowsableState.Never)]
1579 public new event PaintEventHandler Paint {
1580 add { base.Paint += value; }
1581 remove { base.Paint -= value; }
1585 [EditorBrowsable(EditorBrowsableState.Never)]
1586 public new event EventHandler TextChanged {
1587 add { base.TextChanged += value; }
1588 remove { base.TextChanged -= value; }
1591 static object DrawItemEvent = new object ();
1592 static object SelectedIndexChangedEvent = new object ();
1594 public event DrawItemEventHandler DrawItem {
1595 add { Events.AddHandler (DrawItemEvent, value); }
1596 remove { Events.RemoveHandler (DrawItemEvent, value); }
1599 public event EventHandler SelectedIndexChanged {
1600 add { Events.AddHandler (SelectedIndexChangedEvent, value); }
1601 remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); }
1605 static object SelectedEvent = new object ();
1607 public event TabControlEventHandler Selected {
1608 add { Events.AddHandler (SelectedEvent, value); }
1609 remove { Events.RemoveHandler (SelectedEvent, value); }
1612 static object DeselectedEvent = new object ();
1614 public event TabControlEventHandler Deselected
1616 add { Events.AddHandler (DeselectedEvent, value); }
1617 remove { Events.RemoveHandler (DeselectedEvent, value); }
1620 static object SelectingEvent = new object ();
1622 public event TabControlCancelEventHandler Selecting
1624 add { Events.AddHandler (SelectingEvent, value); }
1625 remove { Events.RemoveHandler (SelectingEvent, value); }
1628 static object DeselectingEvent = new object ();
1630 public event TabControlCancelEventHandler Deselecting
1632 add { Events.AddHandler (DeselectingEvent, value); }
1633 remove { Events.RemoveHandler (DeselectingEvent, value); }
1636 static object RightToLeftLayoutChangedEvent = new object ();
1637 public event EventHandler RightToLeftLayoutChanged
1639 add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); }
1640 remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); }
1643 #endregion // Events
1646 #region Class TaControl.ControlCollection
1648 [ComVisible (false)]
1650 public new class ControlCollection : System.Windows.Forms.Control.ControlCollection {
1652 private TabControl owner;
1654 public ControlCollection (TabControl owner) : base (owner)
1659 public override void Add (Control value)
1661 TabPage page = value as TabPage;
1663 throw new ArgumentException ("Cannot add " +
1664 value.GetType ().Name + " to TabControl. " +
1665 "Only TabPages can be directly added to TabControls.");
1667 page.SetVisible (false);
1669 if (owner.TabCount == 1 && owner.selected_index < 0)
1670 owner.SelectedIndex = 0;
1674 public override void Remove (Control value)
1676 bool change_index = false;
1678 TabPage page = value as TabPage;
1679 if (page != null && owner.Controls.Contains (page)) {
1680 int index = owner.IndexForTabPage (page);
1681 if (index < owner.SelectedIndex || owner.SelectedIndex == Count - 1)
1682 change_index = true;
1685 base.Remove (value);
1687 // We don't want to raise SelectedIndexChanged until after we
1688 // have removed from the collection, so TabCount will be
1689 // correct for the user.
1690 if (change_index && Count > 0) {
1691 // Clear the selected index internally, to avoid trying to access the previous
1692 // selected tab when setting the new one - this is what .net seems to do
1693 int prev_selected_index = owner.SelectedIndex;
1694 owner.selected_index = -1;
1696 owner.SelectedIndex = --prev_selected_index;
1697 } else if (change_index) {
1698 owner.selected_index = -1;
1699 owner.OnSelectedIndexChanged (EventArgs.Empty);
1704 #endregion // Class TabControl.ControlCollection
1706 #region Class TabPage.TabPageCollection
1707 public class TabPageCollection : IList, ICollection, IEnumerable {
1709 private TabControl owner;
1711 public TabPageCollection (TabControl owner)
1714 throw new ArgumentNullException ("Value cannot be null.");
1720 get { return owner.Controls.Count; }
1723 public bool IsReadOnly {
1724 get { return false; }
1727 public virtual TabPage this [int index] {
1729 return owner.GetTab (index);
1732 owner.SetTab (index, value);
1736 public virtual TabPage this [string key] {
1738 if (string.IsNullOrEmpty (key))
1741 int index = this.IndexOfKey (key);
1742 if (index < 0 || index >= this.Count)
1750 internal int this[TabPage tabPage] {
1752 if (tabPage == null)
1755 for (int i = 0; i < this.Count; i++)
1756 if (this[i].Equals (tabPage))
1763 bool ICollection.IsSynchronized {
1764 get { return false; }
1767 object ICollection.SyncRoot {
1768 get { return this; }
1771 bool IList.IsFixedSize {
1772 get { return false; }
1775 object IList.this [int index] {
1777 return owner.GetTab (index);
1780 owner.SetTab (index, (TabPage) value);
1784 public void Add (TabPage value)
1787 throw new ArgumentNullException ("Value cannot be null.");
1788 owner.Controls.Add (value);
1792 public void Add (string text)
1794 TabPage page = new TabPage (text);
1798 public void Add (string key, string text)
1800 TabPage page = new TabPage (text);
1805 public void Add (string key, string text, int imageIndex)
1807 TabPage page = new TabPage (text);
1809 page.ImageIndex = imageIndex;
1813 // .Net sets the ImageKey, but does not show the image when this is used
1814 public void Add (string key, string text, string imageKey)
1816 TabPage page = new TabPage (text);
1818 page.ImageKey = imageKey;
1823 public void AddRange (TabPage [] pages)
1826 throw new ArgumentNullException ("Value cannot be null.");
1827 owner.Controls.AddRange (pages);
1830 public virtual void Clear ()
1832 owner.Controls.Clear ();
1833 owner.Invalidate ();
1836 public bool Contains (TabPage page)
1839 throw new ArgumentNullException ("Value cannot be null.");
1840 return owner.Controls.Contains (page);
1844 public virtual bool ContainsKey (string key)
1846 int index = this.IndexOfKey (key);
1847 return (index >= 0 && index < this.Count);
1851 public IEnumerator GetEnumerator ()
1853 return owner.Controls.GetEnumerator ();
1856 public int IndexOf (TabPage page)
1858 return owner.Controls.IndexOf (page);
1862 public virtual int IndexOfKey(string key)
1864 if (string.IsNullOrEmpty (key))
1867 for (int i = 0; i < this.Count; i++) {
1868 if (string.Compare (this[i].Name, key, true,
1869 System.Globalization.CultureInfo.InvariantCulture) == 0) {
1878 public void Remove (TabPage value)
1880 owner.Controls.Remove (value);
1881 owner.Invalidate ();
1884 public void RemoveAt (int index)
1886 owner.Controls.RemoveAt (index);
1887 owner.Invalidate ();
1891 public virtual void RemoveByKey (string key)
1893 int index = this.IndexOfKey (key);
1894 if (index >= 0 && index < this.Count)
1895 this.RemoveAt (index);
1899 void ICollection.CopyTo (Array dest, int index)
1901 owner.Controls.CopyTo (dest, index);
1904 int IList.Add (object value)
1906 TabPage page = value as TabPage;
1908 throw new ArgumentException ("value");
1909 owner.Controls.Add (page);
1910 return owner.Controls.IndexOf (page);
1913 bool IList.Contains (object page)
1915 TabPage tabPage = page as TabPage;
1916 if (tabPage == null)
1918 return Contains (tabPage);
1921 int IList.IndexOf (object page)
1923 TabPage tabPage = page as TabPage;
1924 if (tabPage == null)
1926 return IndexOf (tabPage);
1930 void IList.Insert (int index, object tabPage)
1932 void IList.Insert (int index, object value)
1935 throw new NotSupportedException ();
1939 public void Insert (int index, string text)
1941 owner.InsertTab (index, new TabPage (text));
1944 public void Insert (int index, TabPage tabPage)
1946 owner.InsertTab (index, tabPage);
1949 public void Insert (int index, string key, string text)
1951 TabPage page = new TabPage(text);
1953 owner.InsertTab (index, page);
1956 public void Insert (int index, string key, string text, int imageIndex)
1958 TabPage page = new TabPage(text);
1960 owner.InsertTab (index, page);
1961 page.ImageIndex = imageIndex;
1964 public void Insert (int index, string key, string text, string imageKey)
1966 TabPage page = new TabPage(text);
1968 owner.InsertTab (index, page);
1969 page.ImageKey = imageKey;
1972 void IList.Remove (object value)
1974 TabPage page = value as TabPage;
1977 Remove ((TabPage) value);
1980 #endregion // Class TabPage.TabPageCollection