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)
28 using System
.Collections
;
31 namespace System
.Windows
.Forms
{
33 public class TabControl
: Control
{
35 private int selected_index
= -1;
36 private TabAlignment alignment
;
37 private TabAppearance appearance
;
38 private TabDrawMode draw_mode
;
39 private bool multiline
;
40 private ImageList image_list
;
41 private Size item_size
= Size
.Empty
;
42 private Point padding
;
43 private int row_count
= 1;
44 private bool hottrack
;
45 private TabPageCollection tab_pages
;
46 private bool show_tool_tips
;
47 private TabSizeMode size_mode
;
49 private Rectangle display_rect
;
50 private bool show_slider
= false;
51 private ButtonState right_slider_state
;
52 private ButtonState left_slider_state
;
53 private int slider_pos
= 0;
57 tab_pages
= new TabPageCollection (this);
58 SetStyle (ControlStyles
.UserPaint
, true);
59 padding
= ThemeEngine
.Current
.TabControlDefaultPadding
;
60 item_size
= ThemeEngine
.Current
.TabControlDefaultItemSize
;
62 MouseDown
+= new MouseEventHandler (MouseDownHandler
);
63 MouseUp
+= new MouseEventHandler (MouseUpHandler
);
64 SizeChanged
+= new EventHandler (SizeChangedHandler
);
67 public TabAlignment Alignment
{
68 get { return alignment; }
70 if (alignment
== value)
73 if (alignment
== TabAlignment
.Left
|| alignment
== TabAlignment
.Right
)
79 public TabAppearance Appearance
{
80 get { return appearance; }
82 if (appearance
== value)
89 public override Color BackColor
{
90 get { return base.BackColor; }
91 set { /* nothing happens on set on MS */ }
94 public override Image BackgroundImage
{
95 get { return base.BackgroundImage; }
96 set { base.BackgroundImage = value; }
99 public override Rectangle DisplayRectangle
{
101 return ThemeEngine
.Current
.GetTabControlDisplayRectangle (this);
105 public TabDrawMode DrawMode
{
106 get { return draw_mode; }
108 if (draw_mode
== value)
115 public override Color ForeColor
{
116 get { return base.ForeColor; }
117 set { base.ForeColor = value; }
120 public bool HotTrack
{
121 get { return hottrack; }
123 if (hottrack
== value)
130 public ImageList ImageList
{
131 get { return image_list; }
132 set { image_list = value; }
135 public Size ItemSize
{
140 if (value.Height
< 0 || value.Width
< 0)
141 throw new ArgumentException ("'" + value + "' is not a valid value for 'ItemSize'.");
147 public bool Multiline
{
148 get { return multiline; }
150 if (multiline
== value)
153 if (!multiline
&& alignment
== TabAlignment
.Left
|| alignment
== TabAlignment
.Right
)
154 alignment
= TabAlignment
.Top
;
159 public Point Padding
{
160 get { return padding; }
162 if (value.X
< 0 || value.Y
< 0)
163 throw new ArgumentException ("'" + value + "' is not a valid value for 'Padding'.");
164 if (padding
== value)
172 public int RowCount
{
173 get { return row_count; }
176 public int SelectedIndex
{
177 get { return selected_index; }
179 if (selected_index
== value)
181 if (selected_index
< -1) {
182 throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
183 "'value' must be greater than or equal to -1.");
187 if (selected_index
!= -1)
188 Controls
[selected_index
].Visible
= false;
189 selected_index
= value;
190 if (selected_index
!= -1)
191 Controls
[selected_index
].Visible
= true;
194 if (SelectedIndex
!= -1 && TabPages
[SelectedIndex
].Row
!= BottomRow
)
195 DropRow (TabPages
[selected_index
].Row
);
202 public TabPage SelectedTab
{
204 if (selected_index
== -1)
206 return tab_pages
[selected_index
];
209 int index
= IndexForTabPage (value);
210 if (index
== selected_index
)
212 selected_index
= index
;
217 public bool ShowToolTips
{
218 get { return show_tool_tips; }
220 if (show_tool_tips
== value)
222 show_tool_tips
= value;
227 public TabSizeMode SizeMode
{
228 get { return size_mode; }
230 if (size_mode
== value)
237 public int TabCount
{
239 return tab_pages
.Count
;
243 public TabPageCollection TabPages
{
244 get { return tab_pages; }
247 public override string Text
{
248 get { return base.Text; }
249 set { base.Text = value; }
252 internal bool ShowSlider
{
253 get { return show_slider; }
254 set { show_slider = value; }
257 internal ButtonState RightSliderState
{
258 get { return right_slider_state; }
261 internal ButtonState LeftSliderState
{
262 get { return left_slider_state; }
265 [MonoTODO ("Anything special need to be done?")]
266 protected override CreateParams CreateParams
{
268 CreateParams c
= base.CreateParams
;
269 // Do we need to do anything here?
274 protected override Size DefaultSize
{
275 get { return new Size (200, 100); }
278 private Size DefaultItemSize
{
280 return ThemeEngine
.Current
.TabControlDefaultItemSize
;
284 public new event EventHandler BackColorChanged
{
285 add { base.BackColorChanged += value; }
286 remove { base.BackColorChanged -= value; }
289 public new event EventHandler BackgroundImageChanged
{
290 add { base.BackgroundImageChanged += value; }
291 remove { base.BackgroundImageChanged -= value; }
294 public new event EventHandler ForeColorChanged
{
295 add { base.ForeColorChanged += value; }
296 remove { base.ForeColorChanged -= value; }
299 public new event PaintEventHandler Paint
{
300 add { base.Paint += value; }
301 remove { base.Paint -= value; }
304 public new event EventHandler TextChanged
{
305 add { base.TextChanged += value; }
306 remove { base.TextChanged -= value; }
309 public event DrawItemEventHandler DrawItem
;
310 public event EventHandler SelectedIndexChanged
;
312 public Rectangle
GetTabRect (int index
)
314 TabPage page
= GetTab (index
);
315 return page
.TabBounds
;
318 public Control
GetControl (int index
)
320 return GetTab (index
);
323 protected override Control
.ControlCollection
CreateControlsInstance ()
325 return new TabControl
.ControlCollection (this);
328 protected override void CreateHandle ()
331 base.CreateHandle ();
334 protected override void Dispose (bool disposing
)
336 base.Dispose (disposing
);
339 protected virtual object [] GetItems ()
341 TabPage
[] pages
= new TabPage
[Controls
.Count
];
342 Controls
.CopyTo (pages
, 0);
346 protected virtual object [] GetItems (Type type
)
348 object [] pages
= (object []) Array
.CreateInstance (type
, Controls
.Count
);
349 Controls
.CopyTo (pages
, 0);
353 protected string GetToolTipText (object item
)
355 TabPage page
= (TabPage
) item
;
356 return page
.ToolTipText
;
359 protected override void WndProc (ref Message m
)
361 switch ((Msg
) m
.Msg
) {
363 PaintEventArgs paint_event
;
364 paint_event
= XplatUI
.PaintEventStart (Handle
);
365 PaintInternal (paint_event
);
366 XplatUI
.PaintEventEnd (Handle
);
369 base.WndProc (ref m
);
374 private bool CanScrollRight
{
375 get { return slider_pos != 0; }
378 private bool CanScrollLeft
{
380 if (TabPages
[TabCount
- 1].TabBounds
.Right
> ClientRectangle
.Right
- 40)
386 private void MouseDownHandler (object sender
, MouseEventArgs e
)
389 Rectangle right
= ThemeEngine
.Current
.GetTabControlRightScrollRect (this);
390 Rectangle left
= ThemeEngine
.Current
.GetTabControlLeftScrollRect (this);
391 if (right
.Contains (e
.X
, e
.Y
)) {
392 right_slider_state
= ButtonState
.Pushed
;
393 if (CanScrollRight
) {
399 } else if (left
.Contains (e
.X
, e
.Y
)) {
400 left_slider_state
= ButtonState
.Pushed
;
411 int count
= Controls
.Count
;
412 for (int i
= 0; i
<count
; i
++) {
413 if (!GetTabRect (i
).Contains (e
.X
, e
.Y
))
420 private void MouseUpHandler (object sender
, MouseEventArgs e
)
422 left_slider_state
= ButtonState
.Normal
;
423 right_slider_state
= ButtonState
.Normal
;
427 private void SizeChangedHandler (object sender
, EventArgs e
)
432 internal void UpdateTabpage (TabPage page
)
437 internal int IndexForTabPage (TabPage page
)
439 for (int i
= 0; i
< tab_pages
.Count
; i
++) {
440 if (page
== tab_pages
[i
])
446 private void ResizeTabPages ()
450 Rectangle r
= DisplayRectangle
;
451 foreach (TabPage page
in Controls
) {
456 private int MinimumTabWidth
{
458 return ThemeEngine
.Current
.TabControlMinimumTabWidth
;
462 private Size TabSpacing
{
464 return ThemeEngine
.Current
.TabControlGetSpacing (this);
468 private void CalcTabRows ()
471 case TabAlignment
.Right
:
472 case TabAlignment
.Left
:
473 CalcTabRows (Height
);
481 private void CalcTabRows (int row_width
)
484 Size spacing
= TabSpacing
;
489 for (int i
= 0; i
< TabPages
.Count
; i
++) {
490 TabPage page
= TabPages
[i
];
495 if (SizeMode
== TabSizeMode
.Fixed
) {
496 width
= item_size
.Width
;
498 width
= (int) DeviceContext
.MeasureString (page
.Text
, Font
).Width
+ (Padding
.X
* 2);
501 if (i
== SelectedIndex
)
503 if (width
< MinimumTabWidth
)
504 width
= MinimumTabWidth
;
506 if (xpos
+ width
> row_width
&& multiline
) {
508 for (int j
= 0; j
< i
; j
++) {
512 } else if (xpos
+ width
> row_width
) {
516 xpos
+= width
+ 1 + spacing
.Width
;
519 if (SelectedIndex
!= -1 && TabPages
[SelectedIndex
].Row
!= BottomRow
)
520 DropRow (TabPages
[SelectedIndex
].Row
);
523 private int BottomRow
{
526 case TabAlignment
.Right
:
527 case TabAlignment
.Bottom
:
535 private int Direction
539 case TabAlignment
.Right
:
540 case TabAlignment
.Bottom
:
548 private void DropRow (int row
)
550 int bottom
= BottomRow
;
551 int direction
= Direction
;
553 foreach (TabPage page
in TabPages
) {
554 if (page
.Row
== row
) {
556 } else if (direction
== 1 && page
.Row
< row
) {
557 page
.Row
+= direction
;
558 } else if (direction
== -1 && page
.Row
> row
) {
559 page
.Row
+= direction
;
564 private int CalcYPos ()
566 if (Alignment
== TabAlignment
.Bottom
) {
567 Rectangle r
= ThemeEngine
.Current
.GetTabControlDisplayRectangle (this);
573 private int CalcXPos ()
575 if (Alignment
== TabAlignment
.Right
) {
576 Rectangle r
= ThemeEngine
.Current
.GetTabControlDisplayRectangle (this);
583 private void SizeTabs ()
586 case TabAlignment
.Right
:
587 case TabAlignment
.Left
:
596 private void SizeTabsV (int row_width
)
600 Size spacing
= TabSpacing
;
601 int size
= item_size
.Height
+ 2 + spacing
.Width
;
602 int xpos
= CalcXPos ();
604 if (TabPages
.Count
== 0)
607 prev_row
= TabPages
[0].Row
;
609 for (int i
= 0; i
< TabPages
.Count
; i
++) {
610 TabPage page
= TabPages
[i
];
613 if (SizeMode
== TabSizeMode
.Fixed
) {
614 width
= item_size
.Width
;
616 width
= (int) DeviceContext
.MeasureString (page
.Text
, Font
).Width
+ (Padding
.X
* 2);
619 if (width
< MinimumTabWidth
)
620 width
= MinimumTabWidth
;
621 if (page
.Row
!= prev_row
)
624 page
.TabBounds
= new Rectangle (xpos
+ (row_count
- page
.Row
) * ((item_size
.Height
- 2) + spacing
.Width
),
625 ypos
, item_size
.Height
- 2, width
);
627 ypos
+= width
+ spacing
.Width
;
631 if (SelectedIndex
!= -1) {
632 TabPage page
= TabPages
[SelectedIndex
];
633 ExpandSelected (TabPages
[SelectedIndex
], 1, row_width
- 1);
637 private void SizeTabs (int row_width
)
639 int ypos
= CalcYPos ();
641 Size spacing
= TabSpacing
;
642 int size
= item_size
.Width
+ 2 + (spacing
.Width
* 2);
643 int xpos
= 4 + (slider_pos
* size
);
646 if (TabPages
.Count
== 0)
649 prev_row
= TabPages
[0].Row
;
651 for (int i
= 0; i
< TabPages
.Count
; i
++) {
652 TabPage page
= TabPages
[i
];
655 if (SizeMode
== TabSizeMode
.Fixed
) {
656 width
= item_size
.Width
;
658 width
= (int) DeviceContext
.MeasureString (page
.Text
, Font
).Width
+ (Padding
.X
* 2);
661 if (width
< MinimumTabWidth
)
662 width
= MinimumTabWidth
;
663 if (page
.Row
!= prev_row
)
664 xpos
= 4 + (slider_pos
* size
);
666 page
.TabBounds
= new Rectangle (xpos
,
667 ypos
+ (row_count
- page
.Row
) * (item_size
.Height
+ spacing
.Height
),
668 width
, item_size
.Height
);
671 if (page
.Row
!= prev_row
) {
672 if (SizeMode
== TabSizeMode
.FillToRight
) {
673 FillRow (begin_prev
, i
- 1, ((row_width
- TabPages
[i
- 1].TabBounds
.Right
) / (i
- begin_prev
)), spacing
);
678 xpos
+= width
+ 1 + spacing
.Width
;
682 if (SizeMode
== TabSizeMode
.FillToRight
) {
683 FillRow (begin_prev
, TabPages
.Count
- 1,
684 ((row_width
- TabPages
[TabPages
.Count
- 1].TabBounds
.Right
) / (TabPages
.Count
- begin_prev
)), spacing
);
687 if (SelectedIndex
!= -1) {
688 TabPage page
= TabPages
[SelectedIndex
];
689 ExpandSelected (TabPages
[SelectedIndex
], 2, row_width
- 1);
693 private void FillRow (int start
, int end
, int amount
, Size spacing
)
695 int xpos
= TabPages
[start
].TabBounds
.Left
;
696 for (int i
= start
; i
<= end
; i
++) {
697 TabPage page
= TabPages
[i
];
699 int width
= (i
== end
? Width
- left
- 3 : page
.TabBounds
.Width
+ amount
);
701 page
.TabBounds
= new Rectangle (left
, page
.TabBounds
.Top
,
702 width
, page
.TabBounds
.Height
);
703 xpos
= page
.TabBounds
.Right
+ 1 + spacing
.Width
;
707 private void ExpandSelected (TabPage page
, int left_edge
, int right_edge
)
709 if (Appearance
!= TabAppearance
.Normal
)
712 if (Alignment
== TabAlignment
.Top
|| Alignment
== TabAlignment
.Bottom
) {
713 int l
= page
.TabBounds
.Left
- 4;
714 int r
= page
.TabBounds
.Right
+ 4;
715 int y
= page
.TabBounds
.Y
;
716 int h
= page
.TabBounds
.Height
+ 2;
720 if (r
> right_edge
&& SizeMode
!= TabSizeMode
.Normal
)
722 if (Alignment
== TabAlignment
.Top
)
724 if (Alignment
== TabAlignment
.Bottom
)
727 page
.TabBounds
= new Rectangle (l
, y
, r
- l
, h
);
729 int l
= page
.TabBounds
.Left
- 3;
730 int r
= page
.TabBounds
.Right
+ 3;
731 int t
= page
.TabBounds
.Top
- 3;
732 int b
= page
.TabBounds
.Bottom
+ 3;
739 page
.TabBounds
= new Rectangle (l
, t
, r
- l
, b
- t
);
743 private void PaintInternal (PaintEventArgs pe
)
745 if (this.Width
<= 0 || this.Height
<= 0 || this.Visible
== false)
749 pe
.Graphics
.DrawImageUnscaled (ImageBuffer
, 0, 0);
750 ImageBuffer
.Save ("ImageBuffer.bmp");
751 // On MS the Paint event never seems to be raised
754 private void Redraw (bool recalculate
)
765 ThemeEngine
.Current
.DrawTabControl (DeviceContext
, ClientRectangle
, this);
769 private TabPage
GetTab (int index
)
771 return Controls
[index
] as TabPage
;
774 private void SetTab (int index
, TabPage
value)
776 ((IList
) Controls
).Insert (index
, value);
780 public class ControlCollection
: System
.Windows
.Forms
.Control
.ControlCollection
{
782 private TabControl owner
;
783 private ArrayList list
= new ArrayList ();
785 public ControlCollection (TabControl owner
) : base (owner
)
790 public override void Add (Control
value)
792 if (!(value is TabPage
))
793 throw new ArgumentException ("Cannot add " +
794 value.GetType ().Name
+ " to TabControl. " +
795 "Only TabPages can be directly added to TabControls.");
797 value.Visible
= false;
800 owner
.SelectedIndex
= 0;
802 // Setting the selected index will calc the tab rows so
803 // we don't need to do it again
804 owner
.CalcTabRows ();
809 public class TabPageCollection
: IList
, ICollection
, IEnumerable
{
811 private TabControl owner
;
812 private IList controls
;
814 public TabPageCollection (TabControl owner
)
817 throw new ArgumentNullException ("Value cannot be null.");
819 controls
= owner
.Controls
;
822 public virtual int Count
{
823 get { return owner.Controls.Count; }
826 public virtual bool IsReadOnly
{
827 get { return false; }
830 public virtual TabPage
this [int index
] {
832 return owner
.GetTab (index
);
835 owner
.SetTab (index
, value);
839 bool ICollection
.IsSynchronized
{
840 get { return false; }
843 object ICollection
.SyncRoot
{
847 bool IList
.IsFixedSize
{
848 get { return false; }
851 object IList
.this [int index
] {
853 return owner
.GetTab (index
);
856 owner
.SetTab (index
, (TabPage
) value);
860 public void Add (TabPage page
)
863 throw new ArgumentNullException ("Value cannot be null.");
864 owner
.Controls
.Add (page
);
867 public void AddRange (TabPage
[] pages
)
870 throw new ArgumentNullException ("Value cannot be null.");
871 owner
.Controls
.AddRange (pages
);
874 public virtual void Clear ()
876 owner
.Controls
.Clear ();
879 public bool Contains (TabPage page
)
882 throw new ArgumentNullException ("Value cannot be null.");
883 return owner
.Controls
.Contains (page
);
886 public virtual IEnumerator
GetEnumerator ()
888 return owner
.Controls
.GetEnumerator ();
891 public int IndexOf (TabPage page
)
893 return owner
.Controls
.IndexOf (page
);
896 public void Remove (TabPage page
)
898 owner
.Controls
.Remove (page
);
901 public virtual void RemoveAt (int index
)
903 owner
.Controls
.RemoveAt (index
);
906 void ICollection
.CopyTo (Array dest
, int index
)
908 owner
.Controls
.CopyTo (dest
, index
);
911 int IList
.Add (object value)
913 // return owner.Controls.Add ((TabPage) value);
917 bool IList
.Contains (object page
)
919 return Contains ((TabPage
) page
);
922 int IList
.IndexOf (object page
)
924 return IndexOf ((TabPage
) page
);
927 void IList
.Insert (int index
, object value)
929 controls
.Insert (index
, (TabPage
) value);
932 void IList
.Remove (object value)
934 Remove ((TabPage
) value);