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 bool item_size_manual;
53 private Point padding;
54 private int row_count = 0;
55 private bool hottrack;
56 private TabPageCollection tab_pages;
57 private bool show_tool_tips;
58 private TabSizeMode size_mode;
59 private bool show_slider = false;
60 private PushButtonState right_slider_state = PushButtonState.Normal;
61 private PushButtonState left_slider_state = PushButtonState.Normal;
62 private int slider_pos = 0;
63 TabPage entered_tab_page;
64 bool mouse_down_on_a_tab_page;
66 ToolTip.TipState tooltip_state = ToolTip.TipState.Down;
70 private bool rightToLeftLayout;
74 #region UIA Framework Events
76 static object UIAHorizontallyScrollableChangedEvent = new object ();
78 internal event EventHandler UIAHorizontallyScrollableChanged {
79 add { Events.AddHandler (UIAHorizontallyScrollableChangedEvent, value); }
80 remove { Events.RemoveHandler (UIAHorizontallyScrollableChangedEvent, value); }
83 internal void OnUIAHorizontallyScrollableChanged (EventArgs e)
86 = (EventHandler) Events [UIAHorizontallyScrollableChangedEvent];
91 static object UIAHorizontallyScrolledEvent = new object ();
93 internal event EventHandler UIAHorizontallyScrolled {
94 add { Events.AddHandler (UIAHorizontallyScrolledEvent, value); }
95 remove { Events.RemoveHandler (UIAHorizontallyScrolledEvent, value); }
98 internal void OnUIAHorizontallyScrolled (EventArgs e)
101 = (EventHandler) Events [UIAHorizontallyScrolledEvent];
108 #region UIA Framework Property
110 internal double UIAHorizontalViewSize {
111 get { return LeftScrollButtonArea.Left * 100 / TabPages [TabCount - 1].TabBounds.Right; }
116 #region Public Constructors
119 tab_pages = new TabPageCollection (this);
120 SetStyle (ControlStyles.UserPaint, false);
121 padding = ThemeEngine.Current.TabControlDefaultPadding;
123 MouseDown += new MouseEventHandler (MouseDownHandler);
124 MouseLeave += new EventHandler (OnMouseLeave);
125 MouseMove += new MouseEventHandler (OnMouseMove);
126 MouseUp += new MouseEventHandler (MouseUpHandler);
127 SizeChanged += new EventHandler (SizeChangedHandler);
130 #endregion // Public Constructors
132 #region Public Instance Properties
133 [DefaultValue(TabAlignment.Top)]
135 [RefreshProperties(RefreshProperties.All)]
136 public TabAlignment Alignment {
137 get { return alignment; }
139 if (alignment == value)
142 if (alignment == TabAlignment.Left || alignment == TabAlignment.Right)
148 [DefaultValue(TabAppearance.Normal)]
150 public TabAppearance Appearance {
151 get { return appearance; }
153 if (appearance == value)
161 [EditorBrowsable(EditorBrowsableState.Never)]
162 public override Color BackColor {
163 get { return ThemeEngine.Current.ColorControl; }
164 set { /* nothing happens on set on MS */ }
168 [EditorBrowsable(EditorBrowsableState.Never)]
169 public override Image BackgroundImage {
170 get { return base.BackgroundImage; }
171 set { base.BackgroundImage = value; }
176 [EditorBrowsable (EditorBrowsableState.Never)]
177 public override ImageLayout BackgroundImageLayout {
178 get { return base.BackgroundImageLayout; }
179 set { base.BackgroundImageLayout = value; }
183 public override Rectangle DisplayRectangle {
185 return ThemeEngine.Current.TabControlGetDisplayRectangle (this);
190 [EditorBrowsable (EditorBrowsableState.Never)]
191 protected override bool DoubleBuffered {
192 get { return base.DoubleBuffered; }
193 set { base.DoubleBuffered = value; }
197 [DefaultValue(TabDrawMode.Normal)]
198 public TabDrawMode DrawMode {
199 get { return draw_mode; }
201 if (draw_mode == value)
209 [EditorBrowsable(EditorBrowsableState.Never)]
210 public override Color ForeColor {
211 get { return base.ForeColor; }
212 set { base.ForeColor = value; }
215 [DefaultValue(false)]
216 public bool HotTrack {
217 get { return hottrack; }
219 if (hottrack == value)
227 [RefreshProperties (RefreshProperties.Repaint)]
230 public ImageList ImageList {
231 get { return image_list; }
239 public Size ItemSize {
241 if (item_size_manual)
244 if (!IsHandleCreated)
247 Size size = item_size;
248 if (SizeMode != TabSizeMode.Fixed) {
249 size.Width += padding.X * 2;
250 size.Height += padding.Y * 2;
253 if (tab_pages.Count == 0)
259 if (value.Height < 0 || value.Width < 0)
260 throw new ArgumentException ("'" + value + "' is not a valid value for 'ItemSize'.");
262 item_size_manual = true;
267 [DefaultValue(false)]
268 public bool Multiline {
269 get { return multiline; }
271 if (multiline == value)
274 if (!multiline && alignment == TabAlignment.Left || alignment == TabAlignment.Right)
275 alignment = TabAlignment.Top;
286 get { return padding; }
288 if (value.X < 0 || value.Y < 0)
289 throw new ArgumentException ("'" + value + "' is not a valid value for 'Padding'.");
290 if (padding == value)
299 [MonoTODO ("RTL not supported")]
301 [DefaultValue (false)]
302 public virtual bool RightToLeftLayout {
303 get { return this.rightToLeftLayout; }
305 if (value != this.rightToLeftLayout) {
306 this.rightToLeftLayout = value;
307 this.OnRightToLeftLayoutChanged (EventArgs.Empty);
314 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
315 public int RowCount {
316 get { return row_count; }
321 public int SelectedIndex {
322 get { return selected_index; }
327 throw new ArgumentOutOfRangeException ("SelectedIndex", "Value of '" + value + "' is valid for 'SelectedIndex'. " +
328 "'SelectedIndex' must be greater than or equal to -1.");
330 throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
331 "'value' must be greater than or equal to -1.");
334 if (!this.IsHandleCreated) {
335 if (selected_index != value) {
336 selected_index = value;
338 OnSelectedIndexChanged (EventArgs.Empty);
344 if (value >= TabCount) {
345 if (value != selected_index)
346 OnSelectedIndexChanged (EventArgs.Empty);
350 if (value == selected_index) {
351 if (selected_index > -1)
352 Invalidate(GetTabRect (selected_index));
357 TabControlCancelEventArgs ret = new TabControlCancelEventArgs (SelectedTab, selected_index, false, TabControlAction.Deselecting);
364 int old_index = selected_index;
365 int new_index = value;
367 selected_index = new_index;
370 ret = new TabControlCancelEventArgs (SelectedTab, selected_index, false, TabControlAction.Selecting);
373 selected_index = old_index;
380 Rectangle invalid = Rectangle.Empty;
381 bool refresh = false;
383 if (new_index != -1 && show_slider && new_index < slider_pos) {
384 slider_pos = new_index;
388 if (new_index != -1) {
389 int le = TabPages[new_index].TabBounds.Right;
390 int re = LeftScrollButtonArea.Left;
391 if (show_slider && le > re) {
393 for (i = 0; i < new_index - 1; i++) {
394 if (TabPages [i].TabBounds.Left < 0) // tab scrolled off the visible area, ignore
397 if (TabPages [new_index].TabBounds.Right - TabPages[i].TabBounds.Right < re) {
407 if (old_index != -1 && new_index != -1) {
409 invalid = GetTabRect (old_index);
410 ((TabPage) Controls[old_index]).SetVisible (false);
413 TabPage selected = null;
415 if (new_index != -1) {
416 selected = (TabPage) Controls[new_index];
417 invalid = Rectangle.Union (invalid, GetTabRect (new_index));
418 selected.SetVisible (true);
421 OnSelectedIndexChanged (EventArgs.Empty);
428 } else if (new_index != -1 && selected.Row != BottomRow) {
429 DropRow (TabPages[new_index].Row);
430 // calculating what to invalidate here seems to be slower then just
431 // refreshing the whole thing
436 // The lines are drawn on the edges of the tabs so the invalid area should
437 // needs to include the extra pixels of line width (but should not
438 // overflow the control bounds).
439 if (appearance == TabAppearance.Normal) {
440 invalid.Inflate (6, 4);
441 invalid.Intersect (ClientRectangle);
443 Invalidate (invalid);
449 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
450 public TabPage SelectedTab {
452 if (selected_index == -1)
454 return tab_pages [selected_index];
457 int index = IndexForTabPage (value);
458 if (index == selected_index)
460 SelectedIndex = index;
464 [DefaultValue(false)]
466 public bool ShowToolTips {
467 get { return show_tool_tips; }
469 if (show_tool_tips == value)
471 show_tool_tips = value;
475 [DefaultValue(TabSizeMode.Normal)]
476 [RefreshProperties(RefreshProperties.Repaint)]
477 public TabSizeMode SizeMode {
478 get { return size_mode; }
480 if (size_mode == value)
488 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
489 public int TabCount {
491 return tab_pages.Count;
496 [Editor ("System.Windows.Forms.Design.TabPageCollectionEditor, " + Consts.AssemblySystem_Design, typeof (System.Drawing.Design.UITypeEditor))]
500 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
501 [MergableProperty(false)]
502 public TabPageCollection TabPages {
503 get { return tab_pages; }
508 [EditorBrowsable(EditorBrowsableState.Never)]
509 public override string Text {
510 get { return base.Text; }
511 set { base.Text = value; }
513 #endregion // Public Instance Properties
515 #region Internal Properties
516 internal bool ShowSlider {
517 get { return show_slider; }
522 // UIA Framework Event: HorizontallyScrollable Changed
523 OnUIAHorizontallyScrollableChanged (EventArgs.Empty);
528 internal int SliderPos {
529 get { return slider_pos; }
532 internal PushButtonState RightSliderState {
533 get { return right_slider_state; }
535 if (right_slider_state == value)
537 PushButtonState old_value = right_slider_state;
538 right_slider_state = value;
539 if (NeedsToInvalidateScrollButton (old_value, value))
540 Invalidate (RightScrollButtonArea);
544 internal PushButtonState LeftSliderState {
545 get { return left_slider_state; }
547 if (left_slider_state == value)
549 PushButtonState old_value = left_slider_state;
550 left_slider_state = value;
551 if (NeedsToInvalidateScrollButton (old_value, value))
552 Invalidate (LeftScrollButtonArea);
556 bool NeedsToInvalidateScrollButton (PushButtonState oldState, PushButtonState newState)
558 if ((oldState == PushButtonState.Hot && newState == PushButtonState.Normal) ||
559 (oldState == PushButtonState.Normal && newState == PushButtonState.Hot))
560 return HasHotElementStyles;
564 internal TabPage EnteredTabPage {
565 get { return entered_tab_page; }
567 if (entered_tab_page == value)
569 if (HasHotElementStyles) {
570 Region area_to_invalidate = new Region ();
571 area_to_invalidate.MakeEmpty ();
572 if (entered_tab_page != null)
573 area_to_invalidate.Union (entered_tab_page.TabBounds);
574 entered_tab_page = value;
575 if (entered_tab_page != null)
576 area_to_invalidate.Union (entered_tab_page.TabBounds);
577 Invalidate (area_to_invalidate);
578 area_to_invalidate.Dispose ();
580 entered_tab_page = value;
585 SetToolTip (GetToolTipText (value));
588 #endregion // Internal Properties
590 #region Protected Instance Properties
591 protected override CreateParams CreateParams {
593 CreateParams c = base.CreateParams;
598 protected override Size DefaultSize {
599 get { return new Size (200, 100); }
602 #endregion // Protected Instance Properties
604 #region Public Instance Methods
605 public Rectangle GetTabRect (int index)
607 TabPage page = GetTab (index);
608 return page.TabBounds;
611 public Control GetControl (int index)
613 return GetTab (index);
617 public void SelectTab (TabPage tabPage)
620 throw new ArgumentNullException ("tabPage");
622 SelectTab (this.tab_pages [tabPage]);
625 public void SelectTab (string tabPageName)
627 if (tabPageName == null)
628 throw new ArgumentNullException ("tabPageName");
630 SelectTab (this.tab_pages [tabPageName]);
633 public void SelectTab (int index)
635 if (index < 0 || index > this.tab_pages.Count - 1)
636 throw new ArgumentOutOfRangeException ("index");
638 SelectedIndex = index;
641 public void DeselectTab (TabPage tabPage)
644 throw new ArgumentNullException ("tabPage");
646 DeselectTab (this.tab_pages [tabPage]);
649 public void DeselectTab (string tabPageName)
651 if (tabPageName == null)
652 throw new ArgumentNullException ("tabPageName");
654 DeselectTab (this.tab_pages [tabPageName]);
657 public void DeselectTab (int index)
659 if (index == SelectedIndex) {
660 if (index >= 0 && index < this.tab_pages.Count - 1)
661 SelectedIndex = ++index;
669 public override string ToString ()
671 string res = String.Concat (base.ToString (),
672 ", TabPages.Count: ",
675 res = String.Concat (res, ", TabPages[0]: ",
680 #endregion // Public Instance Methods
682 #region Protected Instance Methods
685 protected override Control.ControlCollection CreateControlsInstance ()
687 return new TabControl.ControlCollection (this);
690 protected override void CreateHandle ()
692 base.CreateHandle ();
693 selected_index = (selected_index >= TabCount ? (TabCount > 0 ? 0 : -1) : selected_index);
696 if (selected_index > -1)
697 this.SelectedTab.SetVisible(true);
699 tab_pages[0].SetVisible(true);
704 protected override void OnHandleCreated (EventArgs e)
706 base.OnHandleCreated (e);
709 protected override void OnHandleDestroyed (EventArgs e)
711 base.OnHandleDestroyed (e);
714 protected override void Dispose (bool disposing)
717 base.Dispose (disposing);
723 protected virtual void OnDrawItem (DrawItemEventArgs e)
725 if (DrawMode != TabDrawMode.OwnerDrawFixed)
728 DrawItemEventHandler eh = (DrawItemEventHandler)(Events [DrawItemEvent]);
733 internal void OnDrawItemInternal (DrawItemEventArgs e)
738 protected override void OnFontChanged (EventArgs e)
740 base.OnFontChanged (e);
744 protected override void OnResize (EventArgs e)
749 protected override void OnStyleChanged (EventArgs e)
751 base.OnStyleChanged (e);
754 protected virtual void OnSelectedIndexChanged (EventArgs e)
756 EventHandler eh = (EventHandler) (Events[SelectedIndexChangedEvent]);
761 internal override void OnPaintInternal (PaintEventArgs pe)
763 if (GetStyle (ControlStyles.UserPaint))
766 Draw (pe.Graphics, pe.ClipRectangle);
771 protected override void OnEnter (EventArgs e)
774 if (SelectedTab != null)
775 SelectedTab.FireEnter ();
778 protected override void OnLeave (EventArgs e)
780 if (SelectedTab != null)
781 SelectedTab.FireLeave ();
785 [EditorBrowsable (EditorBrowsableState.Advanced)]
786 protected virtual void OnRightToLeftLayoutChanged (EventArgs e)
788 EventHandler eh = (EventHandler) (Events[RightToLeftLayoutChangedEvent]);
793 [EditorBrowsable (EditorBrowsableState.Never)]
794 protected override void ScaleCore (float dx, float dy)
796 base.ScaleCore (dx, dy);
799 protected virtual void OnDeselecting (TabControlCancelEventArgs e)
801 TabControlCancelEventHandler eh = (TabControlCancelEventHandler) (Events[DeselectingEvent]);
806 OnDeselected (new TabControlEventArgs (SelectedTab, selected_index, TabControlAction.Deselected));
809 protected virtual void OnDeselected (TabControlEventArgs e)
811 TabControlEventHandler eh = (TabControlEventHandler) (Events[DeselectedEvent]);
815 if (this.SelectedTab != null)
816 this.SelectedTab.FireLeave ();
819 protected virtual void OnSelecting (TabControlCancelEventArgs e)
821 TabControlCancelEventHandler eh = (TabControlCancelEventHandler) (Events[SelectingEvent]);
826 OnSelected (new TabControlEventArgs (SelectedTab, selected_index, TabControlAction.Selected));
829 protected virtual void OnSelected (TabControlEventArgs e)
831 TabControlEventHandler eh = (TabControlEventHandler) (Events[SelectedEvent]);
835 if (this.SelectedTab != null)
836 this.SelectedTab.FireEnter ();
843 protected override bool ProcessKeyPreview (ref Message m)
845 return base.ProcessKeyPreview (ref m);
848 protected override void OnKeyDown (KeyEventArgs ke)
854 if (ke.KeyCode == Keys.Tab && (ke.KeyData & Keys.Control) != 0) {
855 if ((ke.KeyData & Keys.Shift) == 0)
856 SelectedIndex = (SelectedIndex + 1) % TabCount;
858 SelectedIndex = (SelectedIndex + TabCount - 1) % TabCount;
860 } else if (ke.KeyCode == Keys.Home) {
863 } else if (ke.KeyCode == Keys.End) {
864 SelectedIndex = TabCount - 1;
866 } else if (NavigateTabs (ke.KeyCode))
870 protected override bool IsInputKey (Keys keyData)
872 switch (keyData & Keys.KeyCode) {
881 return base.IsInputKey (keyData);
884 private bool NavigateTabs (Keys keycode)
886 bool move_left = false;
887 bool move_right = false;
889 if (alignment == TabAlignment.Bottom || alignment == TabAlignment.Top) {
890 if (keycode == Keys.Left)
892 else if (keycode == Keys.Right)
895 if (keycode == Keys.Up)
897 else if (keycode == Keys.Down)
902 if (SelectedIndex > 0) {
909 if (SelectedIndex < TabCount - 1) {
919 #region Pages Collection
920 protected void RemoveAll ()
925 protected virtual object [] GetItems ()
927 TabPage [] pages = new TabPage [Controls.Count];
928 Controls.CopyTo (pages, 0);
932 protected virtual object [] GetItems (Type baseType)
934 object[] pages = (object[])Array.CreateInstance (baseType, Controls.Count);
935 Controls.CopyTo (pages, 0);
941 protected void UpdateTabSelection (bool updateFocus)
943 protected void UpdateTabSelection (bool uiselected)
949 protected string GetToolTipText (object item)
951 TabPage page = (TabPage) item;
952 return page.ToolTipText;
955 protected override void WndProc (ref Message m)
957 switch ((Msg)m.Msg) {
958 case Msg.WM_SETFOCUS:
959 if (selected_index == -1 && this.TabCount > 0)
960 this.SelectedIndex = 0;
961 if (selected_index != -1)
962 Invalidate(GetTabRect(selected_index));
963 base.WndProc (ref m);
965 case Msg.WM_KILLFOCUS:
966 if (selected_index != -1)
967 Invalidate(GetTabRect(selected_index));
968 base.WndProc (ref m);
971 base.WndProc (ref m);
976 #endregion // Protected Instance Methods
978 #region Internal & Private Methods
979 private bool CanScrollRight {
981 return (slider_pos < TabCount - 1);
985 private bool CanScrollLeft {
986 get { return slider_pos > 0; }
989 private void MouseDownHandler (object sender, MouseEventArgs e)
991 if ((e.Button & MouseButtons.Left) == 0)
995 Rectangle right = RightScrollButtonArea;
996 Rectangle left = LeftScrollButtonArea;
997 if (right.Contains (e.X, e.Y)) {
998 right_slider_state = PushButtonState.Pressed;
999 if (CanScrollRight) {
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));
1027 } else if (left.Contains (e.X, e.Y)) {
1028 left_slider_state = PushButtonState.Pressed;
1029 if (CanScrollLeft) {
1034 // UIA Framework Event: Horizontally Scrolled
1035 OnUIAHorizontallyScrolled (EventArgs.Empty);
1038 switch (this.Alignment) {
1039 case TabAlignment.Top:
1040 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
1042 case TabAlignment.Bottom:
1043 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
1045 case TabAlignment.Left:
1046 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
1048 case TabAlignment.Right:
1049 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
1059 int count = Controls.Count;
1060 for (int i = SliderPos; i < count; i++) {
1061 if (!GetTabRect (i).Contains (e.X, e.Y))
1064 mouse_down_on_a_tab_page = true;
1069 private void MouseUpHandler (object sender, MouseEventArgs e)
1071 mouse_down_on_a_tab_page = false;
1072 if (ShowSlider && (left_slider_state == PushButtonState.Pressed || right_slider_state == PushButtonState.Pressed)) {
1074 if (left_slider_state == PushButtonState.Pressed) {
1075 invalid = LeftScrollButtonArea;
1076 left_slider_state = GetScrollButtonState (invalid, e.Location);
1078 invalid = RightScrollButtonArea;
1079 right_slider_state = GetScrollButtonState (invalid, e.Location);
1081 Invalidate (invalid);
1085 bool HasHotElementStyles {
1087 return ThemeElements.CurrentTheme.TabControlPainter.HasHotElementStyles (this);
1091 Rectangle LeftScrollButtonArea {
1093 return ThemeElements.CurrentTheme.TabControlPainter.GetLeftScrollRect (this);
1097 Rectangle RightScrollButtonArea {
1099 return ThemeElements.CurrentTheme.TabControlPainter.GetRightScrollRect (this);
1103 static PushButtonState GetScrollButtonState (Rectangle scrollButtonArea, Point cursorLocation)
1105 return scrollButtonArea.Contains (cursorLocation) ? PushButtonState.Hot : PushButtonState.Normal;
1108 private void SizeChangedHandler (object sender, EventArgs e)
1113 internal int IndexForTabPage (TabPage page)
1115 for (int i = 0; i < tab_pages.Count; i++) {
1116 if (page == tab_pages [i])
1122 private void ResizeTabPages ()
1126 Rectangle r = DisplayRectangle;
1127 foreach (TabPage page in Controls) {
1132 private int MinimumTabWidth {
1134 return ThemeEngine.Current.TabControlMinimumTabWidth;
1138 private Size TabSpacing {
1140 return ThemeEngine.Current.TabControlGetSpacing (this);
1144 private void CalcTabRows ()
1146 switch (Alignment) {
1147 case TabAlignment.Right:
1148 case TabAlignment.Left:
1149 CalcTabRows (Height);
1152 CalcTabRows (Width);
1157 private void CalcTabRows (int row_width)
1161 Size spacing = TabSpacing;
1163 if (TabPages.Count > 0)
1165 show_slider = false;
1167 CalculateItemSize ();
1169 for (int i = 0; i < TabPages.Count; i++) {
1170 TabPage page = TabPages [i];
1172 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, 0, ref aux, true);
1175 if (SelectedIndex != -1 && TabPages.Count > SelectedIndex && TabPages[SelectedIndex].Row != BottomRow)
1176 DropRow (TabPages [SelectedIndex].Row);
1179 // ItemSize per-se is used mostly only to retrieve the Height,
1180 // since the actual Width of the tabs is computed individually,
1181 // except when SizeMode is TabSizeMode.Fixed, where Width is used as well.
1182 private void CalculateItemSize ()
1184 if (item_size_manual)
1188 if (tab_pages.Count > 0) {
1189 // .Net uses the first tab page if available.
1190 size = TextRenderer.MeasureString (tab_pages [0].Text, Font);
1193 size = TextRenderer.MeasureString ("a", Font);
1197 if (size_mode == TabSizeMode.Fixed)
1199 if (size.Width < MinimumTabWidth)
1200 size.Width = MinimumTabWidth;
1201 if (image_list != null && image_list.ImageSize.Height > size.Height)
1202 size.Height = image_list.ImageSize.Height;
1204 item_size = size.ToSize ();
1207 private int BottomRow {
1211 private int Direction
1218 private void DropRow (int row)
1220 if (Appearance != TabAppearance.Normal)
1223 int bottom = BottomRow;
1224 int direction = Direction;
1226 foreach (TabPage page in TabPages) {
1227 if (page.Row == row) {
1229 } else if (direction == 1 && page.Row < row) {
1230 page.Row += direction;
1231 } else if (direction == -1 && page.Row > row) {
1232 page.Row += direction;
1237 private int CalcYPos ()
1239 if (Alignment == TabAlignment.Bottom || Alignment == TabAlignment.Left)
1240 return ThemeEngine.Current.TabControlGetPanelRect (this).Bottom;
1242 if (Appearance == TabAppearance.Normal)
1243 return this.ClientRectangle.Y + ThemeEngine.Current.TabControlSelectedDelta.Y;
1245 return this.ClientRectangle.Y;
1249 private int CalcXPos ()
1251 if (Alignment == TabAlignment.Right)
1252 return ThemeEngine.Current.TabControlGetPanelRect (this).Right;
1254 if (Appearance == TabAppearance.Normal)
1255 return this.ClientRectangle.X + ThemeEngine.Current.TabControlSelectedDelta.X;
1257 return this.ClientRectangle.X;
1260 private void SizeTabs ()
1262 switch (Alignment) {
1263 case TabAlignment.Right:
1264 case TabAlignment.Left:
1265 SizeTabs (Height, true);
1268 SizeTabs (Width, false);
1273 private void SizeTabs (int row_width, bool vertical)
1278 Size spacing = TabSpacing;
1281 if (TabPages.Count == 0)
1284 prev_row = TabPages [0].Row;
1286 // Reset the slider position if the slider isn't needed
1287 // anymore (ie window size was increased so all tabs are visible)
1291 // set X = -1 for marking tabs that are not visible due to scrolling
1292 for (int i = 0; i < slider_pos; i++) {
1293 TabPage page = TabPages[i];
1294 Rectangle x = page.TabBounds;
1300 for (int i = slider_pos; i < TabPages.Count; i++) {
1301 TabPage page = TabPages[i];
1302 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, prev_row, ref begin_prev, false);
1303 prev_row = page.Row;
1306 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1307 FillRow (begin_prev, TabPages.Count - 1,
1308 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)),
1312 if (SelectedIndex != -1) {
1313 ExpandSelected (TabPages [SelectedIndex], 0, row_width - 1);
1317 private void SizeTab (TabPage page, int i, int row_width, ref int xpos, ref int ypos,
1318 Size spacing, int prev_row, ref int begin_prev, bool widthOnly)
1320 int width, height = 0;
1322 if (SizeMode == TabSizeMode.Fixed) {
1323 width = item_size.Width;
1325 width = MeasureStringWidth (DeviceContext, page.Text, page.Font);
1326 width += (Padding.X * 2) + 2;
1328 if (ImageList != null && page.ImageIndex >= 0) {
1329 width += ImageList.ImageSize.Width + ThemeEngine.Current.TabControlImagePadding.X;
1331 int image_size = ImageList.ImageSize.Height + ThemeEngine.Current.TabControlImagePadding.Y;
1332 if (item_size.Height < image_size)
1333 item_size.Height = image_size;
1336 if (width < MinimumTabWidth)
1337 width = MinimumTabWidth;
1340 // Use ItemSize property to recover the padding info as well.
1341 height = ItemSize.Height - ThemeEngine.Current.TabControlSelectedDelta.Height; // full height only for selected tab
1343 if (i == SelectedIndex)
1344 width += ThemeEngine.Current.TabControlSelectedSpacing;
1347 page.TabBounds = new Rectangle (xpos, 0, width, 0);
1348 page.Row = row_count;
1349 if (xpos + width > row_width && multiline) {
1352 } else if (xpos + width > row_width) {
1355 if (i == selected_index && show_slider) {
1356 for (int j = i-1; j >= 0; j--) {
1357 if (TabPages [j].TabBounds.Left < xpos + width - row_width) {
1364 if (page.Row != prev_row) {
1368 switch (Alignment) {
1369 case TabAlignment.Top:
1370 page.TabBounds = new Rectangle (
1372 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (),
1376 case TabAlignment.Bottom:
1377 page.TabBounds = new Rectangle (
1379 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (),
1383 case TabAlignment.Left:
1384 if (Appearance == TabAppearance.Normal) {
1385 // tab rows are positioned right to left
1386 page.TabBounds = new Rectangle (
1387 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1392 // tab rows are positioned left to right
1393 page.TabBounds = new Rectangle (
1394 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1401 case TabAlignment.Right:
1402 if (Appearance == TabAppearance.Normal) {
1403 // tab rows are positioned left to right
1404 page.TabBounds = new Rectangle (
1405 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1410 // tab rows are positioned right to left
1411 page.TabBounds = new Rectangle (
1412 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1421 if (page.Row != prev_row) {
1422 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1423 bool vertical = alignment == TabAlignment.Right || alignment == TabAlignment.Left;
1424 int offset = vertical ? TabPages [i - 1].TabBounds.Bottom : TabPages [i - 1].TabBounds.Right;
1425 FillRow (begin_prev, i - 1, ((row_width - offset) / (i - begin_prev)), spacing,
1432 xpos += width + spacing.Width + ThemeEngine.Current.TabControlColSpacing;
1435 private void FillRow (int start, int end, int amount, Size spacing, bool vertical)
1438 FillRowV (start, end, amount, spacing);
1440 FillRow (start, end, amount, spacing);
1443 private void FillRow (int start, int end, int amount, Size spacing)
1445 int xpos = TabPages [start].TabBounds.Left;
1446 for (int i = start; i <= end; i++) {
1447 TabPage page = TabPages [i];
1449 int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount);
1451 page.TabBounds = new Rectangle (left, page.TabBounds.Top,
1452 width, page.TabBounds.Height);
1453 xpos = page.TabBounds.Right + 1 + spacing.Width;
1457 private void FillRowV (int start, int end, int amount, Size spacing)
1459 int ypos = TabPages [start].TabBounds.Top;
1460 for (int i = start; i <= end; i++) {
1461 TabPage page = TabPages [i];
1463 int height = (i == end ? Height - top - 5 : page.TabBounds.Height + amount);
1465 page.TabBounds = new Rectangle (page.TabBounds.Left, top,
1466 page.TabBounds.Width, height);
1467 ypos = page.TabBounds.Bottom + 1;
1471 private void ExpandSelected (TabPage page, int left_edge, int right_edge)
1473 if (Appearance != TabAppearance.Normal)
1476 Rectangle r = page.TabBounds;
1477 switch (Alignment) {
1478 case TabAlignment.Top:
1479 case TabAlignment.Left:
1480 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1481 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1483 case TabAlignment.Bottom:
1484 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1485 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1487 case TabAlignment.Right:
1488 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1489 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1493 r.Width += ThemeEngine.Current.TabControlSelectedDelta.Width;
1494 r.Height += ThemeEngine.Current.TabControlSelectedDelta.Height;
1495 if (r.Left < left_edge)
1497 // Adjustment can't be used for right alignment, since it is
1498 // the only one that has a different X origin than 0
1499 if (r.Right > right_edge && SizeMode != TabSizeMode.Normal &&
1500 alignment != TabAlignment.Right)
1501 r.Width = right_edge - r.X;
1505 private void Draw (Graphics dc, Rectangle clip)
1507 ThemeEngine.Current.DrawTabControl (dc, clip, this);
1510 private TabPage GetTab (int index)
1512 return Controls [index] as TabPage;
1515 private void SetTab (int index, TabPage value)
1517 if (!tab_pages.Contains (value)) {
1518 this.Controls.Add (value);
1520 this.Controls.RemoveAt (index);
1521 this.Controls.SetChildIndex (value, index);
1525 private void InsertTab (int index, TabPage value)
1527 if (!tab_pages.Contains (value)) {
1528 this.Controls.Add (value);
1530 this.Controls.SetChildIndex (value, index);
1534 internal void Redraw ()
1536 if (!IsHandleCreated)
1543 private int MeasureStringWidth (Graphics graphics, string text, Font font)
1545 if (text == String.Empty)
1547 StringFormat format = new StringFormat();
1548 RectangleF rect = new RectangleF(0, 0, 1000, 1000);
1549 CharacterRange[] ranges = { new CharacterRange(0, text.Length) };
1550 Region[] regions = new Region[1];
1552 format.SetMeasurableCharacterRanges(ranges);
1553 format.FormatFlags = StringFormatFlags.NoClip;
1554 format.FormatFlags |= StringFormatFlags.NoWrap;
1555 regions = graphics.MeasureCharacterRanges(text + "I", font, rect, format);
1556 rect = regions[0].GetBounds(graphics);
1558 return (int)(rect.Width);
1561 void SetToolTip (string text)
1563 if (!show_tool_tips)
1566 if (text == null || text.Length == 0) {
1571 if (tooltip == null) {
1572 tooltip = new ToolTip ();
1573 tooltip_timer = new Timer ();
1574 tooltip_timer.Tick += new EventHandler (ToolTipTimerTick);
1579 tooltip_state = ToolTip.TipState.Initial;
1580 tooltip_timer.Interval = 500;
1581 tooltip_timer.Start ();
1584 void CloseToolTip ()
1586 if (tooltip == null)
1589 tooltip.Hide (this);
1590 tooltip_timer.Stop ();
1591 tooltip_state = ToolTip.TipState.Down;
1594 void ToolTipTimerTick (object o, EventArgs args)
1596 switch (tooltip_state) {
1597 case ToolTip.TipState.Initial:
1598 tooltip_timer.Stop ();
1599 tooltip_timer.Interval = 5000;
1600 tooltip_timer.Start ();
1601 tooltip_state = ToolTip.TipState.Show;
1602 tooltip.Present (this, GetToolTipText (EnteredTabPage));
1604 case ToolTip.TipState.Show:
1610 void OnMouseMove (object sender, MouseEventArgs e)
1612 if (!mouse_down_on_a_tab_page && ShowSlider) {
1613 if (LeftSliderState == PushButtonState.Pressed ||
1614 RightSliderState == PushButtonState.Pressed)
1616 if (LeftScrollButtonArea.Contains (e.Location)) {
1617 LeftSliderState = PushButtonState.Hot;
1618 RightSliderState = PushButtonState.Normal;
1619 EnteredTabPage = null;
1622 if (RightScrollButtonArea.Contains (e.Location)) {
1623 RightSliderState = PushButtonState.Hot;
1624 LeftSliderState = PushButtonState.Normal;
1625 EnteredTabPage = null;
1628 LeftSliderState = PushButtonState.Normal;
1629 RightSliderState = PushButtonState.Normal;
1631 if (EnteredTabPage != null && EnteredTabPage.TabBounds.Contains (e.Location))
1633 for (int index = 0; index < TabCount; index++) {
1634 TabPage tab_page = TabPages[index];
1635 if (tab_page.TabBounds.Contains (e.Location)) {
1636 EnteredTabPage = tab_page;
1640 EnteredTabPage = null;
1643 void OnMouseLeave (object sender, EventArgs e)
1646 LeftSliderState = PushButtonState.Normal;
1647 RightSliderState = PushButtonState.Normal;
1649 EnteredTabPage = null;
1651 #endregion // Internal & Private Methods
1655 [EditorBrowsable(EditorBrowsableState.Never)]
1656 public new event EventHandler BackColorChanged {
1657 add { base.BackColorChanged += value; }
1658 remove { base.BackColorChanged -= value; }
1662 [EditorBrowsable(EditorBrowsableState.Never)]
1663 public new event EventHandler BackgroundImageChanged {
1664 add { base.BackgroundImageChanged += value; }
1665 remove { base.BackgroundImageChanged -= value; }
1670 [EditorBrowsable (EditorBrowsableState.Never)]
1671 public new event EventHandler BackgroundImageLayoutChanged
1673 add { base.BackgroundImageLayoutChanged += value; }
1674 remove { base.BackgroundImageLayoutChanged -= value; }
1679 [EditorBrowsable(EditorBrowsableState.Never)]
1680 public new event EventHandler ForeColorChanged {
1681 add { base.ForeColorChanged += value; }
1682 remove { base.ForeColorChanged -= value; }
1686 [EditorBrowsable(EditorBrowsableState.Never)]
1687 public new event PaintEventHandler Paint {
1688 add { base.Paint += value; }
1689 remove { base.Paint -= value; }
1693 [EditorBrowsable(EditorBrowsableState.Never)]
1694 public new event EventHandler TextChanged {
1695 add { base.TextChanged += value; }
1696 remove { base.TextChanged -= value; }
1699 static object DrawItemEvent = new object ();
1700 static object SelectedIndexChangedEvent = new object ();
1702 public event DrawItemEventHandler DrawItem {
1703 add { Events.AddHandler (DrawItemEvent, value); }
1704 remove { Events.RemoveHandler (DrawItemEvent, value); }
1707 public event EventHandler SelectedIndexChanged {
1708 add { Events.AddHandler (SelectedIndexChangedEvent, value); }
1709 remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); }
1713 static object SelectedEvent = new object ();
1715 public event TabControlEventHandler Selected {
1716 add { Events.AddHandler (SelectedEvent, value); }
1717 remove { Events.RemoveHandler (SelectedEvent, value); }
1720 static object DeselectedEvent = new object ();
1722 public event TabControlEventHandler Deselected
1724 add { Events.AddHandler (DeselectedEvent, value); }
1725 remove { Events.RemoveHandler (DeselectedEvent, value); }
1728 static object SelectingEvent = new object ();
1730 public event TabControlCancelEventHandler Selecting
1732 add { Events.AddHandler (SelectingEvent, value); }
1733 remove { Events.RemoveHandler (SelectingEvent, value); }
1736 static object DeselectingEvent = new object ();
1738 public event TabControlCancelEventHandler Deselecting
1740 add { Events.AddHandler (DeselectingEvent, value); }
1741 remove { Events.RemoveHandler (DeselectingEvent, value); }
1744 static object RightToLeftLayoutChangedEvent = new object ();
1745 public event EventHandler RightToLeftLayoutChanged
1747 add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); }
1748 remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); }
1751 #endregion // Events
1754 #region Class TaControl.ControlCollection
1756 [ComVisible (false)]
1758 public new class ControlCollection : System.Windows.Forms.Control.ControlCollection {
1760 private TabControl owner;
1762 public ControlCollection (TabControl owner) : base (owner)
1767 public override void Add (Control value)
1769 TabPage page = value as TabPage;
1771 throw new ArgumentException ("Cannot add " +
1772 value.GetType ().Name + " to TabControl. " +
1773 "Only TabPages can be directly added to TabControls.");
1775 page.SetVisible (false);
1777 if (owner.TabCount == 1 && owner.selected_index < 0)
1778 owner.SelectedIndex = 0;
1782 public override void Remove (Control value)
1784 bool change_index = false;
1786 TabPage page = value as TabPage;
1787 if (page != null && owner.Controls.Contains (page)) {
1788 int index = owner.IndexForTabPage (page);
1789 if (index < owner.SelectedIndex || owner.SelectedIndex == Count - 1)
1790 change_index = true;
1793 base.Remove (value);
1795 // We don't want to raise SelectedIndexChanged until after we
1796 // have removed from the collection, so TabCount will be
1797 // correct for the user.
1798 if (change_index && Count > 0) {
1799 // Clear the selected index internally, to avoid trying to access the previous
1800 // selected tab when setting the new one - this is what .net seems to do
1801 int prev_selected_index = owner.SelectedIndex;
1802 owner.selected_index = -1;
1804 owner.SelectedIndex = --prev_selected_index;
1805 } else if (change_index) {
1806 owner.selected_index = -1;
1807 owner.OnSelectedIndexChanged (EventArgs.Empty);
1812 #endregion // Class TabControl.ControlCollection
1814 #region Class TabPage.TabPageCollection
1815 public class TabPageCollection : IList, ICollection, IEnumerable {
1817 private TabControl owner;
1819 public TabPageCollection (TabControl owner)
1822 throw new ArgumentNullException ("Value cannot be null.");
1828 get { return owner.Controls.Count; }
1831 public bool IsReadOnly {
1832 get { return false; }
1835 public virtual TabPage this [int index] {
1837 return owner.GetTab (index);
1840 owner.SetTab (index, value);
1844 public virtual TabPage this [string key] {
1846 if (string.IsNullOrEmpty (key))
1849 int index = this.IndexOfKey (key);
1850 if (index < 0 || index >= this.Count)
1858 internal int this[TabPage tabPage] {
1860 if (tabPage == null)
1863 for (int i = 0; i < this.Count; i++)
1864 if (this[i].Equals (tabPage))
1871 bool ICollection.IsSynchronized {
1872 get { return false; }
1875 object ICollection.SyncRoot {
1876 get { return this; }
1879 bool IList.IsFixedSize {
1880 get { return false; }
1883 object IList.this [int index] {
1885 return owner.GetTab (index);
1888 owner.SetTab (index, (TabPage) value);
1892 public void Add (TabPage value)
1895 throw new ArgumentNullException ("Value cannot be null.");
1896 owner.Controls.Add (value);
1900 public void Add (string text)
1902 TabPage page = new TabPage (text);
1906 public void Add (string key, string text)
1908 TabPage page = new TabPage (text);
1913 public void Add (string key, string text, int imageIndex)
1915 TabPage page = new TabPage (text);
1917 page.ImageIndex = imageIndex;
1921 // .Net sets the ImageKey, but does not show the image when this is used
1922 public void Add (string key, string text, string imageKey)
1924 TabPage page = new TabPage (text);
1926 page.ImageKey = imageKey;
1931 public void AddRange (TabPage [] pages)
1934 throw new ArgumentNullException ("Value cannot be null.");
1935 owner.Controls.AddRange (pages);
1938 public virtual void Clear ()
1940 owner.Controls.Clear ();
1941 owner.Invalidate ();
1944 public bool Contains (TabPage page)
1947 throw new ArgumentNullException ("Value cannot be null.");
1948 return owner.Controls.Contains (page);
1952 public virtual bool ContainsKey (string key)
1954 int index = this.IndexOfKey (key);
1955 return (index >= 0 && index < this.Count);
1959 public IEnumerator GetEnumerator ()
1961 return owner.Controls.GetEnumerator ();
1964 public int IndexOf (TabPage page)
1966 return owner.Controls.IndexOf (page);
1970 public virtual int IndexOfKey(string key)
1972 if (string.IsNullOrEmpty (key))
1975 for (int i = 0; i < this.Count; i++) {
1976 if (string.Compare (this[i].Name, key, true,
1977 System.Globalization.CultureInfo.InvariantCulture) == 0) {
1986 public void Remove (TabPage value)
1988 owner.Controls.Remove (value);
1989 owner.Invalidate ();
1992 public void RemoveAt (int index)
1994 owner.Controls.RemoveAt (index);
1995 owner.Invalidate ();
1999 public virtual void RemoveByKey (string key)
2001 int index = this.IndexOfKey (key);
2002 if (index >= 0 && index < this.Count)
2003 this.RemoveAt (index);
2007 void ICollection.CopyTo (Array dest, int index)
2009 owner.Controls.CopyTo (dest, index);
2012 int IList.Add (object value)
2014 TabPage page = value as TabPage;
2016 throw new ArgumentException ("value");
2017 owner.Controls.Add (page);
2018 return owner.Controls.IndexOf (page);
2021 bool IList.Contains (object page)
2023 TabPage tabPage = page as TabPage;
2024 if (tabPage == null)
2026 return Contains (tabPage);
2029 int IList.IndexOf (object page)
2031 TabPage tabPage = page as TabPage;
2032 if (tabPage == null)
2034 return IndexOf (tabPage);
2038 void IList.Insert (int index, object tabPage)
2040 void IList.Insert (int index, object value)
2043 throw new NotSupportedException ();
2047 public void Insert (int index, string text)
2049 owner.InsertTab (index, new TabPage (text));
2052 public void Insert (int index, TabPage tabPage)
2054 owner.InsertTab (index, tabPage);
2057 public void Insert (int index, string key, string text)
2059 TabPage page = new TabPage(text);
2061 owner.InsertTab (index, page);
2064 public void Insert (int index, string key, string text, int imageIndex)
2066 TabPage page = new TabPage(text);
2068 owner.InsertTab (index, page);
2069 page.ImageIndex = imageIndex;
2072 public void Insert (int index, string key, string text, string imageKey)
2074 TabPage page = new TabPage(text);
2076 owner.InsertTab (index, page);
2077 page.ImageKey = imageKey;
2080 void IList.Remove (object value)
2082 TabPage page = value as TabPage;
2085 Remove ((TabPage) value);
2088 #endregion // Class TabPage.TabPageCollection