4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 // Copyright (c) 2006 Jonathan Pobst
24 // Copyright (c) 2007 Ivan N. Zlatev
27 // Jonathan Pobst (monkey@jpobst.com)
28 // Ivan N. Zlatev (contact@i-nz.net)
33 using System.Runtime.InteropServices;
34 using System.ComponentModel;
36 using System.Drawing.Drawing2D;
38 namespace System.Windows.Forms
40 [ComVisibleAttribute (true)]
41 [ClassInterfaceAttribute (ClassInterfaceType.AutoDispatch)]
42 [DefaultEvent ("SplitterMoved")]
43 [Docking (DockingBehavior.AutoDock)]
44 [Designer ("System.Windows.Forms.Design.SplitContainerDesigner, " + Consts.AssemblySystem_Design)]
45 public class SplitContainer : ContainerControl
47 #region Local Variables
48 private FixedPanel fixed_panel;
49 private Orientation orientation;
51 private int splitter_increment;
52 private Rectangle splitter_rectangle;
53 private Rectangle splitter_rectangle_moving;
54 private bool splitter_fixed;
55 private bool splitter_dragging;
56 private int splitter_prev_move;
57 private Cursor restore_cursor;
58 private double fixed_none_ratio;
60 private SplitterPanel panel1;
61 private bool panel1_collapsed;
62 private int panel1_min_size;
64 private SplitterPanel panel2;
65 private bool panel2_collapsed;
66 private int panel2_min_size;
70 static object SplitterMovedEvent = new object ();
71 static object SplitterMovingEvent = new object ();
74 [EditorBrowsable (EditorBrowsableState.Never)]
75 public new event EventHandler AutoSizeChanged {
76 add { base.AutoSizeChanged += value; }
77 remove { base.AutoSizeChanged -= value; }
81 [EditorBrowsable (EditorBrowsableState.Always)]
82 public new event EventHandler BackgroundImageChanged {
83 add { base.BackgroundImageChanged += value; }
84 remove { base.BackgroundImageChanged -= value; }
88 [EditorBrowsable (EditorBrowsableState.Never)]
89 public new event EventHandler BackgroundImageLayoutChanged {
90 add { base.BackgroundImageLayoutChanged += value; }
91 remove { base.BackgroundImageLayoutChanged -= value; }
95 [EditorBrowsable (EditorBrowsableState.Never)]
96 public new event ControlEventHandler ControlAdded {
97 add { base.ControlAdded += value; }
98 remove { base.ControlAdded -= value; }
102 [EditorBrowsable (EditorBrowsableState.Never)]
103 public new event ControlEventHandler ControlRemoved {
104 add { base.ControlRemoved += value; }
105 remove { base.ControlRemoved -= value; }
109 [EditorBrowsable (EditorBrowsableState.Never)]
110 public new event EventHandler PaddingChanged {
111 add { base.PaddingChanged += value; }
112 remove { base.PaddingChanged -= value; }
115 public event SplitterEventHandler SplitterMoved {
116 add { Events.AddHandler (SplitterMovedEvent, value); }
117 remove { Events.RemoveHandler (SplitterMovedEvent, value); }
120 public event SplitterCancelEventHandler SplitterMoving {
121 add { Events.AddHandler (SplitterMovingEvent, value); }
122 remove { Events.RemoveHandler (SplitterMovingEvent, value); }
126 [EditorBrowsable (EditorBrowsableState.Never)]
127 public new event EventHandler TextChanged {
128 add { base.TextChanged += value; }
129 remove { base.TextChanged -= value; }
133 #region Public Constructors
134 public SplitContainer ()
136 SetStyle (ControlStyles.SupportsTransparentBackColor, true);
137 SetStyle (ControlStyles.OptimizedDoubleBuffer, true);
139 fixed_panel = FixedPanel.None;
140 orientation = Orientation.Vertical;
142 splitter_rectangle = new Rectangle (50, 0, 4, this.Height);
143 splitter_increment = 1;
144 splitter_prev_move = -1;
145 restore_cursor = null;
147 splitter_fixed = false;
148 panel1_collapsed = false;
149 panel2_collapsed = false;
150 panel1_min_size = 25;
151 panel2_min_size = 25;
153 panel1 = new SplitterPanel (this);
154 panel2 = new SplitterPanel (this);
155 panel1.Size = new Size (50, 50);
158 this.Controls.Add (panel2);
159 this.Controls.Add (panel1);
163 #region Public Properties
165 [EditorBrowsable (EditorBrowsableState.Never)]
167 [DefaultValue (false)]
168 public override bool AutoScroll {
169 get { return base.AutoScroll; }
170 set { base.AutoScroll = value; }
174 [EditorBrowsable (EditorBrowsableState.Never)]
175 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
176 new public Size AutoScrollMargin {
177 get { return base.AutoScrollMargin; }
178 set { base.AutoScrollMargin = value; }
182 [EditorBrowsable (EditorBrowsableState.Never)]
183 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
184 new public Size AutoScrollMinSize {
185 get { return base.AutoScrollMinSize; }
186 set { base.AutoScrollMinSize = value; }
190 [DefaultValue ("{X=0,Y=0}")]
191 [EditorBrowsable (EditorBrowsableState.Never)]
192 public override Point AutoScrollOffset {
193 get { return base.AutoScrollOffset; }
194 set { base.AutoScrollOffset = value; }
198 [EditorBrowsable (EditorBrowsableState.Never)]
199 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
200 new public Point AutoScrollPosition {
201 get { return base.AutoScrollPosition; }
202 set { base.AutoScrollPosition = value; }
206 [EditorBrowsable (EditorBrowsableState.Never)]
207 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
208 public override bool AutoSize {
209 get { return base.AutoSize; }
210 set { base.AutoSize = value; }
214 [EditorBrowsable (EditorBrowsableState.Always)]
215 public override Image BackgroundImage {
216 get { return base.BackgroundImage; }
217 set { base.BackgroundImage = value; }
221 [EditorBrowsable (EditorBrowsableState.Never)]
222 public override ImageLayout BackgroundImageLayout {
223 get { return base.BackgroundImageLayout; }
224 set { base.BackgroundImageLayout = value; }
228 public override BindingContext BindingContext {
229 get { return base.BindingContext; }
230 set { base.BindingContext = value; }
233 // MSDN says default is Fixed3D, creating a new SplitContainer says otherwise.
234 [DefaultValue (BorderStyle.None)]
236 public BorderStyle BorderStyle {
237 get { return panel1.BorderStyle; }
239 if (!Enum.IsDefined (typeof (BorderStyle), value))
240 throw new InvalidEnumArgumentException (string.Format ("Enum argument value '{0}' is not valid for BorderStyle", value));
242 panel1.BorderStyle = value;
243 panel2.BorderStyle = value;
247 [EditorBrowsable (EditorBrowsableState.Never)]
248 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
249 new public ControlCollection Controls { get { return base.Controls; } }
251 new public DockStyle Dock {
252 get { return base.Dock; }
253 set { base.Dock = value; }
256 [DefaultValue (FixedPanel.None)]
257 public FixedPanel FixedPanel {
258 get { return this.fixed_panel; }
260 if (!Enum.IsDefined (typeof (FixedPanel), value))
261 throw new InvalidEnumArgumentException (string.Format ("Enum argument value '{0}' is not valid for FixedPanel", value));
263 this.fixed_panel = value;
268 [DefaultValue (false)]
269 public bool IsSplitterFixed {
270 get { return splitter_fixed; }
271 set { splitter_fixed = value; }
275 [DefaultValue (Orientation.Vertical)]
276 public Orientation Orientation {
277 get { return this.orientation; }
279 if (!Enum.IsDefined (typeof (Orientation), value))
280 throw new InvalidEnumArgumentException (string.Format ("Enum argument value '{0}' is not valid for Orientation", value));
282 if (this.orientation != value) {
283 if (value == Orientation.Vertical) {
284 splitter_rectangle.Width = splitter_rectangle.Height;
285 splitter_rectangle.X = splitter_rectangle.Y;
287 splitter_rectangle.Height = splitter_rectangle.Width;
288 splitter_rectangle.Y = splitter_rectangle.X;
291 this.orientation = value;
292 this.UpdateSplitter ();
298 [EditorBrowsable (EditorBrowsableState.Never)]
299 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
300 new public Padding Padding {
301 get { return base.Padding; }
302 set { base.Padding = value; }
305 [Localizable (false)]
306 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
307 public SplitterPanel Panel1 { get { return this.panel1; } }
309 [DefaultValue (false)]
310 public bool Panel1Collapsed {
311 get { return this.panel1_collapsed; }
313 if (panel1_collapsed != value) {
314 this.panel1_collapsed = value;
315 panel1.Visible = !value;
323 [RefreshProperties (RefreshProperties.All)]
324 public int Panel1MinSize {
325 get { return this.panel1_min_size; }
327 this.panel1_min_size = value;
331 [Localizable (false)]
332 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
333 public SplitterPanel Panel2 { get { return this.panel2; } }
335 [DefaultValue (false)]
336 public bool Panel2Collapsed {
337 get { return this.panel2_collapsed; }
339 if (panel2_collapsed != value) {
340 this.panel2_collapsed = value;
341 panel2.Visible = !value;
349 [RefreshProperties (RefreshProperties.All)]
350 public int Panel2MinSize {
351 get { return this.panel2_min_size; }
352 set { this.panel2_min_size = value; }
355 // MSDN says the default is 40, MS's implementation defaults to 50.
358 [SettingsBindable (true)]
359 public int SplitterDistance {
361 if (orientation == Orientation.Vertical)
362 return this.splitter_rectangle.X;
364 return this.splitter_rectangle.Y;
368 throw new ArgumentOutOfRangeException ();
369 if (orientation == Orientation.Vertical)
370 this.splitter_rectangle.X = value;
372 this.splitter_rectangle.Y = value;
379 [MonoTODO ("Not implemented.")]
380 public int SplitterIncrement {
381 get { return this.splitter_increment; }
382 set { this.splitter_increment = value; }
386 public Rectangle SplitterRectangle { get { return splitter_rectangle; } }
390 public int SplitterWidth {
392 if (orientation == Orientation.Vertical)
393 return this.splitter_rectangle.Width;
395 return this.splitter_rectangle.Height;
399 throw new ArgumentOutOfRangeException ();
401 if (orientation == Orientation.Vertical)
402 this.splitter_rectangle.Width = value;
404 this.splitter_rectangle.Height = value;
410 [DefaultValue (true)]
411 [MonoTODO ("Special focus semantics not implemented")]
412 new public bool TabStop {
413 get { return false; }
418 [EditorBrowsable (EditorBrowsableState.Never)]
420 public override string Text {
421 get { return base.Text; }
422 set { base.Text = value; }
426 #region Protected Properties
427 protected override Size DefaultSize { get { return new Size (150, 100); } }
430 #region Public Methods
431 public void OnSplitterMoved (SplitterEventArgs e)
433 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovedEvent]);
438 public void OnSplitterMoving (SplitterCancelEventArgs e)
440 SplitterCancelEventHandler eh = (SplitterCancelEventHandler)(Events [SplitterMovingEvent]);
446 #region Protected Methods
447 [EditorBrowsable (EditorBrowsableState.Advanced)]
448 protected override ControlCollection CreateControlsInstance ()
450 return new SplitContainerTypedControlCollection (this);
453 [MonoTODO ("Special focus semantics not implemented")]
454 protected override void OnGotFocus (EventArgs e)
459 protected override void OnKeyDown (KeyEventArgs e)
464 protected override void OnKeyUp (KeyEventArgs e)
469 protected override void OnLayout (LayoutEventArgs e)
475 protected override void OnLostFocus (EventArgs e)
477 base.OnLostFocus (e);
480 protected override void OnMouseCaptureChanged (EventArgs e)
482 base.OnMouseCaptureChanged (e);
485 protected override void OnMouseDown (MouseEventArgs e)
487 base.OnMouseDown (e);
488 if (!splitter_fixed && SplitterHitTest (e.Location)) {
489 splitter_dragging = true;
490 SplitterBeginMove (e.Location);
494 protected override void OnMouseLeave (EventArgs e)
496 base.OnMouseLeave (e);
497 SplitterRestoreCursor ();
500 [EditorBrowsable (EditorBrowsableState.Advanced)]
501 protected override void OnMouseMove (MouseEventArgs e)
503 base.OnMouseMove (e);
505 if (splitter_dragging)
506 SplitterMove (e.Location);
508 if (!splitter_fixed && SplitterHitTest (e.Location))
509 SplitterSetCursor (orientation);
513 protected override void OnMouseUp (MouseEventArgs e)
516 if (splitter_dragging) {
517 SplitterEndMove (e.Location, false);
518 SplitterRestoreCursor ();
519 splitter_dragging = false;
523 protected override void OnPaint (PaintEventArgs e)
528 [EditorBrowsable (EditorBrowsableState.Advanced)]
529 protected override void OnRightToLeftChanged (EventArgs e)
531 base.OnRightToLeftChanged (e);
534 [MonoTODO ("Special focus semantics not implemented")]
535 protected override bool ProcessDialogKey (Keys keyData)
537 return base.ProcessDialogKey (keyData);
540 [MonoTODO ("Special focus semantics not implemented")]
541 protected override bool ProcessTabKey (bool forward)
543 return base.ProcessTabKey (forward);
546 [EditorBrowsable (EditorBrowsableState.Advanced)]
547 protected override void ScaleControl (SizeF factor, BoundsSpecified specified)
549 base.ScaleControl (factor, specified);
552 [MonoTODO ("Special focus semantics not implemented")]
553 protected override void Select (bool directed, bool forward)
555 base.Select (directed, forward);
558 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
560 base.SetBoundsCore (x, y, width, height, specified);
563 protected override void WndProc (ref Message msg)
565 base.WndProc (ref msg);
569 #region Private Methods
571 private bool SplitterHitTest (Point location)
573 if (location.X >= splitter_rectangle.X &&
574 location.X <= splitter_rectangle.X + splitter_rectangle.Width &&
575 location.Y >= splitter_rectangle.Y &&
576 location.Y <= splitter_rectangle.Y + splitter_rectangle.Height) {
582 private void SplitterBeginMove (Point location)
584 splitter_prev_move = orientation == Orientation.Vertical ? location.X : location.Y;
585 splitter_rectangle_moving = splitter_rectangle;
588 private void SplitterMove (Point location)
591 XplatUI.DrawReversibleRectangle (this.Handle, splitter_rectangle_moving, 1);
593 int currentMove = orientation == Orientation.Vertical ? location.X : location.Y;
594 int delta = currentMove - splitter_prev_move;
596 if (orientation == Orientation.Vertical) {
597 if (splitter_rectangle_moving.X + delta > this.panel1_min_size &&
598 splitter_rectangle_moving.X + delta + splitter_rectangle_moving.Width < panel2.Location.X + (panel2.Width - this.panel2_min_size))
599 splitter_rectangle_moving.X += delta;
600 } else if (orientation == Orientation.Horizontal) {
601 if (splitter_rectangle_moving.Y + delta > this.panel1_min_size &&
602 splitter_rectangle_moving.Y + delta + splitter_rectangle_moving.Height < panel2.Location.Y + (panel2.Height - this.panel2_min_size))
603 splitter_rectangle_moving.Y += delta;
606 splitter_prev_move = currentMove;
608 OnSplitterMoving (new SplitterCancelEventArgs (location.X, location.Y,
609 splitter_rectangle.X, splitter_rectangle.Y));
611 XplatUI.DrawReversibleRectangle (this.Handle, splitter_rectangle_moving, 1);
614 private void SplitterEndMove (Point location, bool cancel)
617 splitter_rectangle = splitter_rectangle_moving;
620 SplitterEventArgs args = new SplitterEventArgs (location.X, location.Y,
621 splitter_rectangle.X, splitter_rectangle.Y);
622 OnSplitterMoved (args);
625 private void SplitterSetCursor (Orientation orientation)
627 if (restore_cursor == null)
628 restore_cursor = this.Cursor;
629 this.Cursor = orientation == Orientation.Vertical ? Cursors.VSplit : Cursors.HSplit;
632 private void SplitterRestoreCursor ()
634 if (restore_cursor != null) {
635 this.Cursor = restore_cursor;
636 restore_cursor = null;
640 private void UpdateSplitter ()
642 this.SuspendLayout ();
643 panel1.SuspendLayout ();
644 panel2.SuspendLayout ();
646 if (panel1_collapsed) {
647 panel2.Size = this.Size;
648 panel2.Location = new Point (0, 0);
649 } else if (panel2_collapsed) {
650 panel1.Size = this.Size;
651 panel1.Location = new Point (0, 0);
653 panel1.Location = new Point (0, 0);
654 if (orientation == Orientation.Vertical) {
655 splitter_rectangle.Y = 0;
656 panel1.InternalHeight = panel2.InternalHeight = this.Height;
657 panel1.InternalWidth = Math.Max (this.SplitterDistance, panel1_min_size);
658 panel2.Location = new Point (this.SplitterWidth + this.SplitterDistance, 0);
659 panel2.InternalWidth = Math.Max (this.Width - (this.SplitterWidth + this.SplitterDistance), panel2_min_size);
660 fixed_none_ratio = (double) this.Width / (double)this.SplitterDistance;
661 } else if (orientation == Orientation.Horizontal) {
662 splitter_rectangle.X = 0;
663 panel1.InternalWidth = panel2.InternalWidth = this.Width;
664 panel1.InternalHeight = Math.Max (this.SplitterDistance, panel1_min_size);
665 panel2.Location = new Point (0, this.SplitterWidth + this.SplitterDistance);
666 panel2.InternalHeight = Math.Max (this.Height - (this.SplitterWidth + this.SplitterDistance), panel2_min_size);
667 fixed_none_ratio = (double) this.Height / (double)this.SplitterDistance;
670 panel1.ResumeLayout ();
671 panel2.ResumeLayout ();
672 this.ResumeLayout ();
675 private void UpdateLayout ()
677 panel1.SuspendLayout ();
678 panel2.SuspendLayout ();
680 if (panel1_collapsed) {
681 panel2.Size = this.Size;
682 panel2.Location = new Point (0, 0);
683 } else if (panel2_collapsed) {
684 panel1.Size = this.Size;
685 panel1.Location = new Point (0, 0);
687 panel1.Location = new Point (0, 0);
688 if (orientation == Orientation.Vertical) {
689 panel1.Location = new Point (0, 0);
690 panel1.InternalHeight = panel2.InternalHeight = this.Height;
691 splitter_rectangle.Height = this.Height;
693 if (fixed_panel == FixedPanel.None) {
694 splitter_rectangle.X = Math.Max ((int)Math.Floor (((double)this.Width) / fixed_none_ratio), panel1_min_size); //set distance
695 panel1.InternalWidth = this.SplitterDistance;
696 panel2.InternalWidth = this.Width - (this.SplitterWidth + this.SplitterDistance);
697 panel2.Location = new Point (this.SplitterWidth + this.SplitterDistance, 0);
698 } else if (fixed_panel == FixedPanel.Panel1) {
699 panel1.InternalWidth = this.SplitterDistance;
700 panel2.InternalWidth = Math.Max (this.Width - (this.SplitterWidth + this.SplitterDistance), panel2_min_size);
701 panel2.Location = new Point (this.SplitterWidth + this.SplitterDistance, 0);
702 } else if (fixed_panel == FixedPanel.Panel2) {
703 splitter_rectangle.X = Math.Max (this.Width - (this.SplitterWidth + panel2.Width), panel1_min_size); //set distance
704 panel1.InternalWidth = this.SplitterDistance;
705 panel2.Location = new Point (this.SplitterWidth + this.SplitterDistance, 0);
707 } else if (orientation == Orientation.Horizontal) {
708 panel1.Location = new Point (0, 0);
709 panel1.InternalWidth = panel2.InternalWidth = this.Width;
710 splitter_rectangle.Width = this.Width;
712 if (fixed_panel == FixedPanel.None) {
713 splitter_rectangle.Y = Math.Max ((int) Math.Floor ((double)this.Height / fixed_none_ratio), panel1_min_size); //set distance
714 panel1.InternalHeight = this.SplitterDistance;
715 panel2.InternalHeight = this.Height - (this.SplitterWidth + this.SplitterDistance);
716 panel2.Location = new Point (0, this.SplitterWidth + this.SplitterDistance);
717 } else if (fixed_panel == FixedPanel.Panel1) {
718 panel1.InternalHeight = this.SplitterDistance;
719 panel2.InternalHeight = Math.Max (this.Height - (this.SplitterWidth + this.SplitterDistance), panel2_min_size);
720 panel2.Location = new Point (0, this.SplitterWidth + this.SplitterDistance);
721 } else if (fixed_panel == FixedPanel.Panel2) {
722 splitter_rectangle.Y = Math.Max (this.Height - (this.SplitterWidth + panel2.Height), panel1_min_size); //set distance
723 panel1.InternalHeight = this.SplitterDistance;
724 panel2.Location = new Point (0, this.SplitterWidth + this.SplitterDistance);
729 panel1.ResumeLayout ();
730 panel2.ResumeLayout ();
735 #region Internal Classes
736 internal class SplitContainerTypedControlCollection : ControlCollection
738 public SplitContainerTypedControlCollection (Control owner) : base (owner)