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) 2005-2008 Novell, Inc. (http://www.novell.com)
23 // Peter Dennis Bartok (pbartok@novell.com)
24 // Ivan N. Zlatev (contact i-nz.net)
33 using System.ComponentModel;
35 using System.Reflection;
36 using System.Runtime.InteropServices;
38 namespace System.Windows.Forms {
41 [ClassInterface (ClassInterfaceType.AutoDispatch)]
43 [DefaultEvent("SplitterMoved")]
44 [Designer("System.Windows.Forms.Design.SplitterDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
45 [DefaultProperty("Dock")]
46 public class Splitter : Control
51 #region Local Variables
52 static private Cursor splitter_ns;
53 static private Cursor splitter_we;
54 // XXX this "new" shouldn't be here. Control shouldn't define border_style as internal.
55 new private BorderStyle border_style;
56 private int min_extra;
59 private int splitter_size; // Size (width or height) of our splitter control
60 private bool horizontal; // true if we've got a horizontal splitter
61 private Control affected; // The control that the splitter resizes
62 private int split_requested; // If the user requests a position before we have ever laid out the doc
63 private int splitter_prev_move;
64 private Rectangle splitter_rectangle_moving;
65 private int moving_offset;
66 #endregion // Local Variables
70 splitter_ns = Cursors.HSplit;
71 splitter_we = Cursors.VSplit;
82 SetStyle(ControlStyles.Selectable, false);
83 Anchor = AnchorStyles.None;
84 Dock = DockStyle.Left;
86 Layout += new LayoutEventHandler(LayoutSplitter);
87 this.ParentChanged += new EventHandler(ReparentSplitter);
90 #endregion // Constructors
92 #region Public Instance Properties
94 [EditorBrowsable(EditorBrowsableState.Never)]
95 public override bool AllowDrop {
97 return base.AllowDrop;
101 base.AllowDrop = value;
106 [DefaultValue(AnchorStyles.None)]
107 [EditorBrowsable(EditorBrowsableState.Never)]
108 public override AnchorStyles Anchor {
110 return AnchorStyles.None;
114 ; // MS doesn't set it
119 [EditorBrowsable(EditorBrowsableState.Never)]
120 public override Image BackgroundImage {
122 return base.BackgroundImage;
126 base.BackgroundImage = value;
132 [EditorBrowsable (EditorBrowsableState.Never)]
133 public override ImageLayout BackgroundImageLayout {
134 get { return base.BackgroundImageLayout; }
135 set { base.BackgroundImageLayout = value; }
140 [DefaultValue (BorderStyle.None)]
141 [MWFDescription("Sets the border style for the splitter")]
142 [MWFCategory("Appearance")]
143 public BorderStyle BorderStyle {
149 border_style = value;
152 case BorderStyle.FixedSingle:
153 splitter_size = 4; // We don't get motion events for 1px wide windows on X11. sigh.
156 case BorderStyle.Fixed3D:
157 value = BorderStyle.None;
161 case BorderStyle.None:
166 throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
169 base.InternalBorderStyle = value;
173 [DefaultValue(DockStyle.Left)]
175 public override DockStyle Dock {
181 if (!Enum.IsDefined (typeof (DockStyle), value) || (value == DockStyle.None) || (value == DockStyle.Fill)) {
182 throw new ArgumentException("Splitter must be docked left, top, bottom or right");
185 if ((value == DockStyle.Top) || (value == DockStyle.Bottom)) {
187 Cursor = splitter_ns;
190 Cursor = splitter_we;
197 [EditorBrowsable(EditorBrowsableState.Never)]
198 public override Font Font {
209 [EditorBrowsable(EditorBrowsableState.Never)]
210 public override Color ForeColor {
212 return base.ForeColor;
216 base.ForeColor = value;
221 [EditorBrowsable(EditorBrowsableState.Never)]
222 public new ImeMode ImeMode {
228 base.ImeMode = value;
234 [MWFDescription("Sets minimum size of undocked window")]
235 [MWFCategory("Behaviour")]
236 public int MinExtra {
248 [MWFDescription("Sets minimum size of the resized control")]
249 [MWFCategory("Behaviour")]
260 internal int MaxSize {
262 if (this.Parent == null)
265 if (affected == null)
266 affected = AffectedControl;
271 int horiz_offset = 0;
272 foreach (Control c in this.Parent.Controls) {
276 case DockStyle.Right:
279 if (c.Location.X < this.Location.X)
280 vert_offset += c.Width;
283 case DockStyle.Bottom:
286 if (c.Location.Y < this.Location.Y)
287 horiz_offset += c.Height;
294 moving_offset = horiz_offset;
296 return Parent.ClientSize.Height - heights - MinExtra;
298 moving_offset = vert_offset;
300 return Parent.ClientSize.Width - widths - MinExtra;
306 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
307 [MWFDescription("Current splitter position")]
308 [MWFCategory("Layout")]
309 public int SplitPosition {
311 affected = AffectedControl;
312 if (affected == null) {
317 return CalculateSplitPosition();
321 return affected.Height;
323 return affected.Width;
333 affected = AffectedControl;
334 if (affected == null)
335 split_requested = value;
338 affected.Height = value;
340 affected.Width = value;
341 OnSplitterMoved (new SplitterEventArgs (Left, Top, value, value));
347 [EditorBrowsable(EditorBrowsableState.Never)]
348 public new bool TabStop {
349 get { return base.TabStop; }
350 set { base.TabStop = value; }
355 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
356 [EditorBrowsable(EditorBrowsableState.Never)]
357 public override string Text {
367 #endregion // Public Instance Properties
369 #region Protected Instance Properties
370 protected override CreateParams CreateParams {
372 return base.CreateParams;
377 protected override Cursor DefaultCursor {
378 get { return base.DefaultCursor; }
382 protected override ImeMode DefaultImeMode {
384 return ImeMode.Disable;
388 protected override Size DefaultSize {
390 return new Size (3, 3);
393 #endregion // Protected Instance Properties
395 #region Public Instance Methods
397 public bool PreFilterMessage(ref Message m) {
402 public override string ToString() {
403 return base.ToString () + String.Format(", MinExtra: {0}, MinSize: {1}", min_extra, min_size);
405 #endregion // Public Instance Methods
407 #region Protected Instance Methods
408 protected override void OnKeyDown(KeyEventArgs e) {
410 if (Capture && (e.KeyCode == Keys.Escape)) {
412 SplitterEndMove (Point.Empty, true);
416 protected override void OnMouseDown(MouseEventArgs e) {
417 base.OnMouseDown (e);
419 // Only allow if we are set up properly
420 if (affected == null)
421 affected = AffectedControl;
424 if (affected == null || e.Button != MouseButtons.Left)
428 SplitterBeginMove (Parent.PointToClient (PointToScreen (new Point (e.X, e.Y))));
431 protected override void OnMouseMove(MouseEventArgs e) {
432 base.OnMouseMove (e);
434 if (!Capture || e.Button != MouseButtons.Left || affected == null)
437 // We need our mouse coordinates relative to our parent
438 SplitterMove (Parent.PointToClient (PointToScreen (new Point (e.X, e.Y))));
441 protected override void OnMouseUp(MouseEventArgs e) {
442 if (!Capture || e.Button != MouseButtons.Left || affected == null) {
449 SplitterEndMove (Parent.PointToClient (PointToScreen (new Point (e.X, e.Y))), false);
452 private void SplitterBeginMove (Point location)
454 splitter_rectangle_moving = new Rectangle (Bounds.X, Bounds.Y,
456 splitter_prev_move = horizontal ? location.Y : location.X;
459 private void SplitterMove (Point location)
461 int currentMove = horizontal ? location.Y : location.X;
462 int delta = currentMove - splitter_prev_move;
463 Rectangle prev_location = splitter_rectangle_moving;
465 int min = this.MinSize + moving_offset;
466 int max = max_size + moving_offset;
469 if (splitter_rectangle_moving.Y + delta > min && splitter_rectangle_moving.Y + delta < max) {
470 splitter_rectangle_moving.Y += delta;
473 // Ensure that the splitter is set to minimum or maximum position,
474 // even if the mouse "skips".
476 if (splitter_rectangle_moving.Y + delta <= min && splitter_rectangle_moving.Y != min) {
477 splitter_rectangle_moving.Y = min;
479 } else if (splitter_rectangle_moving.Y + delta >= max && splitter_rectangle_moving.Y != max) {
480 splitter_rectangle_moving.Y = max;
485 if (splitter_rectangle_moving.X + delta > min && splitter_rectangle_moving.X + delta < max) {
486 splitter_rectangle_moving.X += delta;
489 // Ensure that the splitter is set to minimum or maximum position,
490 // even if the mouse "skips".
492 if (splitter_rectangle_moving.X + delta <= min && splitter_rectangle_moving.X != min) {
493 splitter_rectangle_moving.X = min;
495 } else if (splitter_rectangle_moving.X + delta >= max && splitter_rectangle_moving.X != max) {
496 splitter_rectangle_moving.X = max;
503 splitter_prev_move = currentMove;
504 OnSplitterMoving (new SplitterEventArgs (location.X, location.Y,
505 splitter_rectangle_moving.X,
506 splitter_rectangle_moving.Y));
507 XplatUI.DrawReversibleRectangle (this.Parent.Handle, prev_location, 1);
508 XplatUI.DrawReversibleRectangle (this.Parent.Handle, splitter_rectangle_moving, 1);
512 private void SplitterEndMove (Point location, bool cancel)
515 // Resize the affected window
517 affected.Height = CalculateSplitPosition();
519 affected.Width = CalculateSplitPosition();
522 this.Parent.Refresh (); // to clean up the drag handle artifacts from all controls
523 SplitterEventArgs args = new SplitterEventArgs (location.X, location.Y,
524 splitter_rectangle_moving.X,
525 splitter_rectangle_moving.Y);
526 OnSplitterMoved (args);
529 protected virtual void OnSplitterMoved(SplitterEventArgs sevent) {
530 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovedEvent]);
535 protected virtual void OnSplitterMoving(SplitterEventArgs sevent) {
536 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovingEvent]);
541 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
542 // enforce our width / height
544 splitter_size = height;
545 if (splitter_size < 1) {
548 base.SetBoundsCore (x, y, width, splitter_size, specified);
550 splitter_size = width;
551 if (splitter_size < 1) {
554 base.SetBoundsCore (x, y, splitter_size, height, specified);
557 #endregion // Protected Instance Methods
559 #region Private Properties and Methods
560 private Control AffectedControl {
565 // Doc says the first control preceeding us in the zorder
566 for (int i = Parent.Controls.GetChildIndex(this) + 1; i < Parent.Controls.Count; i++) {
569 if (Top == Parent.Controls[i].Bottom)
570 return Parent.Controls[i];
572 case DockStyle.Bottom:
573 if (Bottom == Parent.Controls[i].Top)
574 return Parent.Controls[i];
577 if (Left == Parent.Controls[i].Right)
578 return Parent.Controls[i];
580 case DockStyle.Right:
581 if (Right == Parent.Controls[i].Left)
582 return Parent.Controls[i];
590 private int CalculateSplitPosition() {
592 if (Dock == DockStyle.Top)
593 return splitter_rectangle_moving.Y - affected.Top;
595 return affected.Bottom - splitter_rectangle_moving.Y - splitter_size;
597 if (Dock == DockStyle.Left)
598 return splitter_rectangle_moving.X - affected.Left;
600 return affected.Right - splitter_rectangle_moving.X - splitter_size;
604 internal override void OnPaintInternal (PaintEventArgs e) {
605 e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(this.BackColor), e.ClipRectangle);
608 private void LayoutSplitter(object sender, LayoutEventArgs e) {
609 affected = AffectedControl;
610 if (split_requested != -1) {
611 SplitPosition = split_requested;
612 split_requested = -1;
616 private void ReparentSplitter(object sender, EventArgs e) {
620 #endregion // Private Properties and Methods
624 [EditorBrowsable(EditorBrowsableState.Never)]
625 public new event EventHandler BackgroundImageChanged {
626 add { base.BackgroundImageChanged += value; }
627 remove { base.BackgroundImageChanged -= value; }
632 [EditorBrowsable (EditorBrowsableState.Never)]
633 public new event EventHandler BackgroundImageLayoutChanged
635 add { base.BackgroundImageLayoutChanged += value; }
636 remove { base.BackgroundImageLayoutChanged -= value; }
642 [EditorBrowsable(EditorBrowsableState.Never)]
643 public new event EventHandler Enter {
644 add { base.Enter += value; }
645 remove { base.Enter -= value; }
649 [EditorBrowsable(EditorBrowsableState.Never)]
650 public new event EventHandler FontChanged {
651 add { base.FontChanged += value; }
652 remove { base.FontChanged -= value; }
656 [EditorBrowsable(EditorBrowsableState.Never)]
657 public new event EventHandler ForeColorChanged {
658 add { base.ForeColorChanged += value; }
659 remove { base.ForeColorChanged -= value; }
663 [EditorBrowsable(EditorBrowsableState.Never)]
664 public new event EventHandler ImeModeChanged {
665 add { base.ImeModeChanged += value; }
666 remove { base.ImeModeChanged -= value; }
670 [EditorBrowsable(EditorBrowsableState.Never)]
671 public new event KeyEventHandler KeyDown {
672 add { base.KeyDown += value; }
673 remove { base.KeyDown -= value; }
677 [EditorBrowsable(EditorBrowsableState.Never)]
678 public new event KeyPressEventHandler KeyPress {
679 add { base.KeyPress += value; }
680 remove { base.KeyPress -= value; }
684 [EditorBrowsable(EditorBrowsableState.Never)]
685 public new event KeyEventHandler KeyUp {
686 add { base.KeyUp += value; }
687 remove { base.KeyUp -= value; }
691 [EditorBrowsable(EditorBrowsableState.Never)]
692 public new event EventHandler Leave {
693 add { base.Leave += value; }
694 remove { base.Leave -= value; }
698 [EditorBrowsable(EditorBrowsableState.Never)]
699 public new event EventHandler TabStopChanged {
700 add { base.TabStopChanged += value; }
701 remove { base.TabStopChanged -= value; }
705 [EditorBrowsable(EditorBrowsableState.Never)]
706 public new event EventHandler TextChanged {
707 add { base.TextChanged += value; }
708 remove { base.TextChanged -= value; }
711 static object SplitterMovedEvent = new object ();
712 static object SplitterMovingEvent = new object ();
714 public event SplitterEventHandler SplitterMoved {
715 add { Events.AddHandler (SplitterMovedEvent, value); }
716 remove { Events.RemoveHandler (SplitterMovedEvent, value); }
719 public event SplitterEventHandler SplitterMoving {
720 add { Events.AddHandler (SplitterMovingEvent, value); }
721 remove { Events.RemoveHandler (SplitterMovingEvent, value); }