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 int SliderPos {
258 get { return slider_pos; }
261 internal ButtonState RightSliderState {
262 get { return right_slider_state; }
265 internal ButtonState LeftSliderState {
266 get { return left_slider_state; }
269 [MonoTODO ("Anything special need to be done?")]
270 protected override CreateParams CreateParams {
272 CreateParams c = base.CreateParams;
273 // Do we need to do anything here?
278 protected override Size DefaultSize {
279 get { return new Size (200, 100); }
282 private Size DefaultItemSize {
284 return ThemeEngine.Current.TabControlDefaultItemSize;
288 public new event EventHandler BackColorChanged {
289 add { base.BackColorChanged += value; }
290 remove { base.BackColorChanged -= value; }
293 public new event EventHandler BackgroundImageChanged {
294 add { base.BackgroundImageChanged += value; }
295 remove { base.BackgroundImageChanged -= value; }
298 public new event EventHandler ForeColorChanged {
299 add { base.ForeColorChanged += value; }
300 remove { base.ForeColorChanged -= value; }
303 public new event PaintEventHandler Paint {
304 add { base.Paint += value; }
305 remove { base.Paint -= value; }
308 public new event EventHandler TextChanged {
309 add { base.TextChanged += value; }
310 remove { base.TextChanged -= value; }
313 public event DrawItemEventHandler DrawItem;
314 public event EventHandler SelectedIndexChanged;
316 public Rectangle GetTabRect (int index)
318 TabPage page = GetTab (index);
319 return page.TabBounds;
322 public Control GetControl (int index)
324 return GetTab (index);
327 protected override Control.ControlCollection CreateControlsInstance ()
329 return new TabControl.ControlCollection (this);
332 protected override void CreateHandle ()
335 base.CreateHandle ();
338 protected override void Dispose (bool disposing)
340 base.Dispose (disposing);
343 protected virtual object [] GetItems ()
345 TabPage [] pages = new TabPage [Controls.Count];
346 Controls.CopyTo (pages, 0);
350 protected virtual object [] GetItems (Type type)
352 object [] pages = (object []) Array.CreateInstance (type, Controls.Count);
353 Controls.CopyTo (pages, 0);
357 protected string GetToolTipText (object item)
359 TabPage page = (TabPage) item;
360 return page.ToolTipText;
363 protected override void WndProc (ref Message m)
365 switch ((Msg) m.Msg) {
367 PaintEventArgs paint_event;
368 paint_event = XplatUI.PaintEventStart (Handle);
369 PaintInternal (paint_event);
370 XplatUI.PaintEventEnd (Handle);
373 base.WndProc (ref m);
378 private bool CanScrollRight {
380 if (TabPages [TabCount - 1].TabBounds.Right > ClientRectangle.Right - 40)
386 private bool CanScrollLeft {
387 get { return slider_pos > 0; }
390 private void MouseDownHandler (object sender, MouseEventArgs e)
393 Rectangle right = ThemeEngine.Current.GetTabControlRightScrollRect (this);
394 Rectangle left = ThemeEngine.Current.GetTabControlLeftScrollRect (this);
395 if (right.Contains (e.X, e.Y)) {
396 right_slider_state = ButtonState.Pushed;
397 if (CanScrollRight) {
403 } else if (left.Contains (e.X, e.Y)) {
404 left_slider_state = ButtonState.Pushed;
415 int count = Controls.Count;
416 for (int i = SliderPos; i < count; i++) {
417 if (!GetTabRect (i).Contains (e.X, e.Y))
424 private void MouseUpHandler (object sender, MouseEventArgs e)
426 left_slider_state = ButtonState.Normal;
427 right_slider_state = ButtonState.Normal;
431 private void SizeChangedHandler (object sender, EventArgs e)
436 internal void UpdateTabpage (TabPage page)
441 internal int IndexForTabPage (TabPage page)
443 for (int i = 0; i < tab_pages.Count; i++) {
444 if (page == tab_pages [i])
450 private void ResizeTabPages ()
454 Rectangle r = DisplayRectangle;
455 foreach (TabPage page in Controls) {
460 private int MinimumTabWidth {
462 return ThemeEngine.Current.TabControlMinimumTabWidth;
466 private Size TabSpacing {
468 return ThemeEngine.Current.TabControlGetSpacing (this);
472 private void CalcTabRows ()
475 case TabAlignment.Right:
476 case TabAlignment.Left:
477 CalcTabRows (Height);
485 private void CalcTabRows (int row_width)
488 Size spacing = TabSpacing;
493 for (int i = 0; i < TabPages.Count; i++) {
494 TabPage page = TabPages [i];
499 if (SizeMode == TabSizeMode.Fixed) {
500 width = item_size.Width;
502 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
505 if (i == SelectedIndex)
507 if (width < MinimumTabWidth)
508 width = MinimumTabWidth;
510 if (xpos + width > row_width && multiline) {
512 for (int j = 0; j < i; j++) {
516 } else if (xpos + width > row_width) {
520 xpos += width + 1 + spacing.Width;
523 if (SelectedIndex != -1 && TabPages [SelectedIndex].Row != BottomRow)
524 DropRow (TabPages [SelectedIndex].Row);
527 private int BottomRow {
530 case TabAlignment.Right:
531 case TabAlignment.Bottom:
539 private int Direction
543 case TabAlignment.Right:
544 case TabAlignment.Bottom:
552 private void DropRow (int row)
554 int bottom = BottomRow;
555 int direction = Direction;
557 foreach (TabPage page in TabPages) {
558 if (page.Row == row) {
560 } else if (direction == 1 && page.Row < row) {
561 page.Row += direction;
562 } else if (direction == -1 && page.Row > row) {
563 page.Row += direction;
568 private int CalcYPos ()
570 if (Alignment == TabAlignment.Bottom) {
571 Rectangle r = ThemeEngine.Current.GetTabControlDisplayRectangle (this);
577 private int CalcXPos ()
579 if (Alignment == TabAlignment.Right) {
580 Rectangle r = ThemeEngine.Current.GetTabControlDisplayRectangle (this);
587 private void SizeTabs ()
590 case TabAlignment.Right:
591 case TabAlignment.Left:
600 private void SizeTabsV (int row_width)
604 Size spacing = TabSpacing;
605 int size = item_size.Height + 2 + spacing.Width;
606 int xpos = CalcXPos ();
608 if (TabPages.Count == 0)
611 prev_row = TabPages [0].Row;
613 for (int i = 0; i < TabPages.Count; i++) {
614 TabPage page = TabPages [i];
617 if (SizeMode == TabSizeMode.Fixed) {
618 width = item_size.Width;
620 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
623 if (width < MinimumTabWidth)
624 width = MinimumTabWidth;
625 if (page.Row != prev_row)
628 page.TabBounds = new Rectangle (xpos + (row_count - page.Row) * ((item_size.Height - 2) + spacing.Width),
629 ypos, item_size.Height - 2, width);
631 ypos += width + spacing.Width;
635 if (SelectedIndex != -1) {
636 TabPage page = TabPages [SelectedIndex];
637 ExpandSelected (TabPages [SelectedIndex], 1, row_width - 1);
641 private void SizeTabs (int row_width)
643 int ypos = CalcYPos ();
645 Size spacing = TabSpacing;
646 int size = item_size.Width + 2 + (spacing.Width * 2);
650 if (TabPages.Count == 0)
653 prev_row = TabPages [0].Row;
655 for (int i = slider_pos; i < TabPages.Count; i++) {
656 TabPage page = TabPages [i];
659 if (SizeMode == TabSizeMode.Fixed) {
660 width = item_size.Width;
662 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
665 if (width < MinimumTabWidth)
666 width = MinimumTabWidth;
667 if (page.Row != prev_row)
670 page.TabBounds = new Rectangle (xpos,
671 ypos + (row_count - page.Row) * (item_size.Height + spacing.Height),
672 width, item_size.Height);
674 if (page.Row != prev_row) {
675 if (SizeMode == TabSizeMode.FillToRight) {
676 FillRow (begin_prev, i - 1, ((row_width - TabPages [i - 1].TabBounds.Right) / (i - begin_prev)), spacing);
681 xpos += width + 1 + spacing.Width;
685 if (SizeMode == TabSizeMode.FillToRight) {
686 FillRow (begin_prev, TabPages.Count - 1,
687 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)), spacing);
690 if (SelectedIndex != -1) {
691 TabPage page = TabPages [SelectedIndex];
692 ExpandSelected (TabPages [SelectedIndex], 2, row_width - 1);
696 private void FillRow (int start, int end, int amount, Size spacing)
698 int xpos = TabPages [start].TabBounds.Left;
699 for (int i = start; i <= end; i++) {
700 TabPage page = TabPages [i];
702 int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount);
704 page.TabBounds = new Rectangle (left, page.TabBounds.Top,
705 width, page.TabBounds.Height);
706 xpos = page.TabBounds.Right + 1 + spacing.Width;
710 private void ExpandSelected (TabPage page, int left_edge, int right_edge)
712 if (Appearance != TabAppearance.Normal)
715 if (Alignment == TabAlignment.Top || Alignment == TabAlignment.Bottom) {
716 int l = page.TabBounds.Left - 4;
717 int r = page.TabBounds.Right + 4;
718 int y = page.TabBounds.Y;
719 int h = page.TabBounds.Height + 2;
723 if (r > right_edge && SizeMode != TabSizeMode.Normal)
725 if (Alignment == TabAlignment.Top)
727 if (Alignment == TabAlignment.Bottom)
730 page.TabBounds = new Rectangle (l, y, r - l, h);
732 int l = page.TabBounds.Left - 3;
733 int r = page.TabBounds.Right + 3;
734 int t = page.TabBounds.Top - 3;
735 int b = page.TabBounds.Bottom + 3;
742 page.TabBounds = new Rectangle (l, t, r - l, b - t);
746 private void PaintInternal (PaintEventArgs pe)
748 if (this.Width <= 0 || this.Height <= 0 || this.Visible == false)
752 pe.Graphics.DrawImageUnscaled (ImageBuffer, 0, 0);
753 ImageBuffer.Save ("ImageBuffer.bmp");
754 // On MS the Paint event never seems to be raised
757 private void Redraw (bool recalculate)
768 ThemeEngine.Current.DrawTabControl (DeviceContext, ClientRectangle, this);
772 private TabPage GetTab (int index)
774 return Controls [index] as TabPage;
777 private void SetTab (int index, TabPage value)
779 ((IList) Controls).Insert (index, value);
783 public class ControlCollection : System.Windows.Forms.Control.ControlCollection {
785 private TabControl owner;
786 private ArrayList list = new ArrayList ();
788 public ControlCollection (TabControl owner) : base (owner)
793 public override void Add (Control value)
795 if (!(value is TabPage))
796 throw new ArgumentException ("Cannot add " +
797 value.GetType ().Name + " to TabControl. " +
798 "Only TabPages can be directly added to TabControls.");
800 value.Visible = false;
803 owner.SelectedIndex = 0;
805 // Setting the selected index will calc the tab rows so
806 // we don't need to do it again
807 owner.CalcTabRows ();
812 public class TabPageCollection : IList, ICollection, IEnumerable {
814 private TabControl owner;
815 private IList controls;
817 public TabPageCollection (TabControl owner)
820 throw new ArgumentNullException ("Value cannot be null.");
822 controls = owner.Controls;
825 public virtual int Count {
826 get { return owner.Controls.Count; }
829 public virtual bool IsReadOnly {
830 get { return false; }
833 public virtual TabPage this [int index] {
835 return owner.GetTab (index);
838 owner.SetTab (index, value);
842 bool ICollection.IsSynchronized {
843 get { return false; }
846 object ICollection.SyncRoot {
850 bool IList.IsFixedSize {
851 get { return false; }
854 object IList.this [int index] {
856 return owner.GetTab (index);
859 owner.SetTab (index, (TabPage) value);
863 public void Add (TabPage page)
866 throw new ArgumentNullException ("Value cannot be null.");
867 owner.Controls.Add (page);
870 public void AddRange (TabPage [] pages)
873 throw new ArgumentNullException ("Value cannot be null.");
874 owner.Controls.AddRange (pages);
877 public virtual void Clear ()
879 owner.Controls.Clear ();
882 public bool Contains (TabPage page)
885 throw new ArgumentNullException ("Value cannot be null.");
886 return owner.Controls.Contains (page);
889 public virtual IEnumerator GetEnumerator ()
891 return owner.Controls.GetEnumerator ();
894 public int IndexOf (TabPage page)
896 return owner.Controls.IndexOf (page);
899 public void Remove (TabPage page)
901 owner.Controls.Remove (page);
904 public virtual void RemoveAt (int index)
906 owner.Controls.RemoveAt (index);
909 void ICollection.CopyTo (Array dest, int index)
911 owner.Controls.CopyTo (dest, index);
914 int IList.Add (object value)
916 // return owner.Controls.Add ((TabPage) value);
920 bool IList.Contains (object page)
922 return Contains ((TabPage) page);
925 int IList.IndexOf (object page)
927 return IndexOf ((TabPage) page);
930 void IList.Insert (int index, object value)
932 controls.Insert (index, (TabPage) value);
935 void IList.Remove (object value)
937 Remove ((TabPage) value);