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 {
36 [ComVisibleAttribute (true)]
37 [ClassInterfaceAttribute (ClassInterfaceType.AutoDispatch)]
38 [DefaultEvent("SelectedIndexChanged")]
39 [DefaultProperty("TabPages")]
40 [Designer("System.Windows.Forms.Design.TabControlDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
41 public class TabControl : Control {
43 private int selected_index = -1;
44 private TabAlignment alignment;
45 private TabAppearance appearance;
46 private TabDrawMode draw_mode;
47 private bool multiline;
48 private ImageList image_list;
49 private Size item_size = Size.Empty;
50 private bool item_size_manual;
51 private Point padding;
52 private int row_count = 0;
53 private bool hottrack;
54 private TabPageCollection tab_pages;
55 private bool show_tool_tips;
56 private TabSizeMode size_mode;
57 private bool show_slider = false;
58 private PushButtonState right_slider_state = PushButtonState.Normal;
59 private PushButtonState left_slider_state = PushButtonState.Normal;
60 private int slider_pos = 0;
61 TabPage entered_tab_page;
62 bool mouse_down_on_a_tab_page;
64 ToolTip.TipState tooltip_state = ToolTip.TipState.Down;
67 private bool rightToLeftLayout;
70 #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];
102 #region UIA Framework Property
103 internal double UIAHorizontalViewSize {
104 get { return LeftScrollButtonArea.Left * 100 / TabPages [TabCount - 1].TabBounds.Right; }
108 #region Public Constructors
111 tab_pages = new TabPageCollection (this);
112 SetStyle (ControlStyles.UserPaint, false);
113 padding = ThemeEngine.Current.TabControlDefaultPadding;
115 MouseDown += new MouseEventHandler (MouseDownHandler);
116 MouseLeave += new EventHandler (OnMouseLeave);
117 MouseMove += new MouseEventHandler (OnMouseMove);
118 MouseUp += new MouseEventHandler (MouseUpHandler);
119 SizeChanged += new EventHandler (SizeChangedHandler);
122 #endregion // Public Constructors
124 #region Public Instance Properties
125 [DefaultValue(TabAlignment.Top)]
127 [RefreshProperties(RefreshProperties.All)]
128 public TabAlignment Alignment {
129 get { return alignment; }
131 if (alignment == value)
134 if (alignment == TabAlignment.Left || alignment == TabAlignment.Right)
140 [DefaultValue(TabAppearance.Normal)]
142 public TabAppearance Appearance {
143 get { return appearance; }
145 if (appearance == value)
153 [EditorBrowsable(EditorBrowsableState.Never)]
154 public override Color BackColor {
155 get { return ThemeEngine.Current.ColorControl; }
156 set { /* nothing happens on set on MS */ }
160 [EditorBrowsable(EditorBrowsableState.Never)]
161 public override Image BackgroundImage {
162 get { return base.BackgroundImage; }
163 set { base.BackgroundImage = value; }
167 [EditorBrowsable (EditorBrowsableState.Never)]
168 public override ImageLayout BackgroundImageLayout {
169 get { return base.BackgroundImageLayout; }
170 set { base.BackgroundImageLayout = value; }
173 public override Rectangle DisplayRectangle {
175 return ThemeEngine.Current.TabControlGetDisplayRectangle (this);
179 [EditorBrowsable (EditorBrowsableState.Never)]
180 protected override bool DoubleBuffered {
181 get { return base.DoubleBuffered; }
182 set { base.DoubleBuffered = value; }
185 [DefaultValue(TabDrawMode.Normal)]
186 public TabDrawMode DrawMode {
187 get { return draw_mode; }
189 if (draw_mode == value)
197 [EditorBrowsable(EditorBrowsableState.Never)]
198 public override Color ForeColor {
199 get { return base.ForeColor; }
200 set { base.ForeColor = value; }
203 [DefaultValue(false)]
204 public bool HotTrack {
205 get { return hottrack; }
207 if (hottrack == value)
214 [RefreshProperties (RefreshProperties.Repaint)]
216 public ImageList ImageList {
217 get { return image_list; }
225 public Size ItemSize {
227 if (item_size_manual)
230 if (!IsHandleCreated)
233 Size size = item_size;
234 if (SizeMode != TabSizeMode.Fixed) {
235 size.Width += padding.X * 2;
236 size.Height += padding.Y * 2;
239 if (tab_pages.Count == 0)
245 if (value.Height < 0 || value.Width < 0)
246 throw new ArgumentException ("'" + value + "' is not a valid value for 'ItemSize'.");
248 item_size_manual = true;
253 [DefaultValue(false)]
254 public bool Multiline {
255 get { return multiline; }
257 if (multiline == value)
260 if (!multiline && alignment == TabAlignment.Left || alignment == TabAlignment.Right)
261 alignment = TabAlignment.Top;
270 get { return padding; }
272 if (value.X < 0 || value.Y < 0)
273 throw new ArgumentException ("'" + value + "' is not a valid value for 'Padding'.");
274 if (padding == value)
282 [MonoTODO ("RTL not supported")]
284 [DefaultValue (false)]
285 public virtual bool RightToLeftLayout {
286 get { return this.rightToLeftLayout; }
288 if (value != this.rightToLeftLayout) {
289 this.rightToLeftLayout = value;
290 this.OnRightToLeftLayoutChanged (EventArgs.Empty);
296 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
297 public int RowCount {
298 get { return row_count; }
303 public int SelectedIndex {
304 get { return selected_index; }
308 throw new ArgumentOutOfRangeException ("SelectedIndex", "Value of '" + value + "' is valid for 'SelectedIndex'. " +
309 "'SelectedIndex' must be greater than or equal to -1.");
311 if (!this.IsHandleCreated) {
312 if (selected_index != value) {
313 selected_index = value;
318 if (value >= TabCount) {
319 if (value != selected_index)
320 OnSelectedIndexChanged (EventArgs.Empty);
324 if (value == selected_index) {
325 if (selected_index > -1)
326 Invalidate(GetTabRect (selected_index));
330 TabControlCancelEventArgs ret = new TabControlCancelEventArgs (SelectedTab, selected_index, false, TabControlAction.Deselecting);
336 int old_index = selected_index;
337 int new_index = value;
339 selected_index = new_index;
341 ret = new TabControlCancelEventArgs (SelectedTab, selected_index, false, TabControlAction.Selecting);
344 selected_index = old_index;
350 Rectangle invalid = Rectangle.Empty;
351 bool refresh = false;
353 if (new_index != -1 && show_slider && new_index < slider_pos) {
354 slider_pos = new_index;
358 if (new_index != -1) {
359 int le = TabPages[new_index].TabBounds.Right;
360 int re = LeftScrollButtonArea.Left;
361 if (show_slider && le > re) {
363 for (i = 0; i < new_index; i++) {
364 if (TabPages [i].TabBounds.Left < 0) // tab scrolled off the visible area, ignore
367 if (TabPages [new_index].TabBounds.Right - TabPages[i].TabBounds.Right < re) {
377 if (old_index != -1 && new_index != -1) {
379 invalid = GetTabRect (old_index);
380 ((TabPage) Controls[old_index]).SetVisible (false);
383 TabPage selected = null;
385 if (new_index != -1) {
386 selected = (TabPage) Controls[new_index];
387 invalid = Rectangle.Union (invalid, GetTabRect (new_index));
388 selected.SetVisible (true);
391 OnSelectedIndexChanged (EventArgs.Empty);
398 } else if (new_index != -1 && selected.Row != BottomRow) {
399 DropRow (TabPages[new_index].Row);
400 // calculating what to invalidate here seems to be slower then just
401 // refreshing the whole thing
406 // The lines are drawn on the edges of the tabs so the invalid area should
407 // needs to include the extra pixels of line width (but should not
408 // overflow the control bounds).
409 if (appearance == TabAppearance.Normal) {
410 invalid.Inflate (6, 4);
411 invalid.Intersect (ClientRectangle);
413 Invalidate (invalid);
419 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
420 public TabPage SelectedTab {
422 if (selected_index == -1)
424 return tab_pages [selected_index];
427 int index = IndexForTabPage (value);
428 if (index == selected_index)
430 SelectedIndex = index;
434 [DefaultValue(false)]
436 public bool ShowToolTips {
437 get { return show_tool_tips; }
439 if (show_tool_tips == value)
441 show_tool_tips = value;
445 [DefaultValue(TabSizeMode.Normal)]
446 [RefreshProperties(RefreshProperties.Repaint)]
447 public TabSizeMode SizeMode {
448 get { return size_mode; }
450 if (size_mode == value)
458 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
459 public int TabCount {
461 return tab_pages.Count;
465 [Editor ("System.Windows.Forms.Design.TabPageCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
466 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
467 [MergableProperty(false)]
468 public TabPageCollection TabPages {
469 get { return tab_pages; }
474 [EditorBrowsable(EditorBrowsableState.Never)]
475 public override string Text {
476 get { return base.Text; }
477 set { base.Text = value; }
479 #endregion // Public Instance Properties
481 #region Internal Properties
482 internal bool ShowSlider {
483 get { return show_slider; }
487 // UIA Framework Event: HorizontallyScrollable Changed
488 OnUIAHorizontallyScrollableChanged (EventArgs.Empty);
492 internal int SliderPos {
493 get { return slider_pos; }
496 internal PushButtonState RightSliderState {
497 get { return right_slider_state; }
499 if (right_slider_state == value)
501 PushButtonState old_value = right_slider_state;
502 right_slider_state = value;
503 if (NeedsToInvalidateScrollButton (old_value, value))
504 Invalidate (RightScrollButtonArea);
508 internal PushButtonState LeftSliderState {
509 get { return left_slider_state; }
511 if (left_slider_state == value)
513 PushButtonState old_value = left_slider_state;
514 left_slider_state = value;
515 if (NeedsToInvalidateScrollButton (old_value, value))
516 Invalidate (LeftScrollButtonArea);
520 bool NeedsToInvalidateScrollButton (PushButtonState oldState, PushButtonState newState)
522 if ((oldState == PushButtonState.Hot && newState == PushButtonState.Normal) ||
523 (oldState == PushButtonState.Normal && newState == PushButtonState.Hot))
524 return HasHotElementStyles;
528 internal TabPage EnteredTabPage {
529 get { return entered_tab_page; }
531 if (entered_tab_page == value)
533 if (HasHotElementStyles) {
534 Region area_to_invalidate = new Region ();
535 area_to_invalidate.MakeEmpty ();
536 if (entered_tab_page != null)
537 area_to_invalidate.Union (entered_tab_page.TabBounds);
538 entered_tab_page = value;
539 if (entered_tab_page != null)
540 area_to_invalidate.Union (entered_tab_page.TabBounds);
541 Invalidate (area_to_invalidate);
542 area_to_invalidate.Dispose ();
544 entered_tab_page = value;
549 SetToolTip (GetToolTipText (value));
552 #endregion // Internal Properties
554 #region Protected Instance Properties
555 protected override CreateParams CreateParams {
557 CreateParams c = base.CreateParams;
562 protected override Size DefaultSize {
563 get { return new Size (200, 100); }
566 #endregion // Protected Instance Properties
568 #region Public Instance Methods
569 public Rectangle GetTabRect (int index)
571 TabPage page = GetTab (index);
572 return page.TabBounds;
575 public Control GetControl (int index)
577 return GetTab (index);
580 public void SelectTab (TabPage tabPage)
583 throw new ArgumentNullException ("tabPage");
585 SelectTab (this.tab_pages [tabPage]);
588 public void SelectTab (string tabPageName)
590 if (tabPageName == null)
591 throw new ArgumentNullException ("tabPageName");
593 SelectTab (this.tab_pages [tabPageName]);
596 public void SelectTab (int index)
598 if (index < 0 || index > this.tab_pages.Count - 1)
599 throw new ArgumentOutOfRangeException ("index");
601 SelectedIndex = index;
604 public void DeselectTab (TabPage tabPage)
607 throw new ArgumentNullException ("tabPage");
609 DeselectTab (this.tab_pages [tabPage]);
612 public void DeselectTab (string tabPageName)
614 if (tabPageName == null)
615 throw new ArgumentNullException ("tabPageName");
617 DeselectTab (this.tab_pages [tabPageName]);
620 public void DeselectTab (int index)
622 if (index == SelectedIndex) {
623 if (index >= 0 && index < this.tab_pages.Count - 1)
624 SelectedIndex = ++index;
631 public override string ToString ()
633 string res = String.Concat (base.ToString (),
634 ", TabPages.Count: ",
637 res = String.Concat (res, ", TabPages[0]: ",
642 #endregion // Public Instance Methods
644 #region Protected Instance Methods
647 protected override Control.ControlCollection CreateControlsInstance ()
649 return new TabControl.ControlCollection (this);
652 protected override void CreateHandle ()
654 base.CreateHandle ();
655 selected_index = (selected_index >= TabCount ? (TabCount > 0 ? 0 : -1) : selected_index);
658 if (selected_index > -1)
659 this.SelectedTab.SetVisible(true);
661 tab_pages[0].SetVisible(true);
666 protected override void OnHandleCreated (EventArgs e)
668 base.OnHandleCreated (e);
671 protected override void OnHandleDestroyed (EventArgs e)
673 base.OnHandleDestroyed (e);
676 protected override void Dispose (bool disposing)
679 base.Dispose (disposing);
685 protected virtual void OnDrawItem (DrawItemEventArgs e)
687 if (DrawMode != TabDrawMode.OwnerDrawFixed)
690 DrawItemEventHandler eh = (DrawItemEventHandler)(Events [DrawItemEvent]);
695 internal void OnDrawItemInternal (DrawItemEventArgs e)
700 protected override void OnFontChanged (EventArgs e)
702 base.OnFontChanged (e);
706 protected override void OnResize (EventArgs e)
711 protected override void OnStyleChanged (EventArgs e)
713 base.OnStyleChanged (e);
716 protected virtual void OnSelectedIndexChanged (EventArgs e)
718 EventHandler eh = (EventHandler) (Events[SelectedIndexChangedEvent]);
723 internal override void OnPaintInternal (PaintEventArgs pe)
725 if (GetStyle (ControlStyles.UserPaint))
728 Draw (pe.Graphics, pe.ClipRectangle);
732 protected override void OnEnter (EventArgs e)
735 if (SelectedTab != null)
736 SelectedTab.FireEnter ();
739 protected override void OnLeave (EventArgs e)
741 if (SelectedTab != null)
742 SelectedTab.FireLeave ();
746 [EditorBrowsable (EditorBrowsableState.Advanced)]
747 protected virtual void OnRightToLeftLayoutChanged (EventArgs e)
749 EventHandler eh = (EventHandler) (Events[RightToLeftLayoutChangedEvent]);
754 [EditorBrowsable (EditorBrowsableState.Never)]
755 protected override void ScaleCore (float dx, float dy)
757 base.ScaleCore (dx, dy);
760 protected virtual void OnDeselecting (TabControlCancelEventArgs e)
762 TabControlCancelEventHandler eh = (TabControlCancelEventHandler) (Events[DeselectingEvent]);
767 OnDeselected (new TabControlEventArgs (SelectedTab, selected_index, TabControlAction.Deselected));
770 protected virtual void OnDeselected (TabControlEventArgs e)
772 TabControlEventHandler eh = (TabControlEventHandler) (Events[DeselectedEvent]);
776 if (this.SelectedTab != null)
777 this.SelectedTab.FireLeave ();
780 protected virtual void OnSelecting (TabControlCancelEventArgs e)
782 TabControlCancelEventHandler eh = (TabControlCancelEventHandler) (Events[SelectingEvent]);
787 OnSelected (new TabControlEventArgs (SelectedTab, selected_index, TabControlAction.Selected));
790 protected virtual void OnSelected (TabControlEventArgs e)
792 TabControlEventHandler eh = (TabControlEventHandler) (Events[SelectedEvent]);
796 if (this.SelectedTab != null)
797 this.SelectedTab.FireEnter ();
803 protected override bool ProcessKeyPreview (ref Message m)
805 return base.ProcessKeyPreview (ref m);
808 protected override void OnKeyDown (KeyEventArgs ke)
814 if (ke.KeyCode == Keys.Tab && (ke.KeyData & Keys.Control) != 0) {
815 if ((ke.KeyData & Keys.Shift) == 0)
816 SelectedIndex = (SelectedIndex + 1) % TabCount;
818 SelectedIndex = (SelectedIndex + TabCount - 1) % TabCount;
820 } else if (ke.KeyCode == Keys.Home) {
823 } else if (ke.KeyCode == Keys.End) {
824 SelectedIndex = TabCount - 1;
826 } else if (NavigateTabs (ke.KeyCode))
830 protected override bool IsInputKey (Keys keyData)
832 switch (keyData & Keys.KeyCode) {
841 return base.IsInputKey (keyData);
844 private bool NavigateTabs (Keys keycode)
846 bool move_left = false;
847 bool move_right = false;
849 if (alignment == TabAlignment.Bottom || alignment == TabAlignment.Top) {
850 if (keycode == Keys.Left)
852 else if (keycode == Keys.Right)
855 if (keycode == Keys.Up)
857 else if (keycode == Keys.Down)
862 if (SelectedIndex > 0) {
869 if (SelectedIndex < TabCount - 1) {
879 #region Pages Collection
880 protected void RemoveAll ()
885 protected virtual object [] GetItems ()
887 TabPage [] pages = new TabPage [Controls.Count];
888 Controls.CopyTo (pages, 0);
892 protected virtual object [] GetItems (Type baseType)
894 object[] pages = (object[])Array.CreateInstance (baseType, Controls.Count);
895 Controls.CopyTo (pages, 0);
900 protected void UpdateTabSelection (bool updateFocus)
905 protected string GetToolTipText (object item)
907 TabPage page = (TabPage) item;
908 return page.ToolTipText;
911 protected override void WndProc (ref Message m)
913 switch ((Msg)m.Msg) {
914 case Msg.WM_SETFOCUS:
915 if (selected_index != -1)
916 Invalidate(GetTabRect(selected_index));
917 base.WndProc (ref m);
919 case Msg.WM_KILLFOCUS:
920 if (selected_index != -1)
921 Invalidate(GetTabRect(selected_index));
922 base.WndProc (ref m);
925 base.WndProc (ref m);
930 #endregion // Protected Instance Methods
932 #region Internal & Private Methods
933 private bool CanScrollRight {
935 return (slider_pos < TabCount - 1);
939 private bool CanScrollLeft {
940 get { return slider_pos > 0; }
943 private void MouseDownHandler (object sender, MouseEventArgs e)
945 if ((e.Button & MouseButtons.Left) == 0)
949 Rectangle right = RightScrollButtonArea;
950 Rectangle left = LeftScrollButtonArea;
951 if (right.Contains (e.X, e.Y)) {
952 right_slider_state = PushButtonState.Pressed;
953 if (CanScrollRight) {
957 // UIA Framework Event: Horizontally Scrolled
958 OnUIAHorizontallyScrolled (EventArgs.Empty);
960 switch (this.Alignment) {
961 case TabAlignment.Top:
962 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
964 case TabAlignment.Bottom:
965 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
967 case TabAlignment.Left:
968 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
970 case TabAlignment.Right:
971 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
979 } else if (left.Contains (e.X, e.Y)) {
980 left_slider_state = PushButtonState.Pressed;
985 // UIA Framework Event: Horizontally Scrolled
986 OnUIAHorizontallyScrolled (EventArgs.Empty);
988 switch (this.Alignment) {
989 case TabAlignment.Top:
990 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
992 case TabAlignment.Bottom:
993 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
995 case TabAlignment.Left:
996 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
998 case TabAlignment.Right:
999 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
1009 int count = Controls.Count;
1010 for (int i = SliderPos; i < count; i++) {
1011 if (!GetTabRect (i).Contains (e.X, e.Y))
1014 mouse_down_on_a_tab_page = true;
1019 private void MouseUpHandler (object sender, MouseEventArgs e)
1021 mouse_down_on_a_tab_page = false;
1022 if (ShowSlider && (left_slider_state == PushButtonState.Pressed || right_slider_state == PushButtonState.Pressed)) {
1024 if (left_slider_state == PushButtonState.Pressed) {
1025 invalid = LeftScrollButtonArea;
1026 left_slider_state = GetScrollButtonState (invalid, e.Location);
1028 invalid = RightScrollButtonArea;
1029 right_slider_state = GetScrollButtonState (invalid, e.Location);
1031 Invalidate (invalid);
1035 bool HasHotElementStyles {
1037 return ThemeElements.CurrentTheme.TabControlPainter.HasHotElementStyles (this);
1041 Rectangle LeftScrollButtonArea {
1043 return ThemeElements.CurrentTheme.TabControlPainter.GetLeftScrollRect (this);
1047 Rectangle RightScrollButtonArea {
1049 return ThemeElements.CurrentTheme.TabControlPainter.GetRightScrollRect (this);
1053 static PushButtonState GetScrollButtonState (Rectangle scrollButtonArea, Point cursorLocation)
1055 return scrollButtonArea.Contains (cursorLocation) ? PushButtonState.Hot : PushButtonState.Normal;
1058 private void SizeChangedHandler (object sender, EventArgs e)
1063 internal int IndexForTabPage (TabPage page)
1065 for (int i = 0; i < tab_pages.Count; i++) {
1066 if (page == tab_pages [i])
1072 private void ResizeTabPages ()
1076 Rectangle r = DisplayRectangle;
1077 foreach (TabPage page in Controls) {
1082 private int MinimumTabWidth {
1084 return ThemeEngine.Current.TabControlMinimumTabWidth;
1088 private Size TabSpacing {
1090 return ThemeEngine.Current.TabControlGetSpacing (this);
1094 private void CalcTabRows ()
1096 switch (Alignment) {
1097 case TabAlignment.Right:
1098 case TabAlignment.Left:
1099 CalcTabRows (Height);
1102 CalcTabRows (Width);
1107 private void CalcTabRows (int row_width)
1111 Size spacing = TabSpacing;
1113 if (TabPages.Count > 0)
1115 show_slider = false;
1117 CalculateItemSize ();
1119 for (int i = 0; i < TabPages.Count; i++) {
1120 TabPage page = TabPages [i];
1122 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, 0, ref aux, true);
1125 if (SelectedIndex != -1 && TabPages.Count > SelectedIndex && TabPages[SelectedIndex].Row != BottomRow)
1126 DropRow (TabPages [SelectedIndex].Row);
1129 // ItemSize per-se is used mostly only to retrieve the Height,
1130 // since the actual Width of the tabs is computed individually,
1131 // except when SizeMode is TabSizeMode.Fixed, where Width is used as well.
1132 private void CalculateItemSize ()
1134 if (item_size_manual)
1138 if (tab_pages.Count > 0) {
1139 // .Net uses the first tab page if available.
1140 size = TextRenderer.MeasureString (tab_pages [0].Text, Font);
1143 size = TextRenderer.MeasureString ("a", Font);
1147 if (size_mode == TabSizeMode.Fixed)
1149 if (size.Width < MinimumTabWidth)
1150 size.Width = MinimumTabWidth;
1151 if (image_list != null && image_list.ImageSize.Height > size.Height)
1152 size.Height = image_list.ImageSize.Height;
1154 item_size = size.ToSize ();
1157 private int BottomRow {
1161 private int Direction
1168 private void DropRow (int row)
1170 if (Appearance != TabAppearance.Normal)
1173 int bottom = BottomRow;
1174 int direction = Direction;
1176 foreach (TabPage page in TabPages) {
1177 if (page.Row == row) {
1179 } else if (direction == 1 && page.Row < row) {
1180 page.Row += direction;
1181 } else if (direction == -1 && page.Row > row) {
1182 page.Row += direction;
1187 private int CalcYPos ()
1189 if (Alignment == TabAlignment.Bottom || Alignment == TabAlignment.Left)
1190 return ThemeEngine.Current.TabControlGetPanelRect (this).Bottom;
1192 if (Appearance == TabAppearance.Normal)
1193 return this.ClientRectangle.Y + ThemeEngine.Current.TabControlSelectedDelta.Y;
1195 return this.ClientRectangle.Y;
1199 private int CalcXPos ()
1201 if (Alignment == TabAlignment.Right)
1202 return ThemeEngine.Current.TabControlGetPanelRect (this).Right;
1204 if (Appearance == TabAppearance.Normal)
1205 return this.ClientRectangle.X + ThemeEngine.Current.TabControlSelectedDelta.X;
1207 return this.ClientRectangle.X;
1210 private void SizeTabs ()
1212 switch (Alignment) {
1213 case TabAlignment.Right:
1214 case TabAlignment.Left:
1215 SizeTabs (Height, true);
1218 SizeTabs (Width, false);
1223 private void SizeTabs (int row_width, bool vertical)
1228 Size spacing = TabSpacing;
1231 if (TabPages.Count == 0)
1234 prev_row = TabPages [0].Row;
1236 // Reset the slider position if the slider isn't needed
1237 // anymore (ie window size was increased so all tabs are visible)
1241 // set X = -1 for marking tabs that are not visible due to scrolling
1242 for (int i = 0; i < slider_pos; i++) {
1243 TabPage page = TabPages[i];
1244 Rectangle x = page.TabBounds;
1250 for (int i = slider_pos; i < TabPages.Count; i++) {
1251 TabPage page = TabPages[i];
1252 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, prev_row, ref begin_prev, false);
1253 prev_row = page.Row;
1256 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1257 FillRow (begin_prev, TabPages.Count - 1,
1258 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)),
1262 if (SelectedIndex != -1) {
1263 ExpandSelected (TabPages [SelectedIndex], 0, row_width - 1);
1267 private void SizeTab (TabPage page, int i, int row_width, ref int xpos, ref int ypos,
1268 Size spacing, int prev_row, ref int begin_prev, bool widthOnly)
1270 int width, height = 0;
1272 if (SizeMode == TabSizeMode.Fixed) {
1273 width = item_size.Width;
1275 width = MeasureStringWidth (DeviceContext, page.Text, Font);
1276 width += (Padding.X * 2) + 2;
1278 if (ImageList != null && page.ImageIndex >= 0) {
1279 width += ImageList.ImageSize.Width + ThemeEngine.Current.TabControlImagePadding.X;
1281 int image_size = ImageList.ImageSize.Height + ThemeEngine.Current.TabControlImagePadding.Y;
1282 if (item_size.Height < image_size)
1283 item_size.Height = image_size;
1286 if (width < MinimumTabWidth)
1287 width = MinimumTabWidth;
1290 // Use ItemSize property to recover the padding info as well.
1291 height = ItemSize.Height - ThemeEngine.Current.TabControlSelectedDelta.Height; // full height only for selected tab
1293 if (i == SelectedIndex)
1294 width += ThemeEngine.Current.TabControlSelectedSpacing;
1297 page.TabBounds = new Rectangle (xpos, 0, width, 0);
1298 page.Row = row_count;
1299 if (xpos + width > row_width && multiline) {
1302 } else if (xpos + width > row_width) {
1305 if (i == selected_index && show_slider) {
1306 for (int j = i-1; j >= 0; j--) {
1307 if (TabPages [j].TabBounds.Left < xpos + width - row_width) {
1314 if (page.Row != prev_row) {
1318 switch (Alignment) {
1319 case TabAlignment.Top:
1320 case TabAlignment.Bottom:
1321 page.TabBounds = new Rectangle (
1323 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (),
1327 case TabAlignment.Left:
1328 if (Appearance == TabAppearance.Normal) {
1329 // tab rows are positioned right to left
1330 page.TabBounds = new Rectangle (
1331 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1336 // tab rows are positioned left to right
1337 page.TabBounds = new Rectangle (
1338 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1345 case TabAlignment.Right:
1346 if (Appearance == TabAppearance.Normal) {
1347 // tab rows are positioned left to right
1348 page.TabBounds = new Rectangle (
1349 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1354 // tab rows are positioned right to left
1355 page.TabBounds = new Rectangle (
1356 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1365 if (page.Row != prev_row) {
1366 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1367 bool vertical = alignment == TabAlignment.Right || alignment == TabAlignment.Left;
1368 int offset = vertical ? TabPages [i - 1].TabBounds.Bottom : TabPages [i - 1].TabBounds.Right;
1369 FillRow (begin_prev, i - 1, ((row_width - offset) / (i - begin_prev)), spacing,
1376 xpos += width + spacing.Width + ThemeEngine.Current.TabControlColSpacing;
1379 private void FillRow (int start, int end, int amount, Size spacing, bool vertical)
1382 FillRowV (start, end, amount, spacing);
1384 FillRow (start, end, amount, spacing);
1387 private void FillRow (int start, int end, int amount, Size spacing)
1389 int xpos = TabPages [start].TabBounds.Left;
1390 for (int i = start; i <= end; i++) {
1391 TabPage page = TabPages [i];
1393 int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount);
1395 page.TabBounds = new Rectangle (left, page.TabBounds.Top,
1396 width, page.TabBounds.Height);
1397 xpos = page.TabBounds.Right + 1 + spacing.Width;
1401 private void FillRowV (int start, int end, int amount, Size spacing)
1403 int ypos = TabPages [start].TabBounds.Top;
1404 for (int i = start; i <= end; i++) {
1405 TabPage page = TabPages [i];
1407 int height = (i == end ? Height - top - 5 : page.TabBounds.Height + amount);
1409 page.TabBounds = new Rectangle (page.TabBounds.Left, top,
1410 page.TabBounds.Width, height);
1411 ypos = page.TabBounds.Bottom + 1;
1415 private void ExpandSelected (TabPage page, int left_edge, int right_edge)
1417 if (Appearance != TabAppearance.Normal)
1420 Rectangle r = page.TabBounds;
1421 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1422 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1424 r.Width += ThemeEngine.Current.TabControlSelectedDelta.Width;
1425 r.Height += ThemeEngine.Current.TabControlSelectedDelta.Height;
1426 if (r.Left < left_edge)
1428 // Adjustment can't be used for right alignment, since it is
1429 // the only one that has a different X origin than 0
1430 if (r.Right > right_edge && SizeMode != TabSizeMode.Normal &&
1431 alignment != TabAlignment.Right)
1432 r.Width = right_edge - r.X;
1436 private void Draw (Graphics dc, Rectangle clip)
1438 ThemeEngine.Current.DrawTabControl (dc, clip, this);
1441 private TabPage GetTab (int index)
1443 return Controls [index] as TabPage;
1446 private void SetTab (int index, TabPage value)
1448 if (!tab_pages.Contains (value)) {
1449 this.Controls.Add (value);
1451 this.Controls.RemoveAt (index);
1452 this.Controls.SetChildIndex (value, index);
1455 private void InsertTab (int index, TabPage value)
1457 if (!tab_pages.Contains (value)) {
1458 this.Controls.Add (value);
1460 this.Controls.SetChildIndex (value, index);
1463 internal void Redraw ()
1465 if (!IsHandleCreated)
1472 private int MeasureStringWidth (Graphics graphics, string text, Font font)
1474 if (text == String.Empty)
1476 StringFormat format = new StringFormat();
1477 RectangleF rect = new RectangleF(0, 0, 1000, 1000);
1478 CharacterRange[] ranges = { new CharacterRange(0, text.Length) };
1479 Region[] regions = new Region[1];
1481 format.SetMeasurableCharacterRanges(ranges);
1482 format.FormatFlags = StringFormatFlags.NoClip;
1483 format.FormatFlags |= StringFormatFlags.NoWrap;
1484 regions = graphics.MeasureCharacterRanges(text + "I", font, rect, format);
1485 rect = regions[0].GetBounds(graphics);
1487 return (int)(rect.Width);
1490 void SetToolTip (string text)
1492 if (!show_tool_tips)
1495 if (text == null || text.Length == 0) {
1500 if (tooltip == null) {
1501 tooltip = new ToolTip ();
1502 tooltip_timer = new Timer ();
1503 tooltip_timer.Tick += new EventHandler (ToolTipTimerTick);
1508 tooltip_state = ToolTip.TipState.Initial;
1509 tooltip_timer.Interval = 500;
1510 tooltip_timer.Start ();
1513 void CloseToolTip ()
1515 if (tooltip == null)
1518 tooltip.Hide (this);
1519 tooltip_timer.Stop ();
1520 tooltip_state = ToolTip.TipState.Down;
1523 void ToolTipTimerTick (object o, EventArgs args)
1525 switch (tooltip_state) {
1526 case ToolTip.TipState.Initial:
1527 tooltip_timer.Stop ();
1528 tooltip_timer.Interval = 5000;
1529 tooltip_timer.Start ();
1530 tooltip_state = ToolTip.TipState.Show;
1531 tooltip.Present (this, GetToolTipText (EnteredTabPage));
1533 case ToolTip.TipState.Show:
1539 void OnMouseMove (object sender, MouseEventArgs e)
1541 if (!mouse_down_on_a_tab_page && ShowSlider) {
1542 if (LeftSliderState == PushButtonState.Pressed ||
1543 RightSliderState == PushButtonState.Pressed)
1545 if (LeftScrollButtonArea.Contains (e.Location)) {
1546 LeftSliderState = PushButtonState.Hot;
1547 RightSliderState = PushButtonState.Normal;
1548 EnteredTabPage = null;
1551 if (RightScrollButtonArea.Contains (e.Location)) {
1552 RightSliderState = PushButtonState.Hot;
1553 LeftSliderState = PushButtonState.Normal;
1554 EnteredTabPage = null;
1557 LeftSliderState = PushButtonState.Normal;
1558 RightSliderState = PushButtonState.Normal;
1560 if (EnteredTabPage != null && EnteredTabPage.TabBounds.Contains (e.Location))
1562 for (int index = 0; index < TabCount; index++) {
1563 TabPage tab_page = TabPages[index];
1564 if (tab_page.TabBounds.Contains (e.Location)) {
1565 EnteredTabPage = tab_page;
1569 EnteredTabPage = null;
1572 void OnMouseLeave (object sender, EventArgs e)
1575 LeftSliderState = PushButtonState.Normal;
1576 RightSliderState = PushButtonState.Normal;
1578 EnteredTabPage = null;
1580 #endregion // Internal & Private Methods
1584 [EditorBrowsable(EditorBrowsableState.Never)]
1585 public new event EventHandler BackColorChanged {
1586 add { base.BackColorChanged += value; }
1587 remove { base.BackColorChanged -= value; }
1591 [EditorBrowsable(EditorBrowsableState.Never)]
1592 public new event EventHandler BackgroundImageChanged {
1593 add { base.BackgroundImageChanged += value; }
1594 remove { base.BackgroundImageChanged -= value; }
1598 [EditorBrowsable (EditorBrowsableState.Never)]
1599 public new event EventHandler BackgroundImageLayoutChanged
1601 add { base.BackgroundImageLayoutChanged += value; }
1602 remove { base.BackgroundImageLayoutChanged -= value; }
1606 [EditorBrowsable(EditorBrowsableState.Never)]
1607 public new event EventHandler ForeColorChanged {
1608 add { base.ForeColorChanged += value; }
1609 remove { base.ForeColorChanged -= value; }
1613 [EditorBrowsable(EditorBrowsableState.Never)]
1614 public new event PaintEventHandler Paint {
1615 add { base.Paint += value; }
1616 remove { base.Paint -= value; }
1620 [EditorBrowsable(EditorBrowsableState.Never)]
1621 public new event EventHandler TextChanged {
1622 add { base.TextChanged += value; }
1623 remove { base.TextChanged -= value; }
1626 static object DrawItemEvent = new object ();
1627 static object SelectedIndexChangedEvent = new object ();
1629 public event DrawItemEventHandler DrawItem {
1630 add { Events.AddHandler (DrawItemEvent, value); }
1631 remove { Events.RemoveHandler (DrawItemEvent, value); }
1634 public event EventHandler SelectedIndexChanged {
1635 add { Events.AddHandler (SelectedIndexChangedEvent, value); }
1636 remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); }
1639 static object SelectedEvent = new object ();
1641 public event TabControlEventHandler Selected {
1642 add { Events.AddHandler (SelectedEvent, value); }
1643 remove { Events.RemoveHandler (SelectedEvent, value); }
1646 static object DeselectedEvent = new object ();
1648 public event TabControlEventHandler Deselected
1650 add { Events.AddHandler (DeselectedEvent, value); }
1651 remove { Events.RemoveHandler (DeselectedEvent, value); }
1654 static object SelectingEvent = new object ();
1656 public event TabControlCancelEventHandler Selecting
1658 add { Events.AddHandler (SelectingEvent, value); }
1659 remove { Events.RemoveHandler (SelectingEvent, value); }
1662 static object DeselectingEvent = new object ();
1664 public event TabControlCancelEventHandler Deselecting
1666 add { Events.AddHandler (DeselectingEvent, value); }
1667 remove { Events.RemoveHandler (DeselectingEvent, value); }
1670 static object RightToLeftLayoutChangedEvent = new object ();
1671 public event EventHandler RightToLeftLayoutChanged
1673 add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); }
1674 remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); }
1676 #endregion // Events
1679 #region Class TaControl.ControlCollection
1680 [ComVisible (false)]
1681 public new class ControlCollection : System.Windows.Forms.Control.ControlCollection {
1683 private TabControl owner;
1685 public ControlCollection (TabControl owner) : base (owner)
1690 public override void Add (Control value)
1692 TabPage page = value as TabPage;
1694 throw new ArgumentException ("Cannot add " +
1695 value.GetType ().Name + " to TabControl. " +
1696 "Only TabPages can be directly added to TabControls.");
1698 page.SetVisible (false);
1700 if (owner.TabCount == 1 && owner.selected_index < 0)
1701 owner.SelectedIndex = 0;
1705 public override void Remove (Control value)
1707 bool change_index = false;
1709 TabPage page = value as TabPage;
1710 if (page != null && owner.Controls.Contains (page)) {
1711 int index = owner.IndexForTabPage (page);
1712 if (index < owner.SelectedIndex || owner.SelectedIndex == Count - 1)
1713 change_index = true;
1716 base.Remove (value);
1718 // We don't want to raise SelectedIndexChanged until after we
1719 // have removed from the collection, so TabCount will be
1720 // correct for the user.
1721 if (change_index && Count > 0) {
1722 // Clear the selected index internally, to avoid trying to access the previous
1723 // selected tab when setting the new one - this is what .net seems to do
1724 int prev_selected_index = owner.SelectedIndex;
1725 owner.selected_index = -1;
1727 owner.SelectedIndex = --prev_selected_index;
1728 owner.Invalidate ();
1729 } else if (change_index) {
1730 owner.selected_index = -1;
1731 owner.OnSelectedIndexChanged (EventArgs.Empty);
1732 owner.Invalidate ();
1737 #endregion // Class TabControl.ControlCollection
1739 #region Class TabPage.TabPageCollection
1740 public class TabPageCollection : IList, ICollection, IEnumerable {
1742 private TabControl owner;
1744 public TabPageCollection (TabControl owner)
1747 throw new ArgumentNullException ("Value cannot be null.");
1753 get { return owner.Controls.Count; }
1756 public bool IsReadOnly {
1757 get { return false; }
1760 public virtual TabPage this [int index] {
1762 return owner.GetTab (index);
1765 owner.SetTab (index, value);
1768 public virtual TabPage this [string key] {
1770 if (string.IsNullOrEmpty (key))
1773 int index = this.IndexOfKey (key);
1774 if (index < 0 || index >= this.Count)
1781 internal int this[TabPage tabPage] {
1783 if (tabPage == null)
1786 for (int i = 0; i < this.Count; i++)
1787 if (this[i].Equals (tabPage))
1794 bool ICollection.IsSynchronized {
1795 get { return false; }
1798 object ICollection.SyncRoot {
1799 get { return this; }
1802 bool IList.IsFixedSize {
1803 get { return false; }
1806 object IList.this [int index] {
1808 return owner.GetTab (index);
1811 owner.SetTab (index, (TabPage) value);
1815 public void Add (TabPage value)
1818 throw new ArgumentNullException ("Value cannot be null.");
1819 owner.Controls.Add (value);
1822 public void Add (string text)
1824 TabPage page = new TabPage (text);
1828 public void Add (string key, string text)
1830 TabPage page = new TabPage (text);
1835 public void Add (string key, string text, int imageIndex)
1837 TabPage page = new TabPage (text);
1839 page.ImageIndex = imageIndex;
1843 // .Net sets the ImageKey, but does not show the image when this is used
1844 public void Add (string key, string text, string imageKey)
1846 TabPage page = new TabPage (text);
1848 page.ImageKey = imageKey;
1852 public void AddRange (TabPage [] pages)
1855 throw new ArgumentNullException ("Value cannot be null.");
1856 owner.Controls.AddRange (pages);
1859 public virtual void Clear ()
1861 owner.Controls.Clear ();
1862 owner.Invalidate ();
1865 public bool Contains (TabPage page)
1868 throw new ArgumentNullException ("Value cannot be null.");
1869 return owner.Controls.Contains (page);
1872 public virtual bool ContainsKey (string key)
1874 int index = this.IndexOfKey (key);
1875 return (index >= 0 && index < this.Count);
1878 public IEnumerator GetEnumerator ()
1880 return owner.Controls.GetEnumerator ();
1883 public int IndexOf (TabPage page)
1885 return owner.Controls.IndexOf (page);
1888 public virtual int IndexOfKey(string key)
1890 if (string.IsNullOrEmpty (key))
1893 for (int i = 0; i < this.Count; i++) {
1894 if (string.Compare (this[i].Name, key, true,
1895 System.Globalization.CultureInfo.InvariantCulture) == 0) {
1903 public void Remove (TabPage value)
1905 owner.Controls.Remove (value);
1906 owner.Invalidate ();
1909 public void RemoveAt (int index)
1911 owner.Controls.RemoveAt (index);
1912 owner.Invalidate ();
1915 public virtual void RemoveByKey (string key)
1917 int index = this.IndexOfKey (key);
1918 if (index >= 0 && index < this.Count)
1919 this.RemoveAt (index);
1922 void ICollection.CopyTo (Array dest, int index)
1924 owner.Controls.CopyTo (dest, index);
1927 int IList.Add (object value)
1929 TabPage page = value as TabPage;
1931 throw new ArgumentException ("value");
1932 owner.Controls.Add (page);
1933 return owner.Controls.IndexOf (page);
1936 bool IList.Contains (object page)
1938 TabPage tabPage = page as TabPage;
1939 if (tabPage == null)
1941 return Contains (tabPage);
1944 int IList.IndexOf (object page)
1946 TabPage tabPage = page as TabPage;
1947 if (tabPage == null)
1949 return IndexOf (tabPage);
1952 void IList.Insert (int index, object tabPage)
1954 throw new NotSupportedException ();
1957 public void Insert (int index, string text)
1959 owner.InsertTab (index, new TabPage (text));
1962 public void Insert (int index, TabPage tabPage)
1964 owner.InsertTab (index, tabPage);
1967 public void Insert (int index, string key, string text)
1969 TabPage page = new TabPage(text);
1971 owner.InsertTab (index, page);
1974 public void Insert (int index, string key, string text, int imageIndex)
1976 TabPage page = new TabPage(text);
1978 owner.InsertTab (index, page);
1979 page.ImageIndex = imageIndex;
1982 public void Insert (int index, string key, string text, string imageKey)
1984 TabPage page = new TabPage(text);
1986 owner.InsertTab (index, page);
1987 page.ImageKey = imageKey;
1989 void IList.Remove (object value)
1991 TabPage page = value as TabPage;
1994 Remove ((TabPage) value);
1997 #endregion // Class TabPage.TabPageCollection