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 Novell, Inc.
23 // Jackson Harper (jackson@ximian.com)
27 using System.Collections;
28 using System.ComponentModel;
29 using System.ComponentModel.Design;
33 namespace System.Windows.Forms {
34 [DefaultEvent("SelectedIndexChanged")]
35 [DefaultProperty("TabPages")]
36 [Designer("System.Windows.Forms.Design.TabControlDesigner, " + Consts.AssemblySystem_Design)]
37 public class TabControl : Control {
39 private int selected_index = -1;
40 private TabAlignment alignment;
41 private TabAppearance appearance;
42 private TabDrawMode draw_mode;
43 private bool multiline;
44 private ImageList image_list;
45 private Size item_size = Size.Empty;
46 private Point padding;
47 private int row_count = 1;
48 private bool hottrack;
49 private TabPageCollection tab_pages;
50 private bool show_tool_tips;
51 private TabSizeMode size_mode;
53 private Rectangle display_rect;
54 private bool show_slider = false;
55 private ButtonState right_slider_state;
56 private ButtonState left_slider_state;
57 private int slider_pos = 0;
60 #region Public Constructors
63 tab_pages = new TabPageCollection (this);
64 SetStyle (ControlStyles.UserPaint, true);
65 padding = ThemeEngine.Current.TabControlDefaultPadding;
66 item_size = ThemeEngine.Current.TabControlDefaultItemSize;
68 MouseDown += new MouseEventHandler (MouseDownHandler);
69 MouseUp += new MouseEventHandler (MouseUpHandler);
70 SizeChanged += new EventHandler (SizeChangedHandler);
73 #endregion // Public Constructors
75 #region Public Instance Properties
76 [DefaultValue(TabAlignment.Top)]
78 [RefreshProperties(RefreshProperties.All)]
79 public TabAlignment Alignment {
80 get { return alignment; }
82 if (alignment == value)
85 if (alignment == TabAlignment.Left || alignment == TabAlignment.Right)
91 [DefaultValue(TabAppearance.Normal)]
93 public TabAppearance Appearance {
94 get { return appearance; }
96 if (appearance == value)
104 [EditorBrowsable(EditorBrowsableState.Never)]
105 public override Color BackColor {
106 get { return base.BackColor; }
107 set { /* nothing happens on set on MS */ }
111 [EditorBrowsable(EditorBrowsableState.Never)]
112 public override Image BackgroundImage {
113 get { return base.BackgroundImage; }
114 set { base.BackgroundImage = value; }
117 public override Rectangle DisplayRectangle {
119 return ThemeEngine.Current.GetTabControlDisplayRectangle (this);
123 [DefaultValue(TabDrawMode.Normal)]
124 public TabDrawMode DrawMode {
125 get { return draw_mode; }
127 if (draw_mode == value)
135 [EditorBrowsable(EditorBrowsableState.Never)]
136 public override Color ForeColor {
137 get { return base.ForeColor; }
138 set { base.ForeColor = value; }
141 [DefaultValue(false)]
142 public bool HotTrack {
143 get { return hottrack; }
145 if (hottrack == value)
153 public ImageList ImageList {
154 get { return image_list; }
155 set { image_list = value; }
159 public Size ItemSize {
164 if (value.Height < 0 || value.Width < 0)
165 throw new ArgumentException ("'" + value + "' is not a valid value for 'ItemSize'.");
171 [DefaultValue(false)]
172 public bool Multiline {
173 get { return multiline; }
175 if (multiline == value)
178 if (!multiline && alignment == TabAlignment.Left || alignment == TabAlignment.Right)
179 alignment = TabAlignment.Top;
185 public Point Padding {
186 get { return padding; }
188 if (value.X < 0 || value.Y < 0)
189 throw new ArgumentException ("'" + value + "' is not a valid value for 'Padding'.");
190 if (padding == value)
199 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
200 public int RowCount {
201 get { return row_count; }
206 public int SelectedIndex {
207 get { return selected_index; }
209 if (selected_index == value)
211 if (selected_index < -1) {
212 throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
213 "'value' must be greater than or equal to -1.");
218 Rectangle invalid = Rectangle.Empty;
220 if (selected_index != -1) {
221 invalid = GetTabRect (selected_index);
222 Controls [selected_index].Visible = false;
224 selected_index = value;
226 OnSelectedIndexChanged (EventArgs.Empty);
228 if (selected_index != -1) {
229 invalid = Rectangle.Union (invalid, GetTabRect (selected_index));
230 Controls [selected_index].Visible = true;
235 if (SelectedIndex != -1 && TabPages [SelectedIndex].Row != BottomRow) {
236 DropRow (TabPages [selected_index].Row);
237 // calculating what to invalidate here seems to be slower then just
238 // refreshing the whole thing
243 Invalidate (invalid);
249 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
250 public TabPage SelectedTab {
252 if (selected_index == -1)
254 return tab_pages [selected_index];
257 int index = IndexForTabPage (value);
258 if (index == selected_index)
260 SelectedIndex = index;
264 [DefaultValue(false)]
266 public bool ShowToolTips {
267 get { return show_tool_tips; }
269 if (show_tool_tips == value)
271 show_tool_tips = value;
276 [DefaultValue(TabSizeMode.Normal)]
277 [RefreshProperties(RefreshProperties.Repaint)]
278 public TabSizeMode SizeMode {
279 get { return size_mode; }
281 if (size_mode == value)
289 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
290 public int TabCount {
292 return tab_pages.Count;
297 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
298 [MergableProperty(false)]
299 public TabPageCollection TabPages {
300 get { return tab_pages; }
305 [EditorBrowsable(EditorBrowsableState.Never)]
306 public override string Text {
307 get { return base.Text; }
308 set { base.Text = value; }
311 #endregion // Public Instance Properties
313 #region Internal Properties
314 internal bool ShowSlider {
315 get { return show_slider; }
316 set { show_slider = value; }
319 internal int SliderPos {
320 get { return slider_pos; }
323 internal ButtonState RightSliderState {
324 get { return right_slider_state; }
327 internal ButtonState LeftSliderState {
328 get { return left_slider_state; }
331 private Size DefaultItemSize {
333 return ThemeEngine.Current.TabControlDefaultItemSize;
337 #endregion // Internal Properties
339 #region Protected Instance Properties
340 [MonoTODO ("Anything special need to be done?")]
341 protected override CreateParams CreateParams {
343 CreateParams c = base.CreateParams;
344 // Do we need to do anything here?
349 protected override Size DefaultSize {
350 get { return new Size (200, 100); }
353 #endregion // Protected Instance Properties
355 #region Public Instance Methods
356 public Rectangle GetTabRect (int index)
358 TabPage page = GetTab (index);
359 return page.TabBounds;
362 public Control GetControl (int index)
364 return GetTab (index);
367 #endregion // Public Instance Methods
369 #region Protected Instance Methods
370 protected override Control.ControlCollection CreateControlsInstance ()
372 return new TabControl.ControlCollection (this);
375 protected override void CreateHandle ()
378 base.CreateHandle ();
381 protected override void Dispose (bool disposing)
383 base.Dispose (disposing);
386 protected virtual object [] GetItems ()
388 TabPage [] pages = new TabPage [Controls.Count];
389 Controls.CopyTo (pages, 0);
393 protected virtual object [] GetItems (Type type)
395 object [] pages = (object []) Array.CreateInstance (type, Controls.Count);
396 Controls.CopyTo (pages, 0);
400 protected string GetToolTipText (object item)
402 TabPage page = (TabPage) item;
403 return page.ToolTipText;
406 protected override void WndProc (ref Message m)
408 switch ((Msg) m.Msg) {
410 PaintEventArgs paint_event;
411 paint_event = XplatUI.PaintEventStart (Handle);
412 PaintInternal (paint_event);
413 XplatUI.PaintEventEnd (Handle);
416 base.WndProc (ref m);
421 protected virtual void OnSelectedIndexChanged (EventArgs e)
423 if (SelectedIndexChanged != null)
424 SelectedIndexChanged (this, e);
427 #endregion // Protected Instance Methods
429 #region Internal & Private Methods
430 private bool CanScrollRight {
432 if (TabPages [TabCount - 1].TabBounds.Right > ClientRectangle.Right - 40)
438 private bool CanScrollLeft {
439 get { return slider_pos > 0; }
442 private void MouseDownHandler (object sender, MouseEventArgs e)
445 Rectangle right = ThemeEngine.Current.GetTabControlRightScrollRect (this);
446 Rectangle left = ThemeEngine.Current.GetTabControlLeftScrollRect (this);
447 if (right.Contains (e.X, e.Y)) {
448 right_slider_state = ButtonState.Pushed;
449 if (CanScrollRight) {
456 } else if (left.Contains (e.X, e.Y)) {
457 left_slider_state = ButtonState.Pushed;
468 int count = Controls.Count;
469 for (int i = SliderPos; i < count; i++) {
470 if (!GetTabRect (i).Contains (e.X, e.Y))
477 private void MouseUpHandler (object sender, MouseEventArgs e)
479 if (ShowSlider && (left_slider_state != ButtonState.Pushed || right_slider_state != ButtonState.Pushed)) {
481 if (left_slider_state == ButtonState.Pushed)
482 invalid = ThemeEngine.Current.GetTabControlLeftScrollRect (this);
484 invalid = ThemeEngine.Current.GetTabControlRightScrollRect (this);
485 left_slider_state = ButtonState.Normal;
486 right_slider_state = ButtonState.Normal;
488 Invalidate (invalid);
492 private void SizeChangedHandler (object sender, EventArgs e)
497 internal void UpdateTabpage (TabPage page)
502 internal int IndexForTabPage (TabPage page)
504 for (int i = 0; i < tab_pages.Count; i++) {
505 if (page == tab_pages [i])
511 private void ResizeTabPages ()
515 Rectangle r = DisplayRectangle;
516 foreach (TabPage page in Controls) {
521 private int MinimumTabWidth {
523 return ThemeEngine.Current.TabControlMinimumTabWidth;
527 private Size TabSpacing {
529 return ThemeEngine.Current.TabControlGetSpacing (this);
533 private void CalcTabRows ()
536 case TabAlignment.Right:
537 case TabAlignment.Left:
538 CalcTabRows (Height);
546 private void CalcTabRows (int row_width)
549 Size spacing = TabSpacing;
554 for (int i = 0; i < TabPages.Count; i++) {
555 TabPage page = TabPages [i];
560 if (SizeMode == TabSizeMode.Fixed) {
561 width = item_size.Width;
563 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
566 if (i == SelectedIndex)
568 if (width < MinimumTabWidth)
569 width = MinimumTabWidth;
571 if (xpos + width > row_width && multiline) {
573 for (int j = 0; j < i; j++) {
577 } else if (xpos + width > row_width) {
581 xpos += width + 1 + spacing.Width;
584 if (SelectedIndex != -1 && TabPages [SelectedIndex].Row != BottomRow)
585 DropRow (TabPages [SelectedIndex].Row);
588 private int BottomRow {
591 case TabAlignment.Right:
592 case TabAlignment.Bottom:
600 private int Direction
604 case TabAlignment.Right:
605 case TabAlignment.Bottom:
613 private void DropRow (int row)
615 int bottom = BottomRow;
616 int direction = Direction;
618 foreach (TabPage page in TabPages) {
619 if (page.Row == row) {
621 } else if (direction == 1 && page.Row < row) {
622 page.Row += direction;
623 } else if (direction == -1 && page.Row > row) {
624 page.Row += direction;
629 private int CalcYPos ()
631 if (Alignment == TabAlignment.Bottom) {
632 Rectangle r = ThemeEngine.Current.GetTabControlDisplayRectangle (this);
638 private int CalcXPos ()
640 if (Alignment == TabAlignment.Right) {
641 Rectangle r = ThemeEngine.Current.GetTabControlDisplayRectangle (this);
648 private void SizeTabs ()
651 case TabAlignment.Right:
652 case TabAlignment.Left:
661 private void SizeTabsV (int row_width)
665 Size spacing = TabSpacing;
666 int xpos = CalcXPos ();
669 if (TabPages.Count == 0)
672 prev_row = TabPages [0].Row;
674 for (int i = 0; i < TabPages.Count; i++) {
675 TabPage page = TabPages [i];
678 if (SizeMode == TabSizeMode.Fixed) {
679 width = item_size.Width;
681 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
684 if (width < MinimumTabWidth)
685 width = MinimumTabWidth;
686 if (page.Row != prev_row)
689 page.TabBounds = new Rectangle (xpos + (row_count - page.Row) * ((item_size.Height - 2) + spacing.Width),
690 ypos, item_size.Height - 2, width);
692 if (page.Row != prev_row) {
693 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
694 FillRowV (begin_prev, i - 1, ((row_width - TabPages [i - 1].TabBounds.Bottom) / (i - begin_prev)), spacing);
699 ypos += width + spacing.Width;
703 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
704 FillRowV (begin_prev, TabPages.Count - 1,
705 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Bottom) / (TabPages.Count - begin_prev)), spacing);
708 if (SelectedIndex != -1) {
709 ExpandSelected (TabPages [SelectedIndex], 2, row_width - 1);
713 private void SizeTabs (int row_width)
715 int ypos = CalcYPos ();
717 Size spacing = TabSpacing;
721 if (TabPages.Count == 0)
724 prev_row = TabPages [0].Row;
726 for (int i = slider_pos; i < TabPages.Count; i++) {
727 TabPage page = TabPages [i];
730 if (SizeMode == TabSizeMode.Fixed) {
731 width = item_size.Width;
733 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
736 if (width < MinimumTabWidth)
737 width = MinimumTabWidth;
738 if (page.Row != prev_row)
741 page.TabBounds = new Rectangle (xpos,
742 ypos + (row_count - page.Row) * (item_size.Height + spacing.Height),
743 width, item_size.Height);
745 if (page.Row != prev_row) {
746 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
747 FillRow (begin_prev, i - 1, ((row_width - TabPages [i - 1].TabBounds.Right) / (i - begin_prev)), spacing);
752 xpos += width + 1 + spacing.Width;
756 if (SizeMode == TabSizeMode.FillToRight && !ShowSlider) {
757 FillRow (begin_prev, TabPages.Count - 1,
758 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)), spacing);
761 if (SelectedIndex != -1) {
762 ExpandSelected (TabPages [SelectedIndex], 2, row_width - 1);
766 private void FillRow (int start, int end, int amount, Size spacing)
768 int xpos = TabPages [start].TabBounds.Left;
769 for (int i = start; i <= end; i++) {
770 TabPage page = TabPages [i];
772 int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount);
774 page.TabBounds = new Rectangle (left, page.TabBounds.Top,
775 width, page.TabBounds.Height);
776 xpos = page.TabBounds.Right + 1 + spacing.Width;
780 private void FillRowV (int start, int end, int amount, Size spacing)
782 int ypos = TabPages [start].TabBounds.Top;
783 for (int i = start; i <= end; i++) {
784 TabPage page = TabPages [i];
786 int height = (i == end ? Height - top - 5 : page.TabBounds.Height + amount);
788 page.TabBounds = new Rectangle (page.TabBounds.Left, top,
789 page.TabBounds.Width, height);
790 ypos = page.TabBounds.Bottom + 1;
794 private void ExpandSelected (TabPage page, int left_edge, int right_edge)
796 if (Appearance != TabAppearance.Normal)
799 if (Alignment == TabAlignment.Top || Alignment == TabAlignment.Bottom) {
800 int l = page.TabBounds.Left - 4;
801 int r = page.TabBounds.Right + 4;
802 int y = page.TabBounds.Y;
803 int h = page.TabBounds.Height + 2;
807 if (r > right_edge && SizeMode != TabSizeMode.Normal)
809 if (Alignment == TabAlignment.Top)
811 if (Alignment == TabAlignment.Bottom)
814 page.TabBounds = new Rectangle (l, y, r - l, h);
816 int l = page.TabBounds.Left - 3;
817 int r = page.TabBounds.Right + 3;
818 int t = page.TabBounds.Top - 3;
819 int b = page.TabBounds.Bottom + 3;
826 page.TabBounds = new Rectangle (l, t, r - l, b - t);
830 internal void RefreshTabs ()
835 private void PaintInternal (PaintEventArgs pe)
837 if (this.Width <= 0 || this.Height <= 0 || this.Visible == false)
840 Draw (pe.ClipRectangle);
841 pe.Graphics.DrawImage (ImageBuffer, pe.ClipRectangle,
842 pe.ClipRectangle, GraphicsUnit.Pixel);
843 // On MS the Paint event never seems to be raised
846 private void Redraw (bool recalculate)
855 private void Draw (Rectangle clip)
857 ThemeEngine.Current.DrawTabControl (DeviceContext, clip, this);
861 private TabPage GetTab (int index)
863 return Controls [index] as TabPage;
866 private void SetTab (int index, TabPage value)
868 ((IList) Controls).Insert (index, value);
872 #endregion // Internal & Private Methods
876 [EditorBrowsable(EditorBrowsableState.Never)]
877 public new event EventHandler BackColorChanged {
878 add { base.BackColorChanged += value; }
879 remove { base.BackColorChanged -= value; }
883 [EditorBrowsable(EditorBrowsableState.Never)]
884 public new event EventHandler BackgroundImageChanged {
885 add { base.BackgroundImageChanged += value; }
886 remove { base.BackgroundImageChanged -= value; }
890 [EditorBrowsable(EditorBrowsableState.Never)]
891 public new event EventHandler ForeColorChanged {
892 add { base.ForeColorChanged += value; }
893 remove { base.ForeColorChanged -= value; }
897 [EditorBrowsable(EditorBrowsableState.Never)]
898 public new event PaintEventHandler Paint {
899 add { base.Paint += value; }
900 remove { base.Paint -= value; }
904 [EditorBrowsable(EditorBrowsableState.Never)]
905 public new event EventHandler TextChanged {
906 add { base.TextChanged += value; }
907 remove { base.TextChanged -= value; }
910 public event DrawItemEventHandler DrawItem;
911 public event EventHandler SelectedIndexChanged;
915 #region Class TabPage.ControlCollection
916 public class ControlCollection : System.Windows.Forms.Control.ControlCollection {
918 private TabControl owner;
919 private ArrayList list = new ArrayList ();
921 public ControlCollection (TabControl owner) : base (owner)
926 public override void Add (Control value)
928 if (!(value is TabPage))
929 throw new ArgumentException ("Cannot add " +
930 value.GetType ().Name + " to TabControl. " +
931 "Only TabPages can be directly added to TabControls.");
933 value.Visible = false;
936 owner.SelectedIndex = 0;
938 // Setting the selected index will calc the tab rows so
939 // we don't need to do it again
940 owner.ResizeTabPages ();
944 #endregion // Class TabPage.ControlCollection
946 #region Class TabPage.TabPageCollection
947 public class TabPageCollection : IList, ICollection, IEnumerable {
949 private TabControl owner;
950 private IList controls;
952 public TabPageCollection (TabControl owner)
955 throw new ArgumentNullException ("Value cannot be null.");
957 controls = owner.Controls;
961 public virtual int Count {
962 get { return owner.Controls.Count; }
965 public virtual bool IsReadOnly {
966 get { return false; }
969 public virtual TabPage this [int index] {
971 return owner.GetTab (index);
974 owner.SetTab (index, value);
978 bool ICollection.IsSynchronized {
979 get { return false; }
982 object ICollection.SyncRoot {
986 bool IList.IsFixedSize {
987 get { return false; }
990 object IList.this [int index] {
992 return owner.GetTab (index);
995 owner.SetTab (index, (TabPage) value);
999 public void Add (TabPage page)
1002 throw new ArgumentNullException ("Value cannot be null.");
1003 owner.Controls.Add (page);
1006 public void AddRange (TabPage [] pages)
1009 throw new ArgumentNullException ("Value cannot be null.");
1010 owner.Controls.AddRange (pages);
1013 public virtual void Clear ()
1015 owner.Controls.Clear ();
1018 public bool Contains (TabPage page)
1021 throw new ArgumentNullException ("Value cannot be null.");
1022 return owner.Controls.Contains (page);
1025 public virtual IEnumerator GetEnumerator ()
1027 return owner.Controls.GetEnumerator ();
1030 public int IndexOf (TabPage page)
1032 return owner.Controls.IndexOf (page);
1035 public void Remove (TabPage page)
1037 owner.Controls.Remove (page);
1040 public virtual void RemoveAt (int index)
1042 owner.Controls.RemoveAt (index);
1045 void ICollection.CopyTo (Array dest, int index)
1047 owner.Controls.CopyTo (dest, index);
1050 int IList.Add (object value)
1052 // return owner.Controls.Add ((TabPage) value);
1056 bool IList.Contains (object page)
1058 return Contains ((TabPage) page);
1061 int IList.IndexOf (object page)
1063 return IndexOf ((TabPage) page);
1066 void IList.Insert (int index, object value)
1068 controls.Insert (index, (TabPage) value);
1071 void IList.Remove (object value)
1073 Remove ((TabPage) value);
1076 #endregion // Class TabPage.TabPageCollection