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.PageUp && (ke.KeyData & Keys.Control) != 0) {
821 SelectedIndex = (SelectedIndex + TabCount - 1) % TabCount;
823 } else if (ke.KeyCode == Keys.PageDown && (ke.KeyData & Keys.Control) != 0) {
824 SelectedIndex = (SelectedIndex + 1) % TabCount;
826 } else if (ke.KeyCode == Keys.Home) {
829 } else if (ke.KeyCode == Keys.End) {
830 SelectedIndex = TabCount - 1;
832 } else if (NavigateTabs (ke.KeyCode))
836 protected override bool IsInputKey (Keys keyData)
838 switch (keyData & Keys.KeyCode) {
847 return base.IsInputKey (keyData);
850 private bool NavigateTabs (Keys keycode)
852 bool move_left = false;
853 bool move_right = false;
855 if (alignment == TabAlignment.Bottom || alignment == TabAlignment.Top) {
856 if (keycode == Keys.Left)
858 else if (keycode == Keys.Right)
861 if (keycode == Keys.Up)
863 else if (keycode == Keys.Down)
868 if (SelectedIndex > 0) {
875 if (SelectedIndex < TabCount - 1) {
885 #region Pages Collection
886 protected void RemoveAll ()
891 protected virtual object [] GetItems ()
893 TabPage [] pages = new TabPage [Controls.Count];
894 Controls.CopyTo (pages, 0);
898 protected virtual object [] GetItems (Type baseType)
900 object[] pages = (object[])Array.CreateInstance (baseType, Controls.Count);
901 Controls.CopyTo (pages, 0);
906 protected void UpdateTabSelection (bool updateFocus)
911 protected string GetToolTipText (object item)
913 TabPage page = (TabPage) item;
914 return page.ToolTipText;
917 protected override void WndProc (ref Message m)
919 switch ((Msg)m.Msg) {
920 case Msg.WM_SETFOCUS:
921 if (selected_index != -1)
922 Invalidate(GetTabRect(selected_index));
923 base.WndProc (ref m);
925 case Msg.WM_KILLFOCUS:
926 if (selected_index != -1)
927 Invalidate(GetTabRect(selected_index));
928 base.WndProc (ref m);
931 base.WndProc (ref m);
936 #endregion // Protected Instance Methods
938 #region Internal & Private Methods
939 private bool CanScrollRight {
941 return (slider_pos < TabCount - 1);
945 private bool CanScrollLeft {
946 get { return slider_pos > 0; }
949 private void MouseDownHandler (object sender, MouseEventArgs e)
951 if ((e.Button & MouseButtons.Left) == 0)
955 Rectangle right = RightScrollButtonArea;
956 Rectangle left = LeftScrollButtonArea;
957 if (right.Contains (e.X, e.Y)) {
958 right_slider_state = PushButtonState.Pressed;
959 if (CanScrollRight) {
963 // UIA Framework Event: Horizontally Scrolled
964 OnUIAHorizontallyScrolled (EventArgs.Empty);
966 switch (this.Alignment) {
967 case TabAlignment.Top:
968 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
970 case TabAlignment.Bottom:
971 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
973 case TabAlignment.Left:
974 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
976 case TabAlignment.Right:
977 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
985 } else if (left.Contains (e.X, e.Y)) {
986 left_slider_state = PushButtonState.Pressed;
991 // UIA Framework Event: Horizontally Scrolled
992 OnUIAHorizontallyScrolled (EventArgs.Empty);
994 switch (this.Alignment) {
995 case TabAlignment.Top:
996 Invalidate (new Rectangle (0, 0, Width, ItemSize.Height));
998 case TabAlignment.Bottom:
999 Invalidate (new Rectangle (0, DisplayRectangle.Bottom, Width, Height - DisplayRectangle.Bottom));
1001 case TabAlignment.Left:
1002 Invalidate (new Rectangle (0, 0, DisplayRectangle.Left, Height));
1004 case TabAlignment.Right:
1005 Invalidate (new Rectangle (DisplayRectangle.Right, 0, Width - DisplayRectangle.Right, Height));
1015 int count = Controls.Count;
1016 for (int i = SliderPos; i < count; i++) {
1017 if (!GetTabRect (i).Contains (e.X, e.Y))
1020 mouse_down_on_a_tab_page = true;
1025 private void MouseUpHandler (object sender, MouseEventArgs e)
1027 mouse_down_on_a_tab_page = false;
1028 if (ShowSlider && (left_slider_state == PushButtonState.Pressed || right_slider_state == PushButtonState.Pressed)) {
1030 if (left_slider_state == PushButtonState.Pressed) {
1031 invalid = LeftScrollButtonArea;
1032 left_slider_state = GetScrollButtonState (invalid, e.Location);
1034 invalid = RightScrollButtonArea;
1035 right_slider_state = GetScrollButtonState (invalid, e.Location);
1037 Invalidate (invalid);
1041 bool HasHotElementStyles {
1043 return ThemeElements.CurrentTheme.TabControlPainter.HasHotElementStyles (this);
1047 Rectangle LeftScrollButtonArea {
1049 return ThemeElements.CurrentTheme.TabControlPainter.GetLeftScrollRect (this);
1053 Rectangle RightScrollButtonArea {
1055 return ThemeElements.CurrentTheme.TabControlPainter.GetRightScrollRect (this);
1059 static PushButtonState GetScrollButtonState (Rectangle scrollButtonArea, Point cursorLocation)
1061 return scrollButtonArea.Contains (cursorLocation) ? PushButtonState.Hot : PushButtonState.Normal;
1064 private void SizeChangedHandler (object sender, EventArgs e)
1069 internal int IndexForTabPage (TabPage page)
1071 for (int i = 0; i < tab_pages.Count; i++) {
1072 if (page == tab_pages [i])
1078 private void ResizeTabPages ()
1082 Rectangle r = DisplayRectangle;
1083 foreach (TabPage page in Controls) {
1088 private int MinimumTabWidth {
1090 return ThemeEngine.Current.TabControlMinimumTabWidth;
1094 private Size TabSpacing {
1096 return ThemeEngine.Current.TabControlGetSpacing (this);
1100 private void CalcTabRows ()
1102 switch (Alignment) {
1103 case TabAlignment.Right:
1104 case TabAlignment.Left:
1105 CalcTabRows (Height);
1108 CalcTabRows (Width);
1113 private void CalcTabRows (int row_width)
1117 Size spacing = TabSpacing;
1119 if (TabPages.Count > 0)
1121 show_slider = false;
1123 CalculateItemSize ();
1125 for (int i = 0; i < TabPages.Count; i++) {
1126 TabPage page = TabPages [i];
1128 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, 0, ref aux, true);
1131 if (SelectedIndex != -1 && TabPages.Count > SelectedIndex && TabPages[SelectedIndex].Row != BottomRow)
1132 DropRow (TabPages [SelectedIndex].Row);
1135 // ItemSize per-se is used mostly only to retrieve the Height,
1136 // since the actual Width of the tabs is computed individually,
1137 // except when SizeMode is TabSizeMode.Fixed, where Width is used as well.
1138 private void CalculateItemSize ()
1140 if (item_size_manual)
1144 if (tab_pages.Count > 0) {
1145 // .Net uses the first tab page if available.
1146 size = TextRenderer.MeasureString (tab_pages [0].Text, Font);
1149 size = TextRenderer.MeasureString ("a", Font);
1153 if (size_mode == TabSizeMode.Fixed)
1155 if (size.Width < MinimumTabWidth)
1156 size.Width = MinimumTabWidth;
1157 if (image_list != null && image_list.ImageSize.Height > size.Height)
1158 size.Height = image_list.ImageSize.Height;
1160 item_size = size.ToSize ();
1163 private int BottomRow {
1167 private int Direction
1174 private void DropRow (int row)
1176 if (Appearance != TabAppearance.Normal)
1179 int bottom = BottomRow;
1180 int direction = Direction;
1182 foreach (TabPage page in TabPages) {
1183 if (page.Row == row) {
1185 } else if (direction == 1 && page.Row < row) {
1186 page.Row += direction;
1187 } else if (direction == -1 && page.Row > row) {
1188 page.Row += direction;
1193 private int CalcYPos ()
1195 if (Alignment == TabAlignment.Bottom || Alignment == TabAlignment.Left)
1196 return ThemeEngine.Current.TabControlGetPanelRect (this).Bottom;
1198 if (Appearance == TabAppearance.Normal)
1199 return this.ClientRectangle.Y + ThemeEngine.Current.TabControlSelectedDelta.Y;
1201 return this.ClientRectangle.Y;
1205 private int CalcXPos ()
1207 if (Alignment == TabAlignment.Right)
1208 return ThemeEngine.Current.TabControlGetPanelRect (this).Right;
1210 if (Appearance == TabAppearance.Normal)
1211 return this.ClientRectangle.X + ThemeEngine.Current.TabControlSelectedDelta.X;
1213 return this.ClientRectangle.X;
1216 private void SizeTabs ()
1218 switch (Alignment) {
1219 case TabAlignment.Right:
1220 case TabAlignment.Left:
1221 SizeTabs (Height, true);
1224 SizeTabs (Width, false);
1229 private void SizeTabs (int row_width, bool vertical)
1234 Size spacing = TabSpacing;
1237 if (TabPages.Count == 0)
1240 prev_row = TabPages [0].Row;
1242 // Reset the slider position if the slider isn't needed
1243 // anymore (ie window size was increased so all tabs are visible)
1247 // set X = -1 for marking tabs that are not visible due to scrolling
1248 for (int i = 0; i < slider_pos; i++) {
1249 TabPage page = TabPages[i];
1250 Rectangle x = page.TabBounds;
1256 for (int i = slider_pos; i < TabPages.Count; i++) {
1257 TabPage page = TabPages[i];
1258 SizeTab (page, i, row_width, ref xpos, ref ypos, spacing, prev_row, ref begin_prev, false);
1259 prev_row = page.Row;
1262 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1263 FillRow (begin_prev, TabPages.Count - 1,
1264 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)),
1268 if (SelectedIndex != -1) {
1269 ExpandSelected (TabPages [SelectedIndex], 0, row_width - 1);
1273 private void SizeTab (TabPage page, int i, int row_width, ref int xpos, ref int ypos,
1274 Size spacing, int prev_row, ref int begin_prev, bool widthOnly)
1276 int width, height = 0;
1278 if (SizeMode == TabSizeMode.Fixed) {
1279 width = item_size.Width;
1281 width = MeasureStringWidth (DeviceContext, page.Text, Font);
1282 width += (Padding.X * 2) + 2;
1284 if (ImageList != null && page.ImageIndex >= 0) {
1285 width += ImageList.ImageSize.Width + ThemeEngine.Current.TabControlImagePadding.X;
1287 int image_size = ImageList.ImageSize.Height + ThemeEngine.Current.TabControlImagePadding.Y;
1288 if (item_size.Height < image_size)
1289 item_size.Height = image_size;
1292 if (width < MinimumTabWidth)
1293 width = MinimumTabWidth;
1296 // Use ItemSize property to recover the padding info as well.
1297 height = ItemSize.Height - ThemeEngine.Current.TabControlSelectedDelta.Height; // full height only for selected tab
1299 if (i == SelectedIndex)
1300 width += ThemeEngine.Current.TabControlSelectedSpacing;
1303 page.TabBounds = new Rectangle (xpos, 0, width, 0);
1304 page.Row = row_count;
1305 if (xpos + width > row_width && multiline) {
1308 } else if (xpos + width > row_width) {
1311 if (i == selected_index && show_slider) {
1312 for (int j = i-1; j >= 0; j--) {
1313 if (TabPages [j].TabBounds.Left < xpos + width - row_width) {
1320 if (page.Row != prev_row) {
1324 switch (Alignment) {
1325 case TabAlignment.Top:
1326 case TabAlignment.Bottom:
1327 page.TabBounds = new Rectangle (
1329 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcYPos (),
1333 case TabAlignment.Left:
1334 if (Appearance == TabAppearance.Normal) {
1335 // tab rows are positioned right to left
1336 page.TabBounds = new Rectangle (
1337 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1342 // tab rows are positioned left to right
1343 page.TabBounds = new Rectangle (
1344 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1351 case TabAlignment.Right:
1352 if (Appearance == TabAppearance.Normal) {
1353 // tab rows are positioned left to right
1354 page.TabBounds = new Rectangle (
1355 ypos + (height + spacing.Height) * (page.Row - 1) + CalcXPos (),
1360 // tab rows are positioned right to left
1361 page.TabBounds = new Rectangle (
1362 ypos + (height + spacing.Height) * (row_count - page.Row) + CalcXPos (),
1371 if (page.Row != prev_row) {
1372 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
1373 bool vertical = alignment == TabAlignment.Right || alignment == TabAlignment.Left;
1374 int offset = vertical ? TabPages [i - 1].TabBounds.Bottom : TabPages [i - 1].TabBounds.Right;
1375 FillRow (begin_prev, i - 1, ((row_width - offset) / (i - begin_prev)), spacing,
1382 xpos += width + spacing.Width + ThemeEngine.Current.TabControlColSpacing;
1385 private void FillRow (int start, int end, int amount, Size spacing, bool vertical)
1388 FillRowV (start, end, amount, spacing);
1390 FillRow (start, end, amount, spacing);
1393 private void FillRow (int start, int end, int amount, Size spacing)
1395 int xpos = TabPages [start].TabBounds.Left;
1396 for (int i = start; i <= end; i++) {
1397 TabPage page = TabPages [i];
1399 int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount);
1401 page.TabBounds = new Rectangle (left, page.TabBounds.Top,
1402 width, page.TabBounds.Height);
1403 xpos = page.TabBounds.Right + 1 + spacing.Width;
1407 private void FillRowV (int start, int end, int amount, Size spacing)
1409 int ypos = TabPages [start].TabBounds.Top;
1410 for (int i = start; i <= end; i++) {
1411 TabPage page = TabPages [i];
1413 int height = (i == end ? Height - top - 5 : page.TabBounds.Height + amount);
1415 page.TabBounds = new Rectangle (page.TabBounds.Left, top,
1416 page.TabBounds.Width, height);
1417 ypos = page.TabBounds.Bottom + 1;
1421 private void ExpandSelected (TabPage page, int left_edge, int right_edge)
1423 if (Appearance != TabAppearance.Normal)
1426 Rectangle r = page.TabBounds;
1427 r.Y -= ThemeEngine.Current.TabControlSelectedDelta.Y;
1428 r.X -= ThemeEngine.Current.TabControlSelectedDelta.X;
1430 r.Width += ThemeEngine.Current.TabControlSelectedDelta.Width;
1431 r.Height += ThemeEngine.Current.TabControlSelectedDelta.Height;
1432 if (r.Left < left_edge)
1434 // Adjustment can't be used for right alignment, since it is
1435 // the only one that has a different X origin than 0
1436 if (r.Right > right_edge && SizeMode != TabSizeMode.Normal &&
1437 alignment != TabAlignment.Right)
1438 r.Width = right_edge - r.X;
1442 private void Draw (Graphics dc, Rectangle clip)
1444 ThemeEngine.Current.DrawTabControl (dc, clip, this);
1447 private TabPage GetTab (int index)
1449 return Controls [index] as TabPage;
1452 private void SetTab (int index, TabPage value)
1454 if (!tab_pages.Contains (value)) {
1455 this.Controls.Add (value);
1457 this.Controls.RemoveAt (index);
1458 this.Controls.SetChildIndex (value, index);
1461 private void InsertTab (int index, TabPage value)
1463 if (!tab_pages.Contains (value)) {
1464 this.Controls.Add (value);
1466 this.Controls.SetChildIndex (value, index);
1469 internal void Redraw ()
1471 if (!IsHandleCreated)
1478 private int MeasureStringWidth (Graphics graphics, string text, Font font)
1480 if (text == String.Empty)
1482 StringFormat format = new StringFormat();
1483 RectangleF rect = new RectangleF(0, 0, 1000, 1000);
1484 CharacterRange[] ranges = { new CharacterRange(0, text.Length) };
1485 Region[] regions = new Region[1];
1487 format.SetMeasurableCharacterRanges(ranges);
1488 format.FormatFlags = StringFormatFlags.NoClip;
1489 format.FormatFlags |= StringFormatFlags.NoWrap;
1490 regions = graphics.MeasureCharacterRanges(text + "I", font, rect, format);
1491 rect = regions[0].GetBounds(graphics);
1493 return (int)(rect.Width);
1496 void SetToolTip (string text)
1498 if (!show_tool_tips)
1501 if (text == null || text.Length == 0) {
1506 if (tooltip == null) {
1507 tooltip = new ToolTip ();
1508 tooltip_timer = new Timer ();
1509 tooltip_timer.Tick += new EventHandler (ToolTipTimerTick);
1514 tooltip_state = ToolTip.TipState.Initial;
1515 tooltip_timer.Interval = 500;
1516 tooltip_timer.Start ();
1519 void CloseToolTip ()
1521 if (tooltip == null)
1524 tooltip.Hide (this);
1525 tooltip_timer.Stop ();
1526 tooltip_state = ToolTip.TipState.Down;
1529 void ToolTipTimerTick (object o, EventArgs args)
1531 switch (tooltip_state) {
1532 case ToolTip.TipState.Initial:
1533 tooltip_timer.Stop ();
1534 tooltip_timer.Interval = 5000;
1535 tooltip_timer.Start ();
1536 tooltip_state = ToolTip.TipState.Show;
1537 tooltip.Present (this, GetToolTipText (EnteredTabPage));
1539 case ToolTip.TipState.Show:
1545 void OnMouseMove (object sender, MouseEventArgs e)
1547 if (!mouse_down_on_a_tab_page && ShowSlider) {
1548 if (LeftSliderState == PushButtonState.Pressed ||
1549 RightSliderState == PushButtonState.Pressed)
1551 if (LeftScrollButtonArea.Contains (e.Location)) {
1552 LeftSliderState = PushButtonState.Hot;
1553 RightSliderState = PushButtonState.Normal;
1554 EnteredTabPage = null;
1557 if (RightScrollButtonArea.Contains (e.Location)) {
1558 RightSliderState = PushButtonState.Hot;
1559 LeftSliderState = PushButtonState.Normal;
1560 EnteredTabPage = null;
1563 LeftSliderState = PushButtonState.Normal;
1564 RightSliderState = PushButtonState.Normal;
1566 if (EnteredTabPage != null && EnteredTabPage.TabBounds.Contains (e.Location))
1568 for (int index = 0; index < TabCount; index++) {
1569 TabPage tab_page = TabPages[index];
1570 if (tab_page.TabBounds.Contains (e.Location)) {
1571 EnteredTabPage = tab_page;
1575 EnteredTabPage = null;
1578 void OnMouseLeave (object sender, EventArgs e)
1581 LeftSliderState = PushButtonState.Normal;
1582 RightSliderState = PushButtonState.Normal;
1584 EnteredTabPage = null;
1586 #endregion // Internal & Private Methods
1590 [EditorBrowsable(EditorBrowsableState.Never)]
1591 public new event EventHandler BackColorChanged {
1592 add { base.BackColorChanged += value; }
1593 remove { base.BackColorChanged -= value; }
1597 [EditorBrowsable(EditorBrowsableState.Never)]
1598 public new event EventHandler BackgroundImageChanged {
1599 add { base.BackgroundImageChanged += value; }
1600 remove { base.BackgroundImageChanged -= value; }
1604 [EditorBrowsable (EditorBrowsableState.Never)]
1605 public new event EventHandler BackgroundImageLayoutChanged
1607 add { base.BackgroundImageLayoutChanged += value; }
1608 remove { base.BackgroundImageLayoutChanged -= value; }
1612 [EditorBrowsable(EditorBrowsableState.Never)]
1613 public new event EventHandler ForeColorChanged {
1614 add { base.ForeColorChanged += value; }
1615 remove { base.ForeColorChanged -= value; }
1619 [EditorBrowsable(EditorBrowsableState.Never)]
1620 public new event PaintEventHandler Paint {
1621 add { base.Paint += value; }
1622 remove { base.Paint -= value; }
1626 [EditorBrowsable(EditorBrowsableState.Never)]
1627 public new event EventHandler TextChanged {
1628 add { base.TextChanged += value; }
1629 remove { base.TextChanged -= value; }
1632 static object DrawItemEvent = new object ();
1633 static object SelectedIndexChangedEvent = new object ();
1635 public event DrawItemEventHandler DrawItem {
1636 add { Events.AddHandler (DrawItemEvent, value); }
1637 remove { Events.RemoveHandler (DrawItemEvent, value); }
1640 public event EventHandler SelectedIndexChanged {
1641 add { Events.AddHandler (SelectedIndexChangedEvent, value); }
1642 remove { Events.RemoveHandler (SelectedIndexChangedEvent, value); }
1645 static object SelectedEvent = new object ();
1647 public event TabControlEventHandler Selected {
1648 add { Events.AddHandler (SelectedEvent, value); }
1649 remove { Events.RemoveHandler (SelectedEvent, value); }
1652 static object DeselectedEvent = new object ();
1654 public event TabControlEventHandler Deselected
1656 add { Events.AddHandler (DeselectedEvent, value); }
1657 remove { Events.RemoveHandler (DeselectedEvent, value); }
1660 static object SelectingEvent = new object ();
1662 public event TabControlCancelEventHandler Selecting
1664 add { Events.AddHandler (SelectingEvent, value); }
1665 remove { Events.RemoveHandler (SelectingEvent, value); }
1668 static object DeselectingEvent = new object ();
1670 public event TabControlCancelEventHandler Deselecting
1672 add { Events.AddHandler (DeselectingEvent, value); }
1673 remove { Events.RemoveHandler (DeselectingEvent, value); }
1676 static object RightToLeftLayoutChangedEvent = new object ();
1677 public event EventHandler RightToLeftLayoutChanged
1679 add { Events.AddHandler (RightToLeftLayoutChangedEvent, value); }
1680 remove { Events.RemoveHandler (RightToLeftLayoutChangedEvent, value); }
1682 #endregion // Events
1685 #region Class TaControl.ControlCollection
1686 [ComVisible (false)]
1687 public new class ControlCollection : System.Windows.Forms.Control.ControlCollection {
1689 private TabControl owner;
1691 public ControlCollection (TabControl owner) : base (owner)
1696 public override void Add (Control value)
1698 TabPage page = value as TabPage;
1700 throw new ArgumentException ("Cannot add " +
1701 value.GetType ().Name + " to TabControl. " +
1702 "Only TabPages can be directly added to TabControls.");
1704 page.SetVisible (false);
1706 if (owner.TabCount == 1 && owner.selected_index < 0)
1707 owner.SelectedIndex = 0;
1711 public override void Remove (Control value)
1713 bool change_index = false;
1715 TabPage page = value as TabPage;
1716 if (page != null && owner.Controls.Contains (page)) {
1717 int index = owner.IndexForTabPage (page);
1718 if (index < owner.SelectedIndex || owner.SelectedIndex == Count - 1)
1719 change_index = true;
1722 base.Remove (value);
1724 // We don't want to raise SelectedIndexChanged until after we
1725 // have removed from the collection, so TabCount will be
1726 // correct for the user.
1727 if (change_index && Count > 0) {
1728 // Clear the selected index internally, to avoid trying to access the previous
1729 // selected tab when setting the new one - this is what .net seems to do
1730 int prev_selected_index = owner.SelectedIndex;
1731 owner.selected_index = -1;
1733 owner.SelectedIndex = --prev_selected_index;
1734 owner.Invalidate ();
1735 } else if (change_index) {
1736 owner.selected_index = -1;
1737 owner.OnSelectedIndexChanged (EventArgs.Empty);
1738 owner.Invalidate ();
1743 #endregion // Class TabControl.ControlCollection
1745 #region Class TabPage.TabPageCollection
1746 public class TabPageCollection : IList, ICollection, IEnumerable {
1748 private TabControl owner;
1750 public TabPageCollection (TabControl owner)
1753 throw new ArgumentNullException ("Value cannot be null.");
1759 get { return owner.Controls.Count; }
1762 public bool IsReadOnly {
1763 get { return false; }
1766 public virtual TabPage this [int index] {
1768 return owner.GetTab (index);
1771 owner.SetTab (index, value);
1774 public virtual TabPage this [string key] {
1776 if (string.IsNullOrEmpty (key))
1779 int index = this.IndexOfKey (key);
1780 if (index < 0 || index >= this.Count)
1787 internal int this[TabPage tabPage] {
1789 if (tabPage == null)
1792 for (int i = 0; i < this.Count; i++)
1793 if (this[i].Equals (tabPage))
1800 bool ICollection.IsSynchronized {
1801 get { return false; }
1804 object ICollection.SyncRoot {
1805 get { return this; }
1808 bool IList.IsFixedSize {
1809 get { return false; }
1812 object IList.this [int index] {
1814 return owner.GetTab (index);
1817 owner.SetTab (index, (TabPage) value);
1821 public void Add (TabPage value)
1824 throw new ArgumentNullException ("Value cannot be null.");
1825 owner.Controls.Add (value);
1828 public void Add (string text)
1830 TabPage page = new TabPage (text);
1834 public void Add (string key, string text)
1836 TabPage page = new TabPage (text);
1841 public void Add (string key, string text, int imageIndex)
1843 TabPage page = new TabPage (text);
1845 page.ImageIndex = imageIndex;
1849 // .Net sets the ImageKey, but does not show the image when this is used
1850 public void Add (string key, string text, string imageKey)
1852 TabPage page = new TabPage (text);
1854 page.ImageKey = imageKey;
1858 public void AddRange (TabPage [] pages)
1861 throw new ArgumentNullException ("Value cannot be null.");
1862 owner.Controls.AddRange (pages);
1865 public virtual void Clear ()
1867 owner.Controls.Clear ();
1868 owner.Invalidate ();
1871 public bool Contains (TabPage page)
1874 throw new ArgumentNullException ("Value cannot be null.");
1875 return owner.Controls.Contains (page);
1878 public virtual bool ContainsKey (string key)
1880 int index = this.IndexOfKey (key);
1881 return (index >= 0 && index < this.Count);
1884 public IEnumerator GetEnumerator ()
1886 return owner.Controls.GetEnumerator ();
1889 public int IndexOf (TabPage page)
1891 return owner.Controls.IndexOf (page);
1894 public virtual int IndexOfKey(string key)
1896 if (string.IsNullOrEmpty (key))
1899 for (int i = 0; i < this.Count; i++) {
1900 if (string.Compare (this[i].Name, key, true,
1901 System.Globalization.CultureInfo.InvariantCulture) == 0) {
1909 public void Remove (TabPage value)
1911 owner.Controls.Remove (value);
1912 owner.Invalidate ();
1915 public void RemoveAt (int index)
1917 owner.Controls.RemoveAt (index);
1918 owner.Invalidate ();
1921 public virtual void RemoveByKey (string key)
1923 int index = this.IndexOfKey (key);
1924 if (index >= 0 && index < this.Count)
1925 this.RemoveAt (index);
1928 void ICollection.CopyTo (Array dest, int index)
1930 owner.Controls.CopyTo (dest, index);
1933 int IList.Add (object value)
1935 TabPage page = value as TabPage;
1937 throw new ArgumentException ("value");
1938 owner.Controls.Add (page);
1939 return owner.Controls.IndexOf (page);
1942 bool IList.Contains (object page)
1944 TabPage tabPage = page as TabPage;
1945 if (tabPage == null)
1947 return Contains (tabPage);
1950 int IList.IndexOf (object page)
1952 TabPage tabPage = page as TabPage;
1953 if (tabPage == null)
1955 return IndexOf (tabPage);
1958 void IList.Insert (int index, object tabPage)
1960 throw new NotSupportedException ();
1963 public void Insert (int index, string text)
1965 owner.InsertTab (index, new TabPage (text));
1968 public void Insert (int index, TabPage tabPage)
1970 owner.InsertTab (index, tabPage);
1973 public void Insert (int index, string key, string text)
1975 TabPage page = new TabPage(text);
1977 owner.InsertTab (index, page);
1980 public void Insert (int index, string key, string text, int imageIndex)
1982 TabPage page = new TabPage(text);
1984 owner.InsertTab (index, page);
1985 page.ImageIndex = imageIndex;
1988 public void Insert (int index, string key, string text, string imageKey)
1990 TabPage page = new TabPage(text);
1992 owner.InsertTab (index, page);
1993 page.ImageKey = imageKey;
1995 void IList.Remove (object value)
1997 TabPage page = value as TabPage;
2000 Remove ((TabPage) value);
2003 #endregion // Class TabPage.TabPageCollection