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
{
40 [ClassInterface (ClassInterfaceType
.AutoDispatch
)]
41 [DefaultEvent("SplitterMoved")]
42 [Designer("System.Windows.Forms.Design.SplitterDesigner, " + Consts
.AssemblySystem_Design
, "System.ComponentModel.Design.IDesigner")]
43 [DefaultProperty("Dock")]
44 public class Splitter
: Control
46 #region Local Variables
47 static private Cursor splitter_ns
;
48 static private Cursor splitter_we
;
49 // XXX this "new" shouldn't be here. Control shouldn't define border_style as internal.
50 new private BorderStyle border_style
;
51 private int min_extra
;
54 private int splitter_size
; // Size (width or height) of our splitter control
55 private bool horizontal
; // true if we've got a horizontal splitter
56 private Control affected
; // The control that the splitter resizes
57 private int split_requested
; // If the user requests a position before we have ever laid out the doc
58 private int splitter_prev_move
;
59 private Rectangle splitter_rectangle_moving
;
60 private int moving_offset
;
61 #endregion // Local Variables
65 splitter_ns
= Cursors
.HSplit
;
66 splitter_we
= Cursors
.VSplit
;
77 SetStyle(ControlStyles
.Selectable
, false);
78 Anchor
= AnchorStyles
.None
;
79 Dock
= DockStyle
.Left
;
81 Layout
+= new LayoutEventHandler(LayoutSplitter
);
82 this.ParentChanged
+= new EventHandler(ReparentSplitter
);
85 #endregion // Constructors
87 #region Public Instance Properties
89 [EditorBrowsable(EditorBrowsableState
.Never
)]
90 public override bool AllowDrop
{
92 return base.AllowDrop
;
96 base.AllowDrop
= value;
101 [DefaultValue(AnchorStyles
.None
)]
102 [EditorBrowsable(EditorBrowsableState
.Never
)]
103 public override AnchorStyles Anchor
{
105 return AnchorStyles
.None
;
109 ; // MS doesn't set it
114 [EditorBrowsable(EditorBrowsableState
.Never
)]
115 public override Image BackgroundImage
{
117 return base.BackgroundImage
;
121 base.BackgroundImage
= value;
126 [EditorBrowsable (EditorBrowsableState
.Never
)]
127 public override ImageLayout BackgroundImageLayout
{
128 get { return base.BackgroundImageLayout; }
129 set { base.BackgroundImageLayout = value; }
133 [DefaultValue (BorderStyle
.None
)]
134 [MWFDescription("Sets the border style for the splitter")]
135 [MWFCategory("Appearance")]
136 public BorderStyle BorderStyle
{
142 border_style
= value;
145 case BorderStyle
.FixedSingle
:
146 splitter_size
= 4; // We don't get motion events for 1px wide windows on X11. sigh.
149 case BorderStyle
.Fixed3D
:
150 value = BorderStyle
.None
;
154 case BorderStyle
.None
:
159 throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
162 base.InternalBorderStyle
= value;
166 [DefaultValue(DockStyle
.Left
)]
168 public override DockStyle Dock
{
174 if (!Enum
.IsDefined (typeof (DockStyle
), value) || (value == DockStyle
.None
) || (value == DockStyle
.Fill
)) {
175 throw new ArgumentException("Splitter must be docked left, top, bottom or right");
178 if ((value == DockStyle
.Top
) || (value == DockStyle
.Bottom
)) {
180 Cursor
= splitter_ns
;
183 Cursor
= splitter_we
;
190 [EditorBrowsable(EditorBrowsableState
.Never
)]
191 public override Font Font
{
202 [EditorBrowsable(EditorBrowsableState
.Never
)]
203 public override Color ForeColor
{
205 return base.ForeColor
;
209 base.ForeColor
= value;
214 [EditorBrowsable(EditorBrowsableState
.Never
)]
215 public new ImeMode ImeMode
{
221 base.ImeMode
= value;
227 [MWFDescription("Sets minimum size of undocked window")]
228 [MWFCategory("Behaviour")]
229 public int MinExtra
{
241 [MWFDescription("Sets minimum size of the resized control")]
242 [MWFCategory("Behaviour")]
253 internal int MaxSize
{
255 if (this.Parent
== null)
258 if (affected
== null)
259 affected
= AffectedControl
;
264 int horiz_offset
= 0;
265 foreach (Control c
in this.Parent
.Controls
) {
269 case DockStyle
.Right
:
272 if (c
.Location
.X
< this.Location
.X
)
273 vert_offset
+= c
.Width
;
276 case DockStyle
.Bottom
:
279 if (c
.Location
.Y
< this.Location
.Y
)
280 horiz_offset
+= c
.Height
;
287 moving_offset
= horiz_offset
;
289 return Parent
.ClientSize
.Height
- heights
- MinExtra
;
291 moving_offset
= vert_offset
;
293 return Parent
.ClientSize
.Width
- widths
- MinExtra
;
299 [DesignerSerializationVisibility(DesignerSerializationVisibility
.Hidden
)]
300 [MWFDescription("Current splitter position")]
301 [MWFCategory("Layout")]
302 public int SplitPosition
{
304 affected
= AffectedControl
;
305 if (affected
== null) {
310 return CalculateSplitPosition();
314 return affected
.Height
;
316 return affected
.Width
;
326 affected
= AffectedControl
;
327 if (affected
== null)
328 split_requested
= value;
331 affected
.Height
= value;
333 affected
.Width
= value;
334 OnSplitterMoved (new SplitterEventArgs (Left
, Top
, value, value));
340 [EditorBrowsable(EditorBrowsableState
.Never
)]
341 public new bool TabStop
{
342 get { return base.TabStop; }
343 set { base.TabStop = value; }
348 [DesignerSerializationVisibility(DesignerSerializationVisibility
.Hidden
)]
349 [EditorBrowsable(EditorBrowsableState
.Never
)]
350 public override string Text
{
360 #endregion // Public Instance Properties
362 #region Protected Instance Properties
363 protected override CreateParams CreateParams
{
365 return base.CreateParams
;
369 protected override Cursor DefaultCursor
{
370 get { return base.DefaultCursor; }
373 protected override ImeMode DefaultImeMode
{
375 return ImeMode
.Disable
;
379 protected override Size DefaultSize
{
381 return new Size (3, 3);
384 #endregion // Protected Instance Properties
386 #region Public Instance Methods
387 public override string ToString() {
388 return base.ToString () + String
.Format(", MinExtra: {0}, MinSize: {1}", min_extra
, min_size
);
390 #endregion // Public Instance Methods
392 #region Protected Instance Methods
393 protected override void OnKeyDown(KeyEventArgs e
) {
395 if (Capture
&& (e
.KeyCode
== Keys
.Escape
)) {
397 SplitterEndMove (Point
.Empty
, true);
401 protected override void OnMouseDown(MouseEventArgs e
) {
402 base.OnMouseDown (e
);
404 // Only allow if we are set up properly
405 if (affected
== null)
406 affected
= AffectedControl
;
409 if (affected
== null || e
.Button
!= MouseButtons
.Left
)
413 SplitterBeginMove (Parent
.PointToClient (PointToScreen (new Point (e
.X
, e
.Y
))));
416 protected override void OnMouseMove (MouseEventArgs e
)
418 base.OnMouseMove (e
);
420 if (!Capture
|| e
.Button
!= MouseButtons
.Left
|| affected
== null)
423 // We need our mouse coordinates relative to our parent
424 SplitterMove (Parent
.PointToClient (PointToScreen (e
.Location
)));
427 protected override void OnMouseUp (MouseEventArgs e
)
430 if (!Capture
|| e
.Button
!= MouseButtons
.Left
|| affected
== null)
433 SplitterEndMove (Parent
.PointToClient (PointToScreen (e
.Location
)), false);
437 private void SplitterBeginMove (Point location
)
439 splitter_rectangle_moving
= new Rectangle (Bounds
.X
, Bounds
.Y
,
441 splitter_prev_move
= horizontal
? location
.Y
: location
.X
;
444 private void SplitterMove (Point location
)
446 int currentMove
= horizontal
? location
.Y
: location
.X
;
447 int delta
= currentMove
- splitter_prev_move
;
448 Rectangle prev_location
= splitter_rectangle_moving
;
450 int min
= this.MinSize
+ moving_offset
;
451 int max
= max_size
+ moving_offset
;
454 if (splitter_rectangle_moving
.Y
+ delta
> min
&& splitter_rectangle_moving
.Y
+ delta
< max
) {
455 splitter_rectangle_moving
.Y
+= delta
;
458 // Ensure that the splitter is set to minimum or maximum position,
459 // even if the mouse "skips".
461 if (splitter_rectangle_moving
.Y
+ delta
<= min
&& splitter_rectangle_moving
.Y
!= min
) {
462 splitter_rectangle_moving
.Y
= min
;
464 } else if (splitter_rectangle_moving
.Y
+ delta
>= max
&& splitter_rectangle_moving
.Y
!= max
) {
465 splitter_rectangle_moving
.Y
= max
;
470 if (splitter_rectangle_moving
.X
+ delta
> min
&& splitter_rectangle_moving
.X
+ delta
< max
) {
471 splitter_rectangle_moving
.X
+= delta
;
474 // Ensure that the splitter is set to minimum or maximum position,
475 // even if the mouse "skips".
477 if (splitter_rectangle_moving
.X
+ delta
<= min
&& splitter_rectangle_moving
.X
!= min
) {
478 splitter_rectangle_moving
.X
= min
;
480 } else if (splitter_rectangle_moving
.X
+ delta
>= max
&& splitter_rectangle_moving
.X
!= max
) {
481 splitter_rectangle_moving
.X
= max
;
488 splitter_prev_move
= currentMove
;
489 OnSplitterMoving (new SplitterEventArgs (location
.X
, location
.Y
,
490 splitter_rectangle_moving
.X
,
491 splitter_rectangle_moving
.Y
));
492 XplatUI
.DrawReversibleRectangle (this.Parent
.Handle
, prev_location
, 1);
493 XplatUI
.DrawReversibleRectangle (this.Parent
.Handle
, splitter_rectangle_moving
, 1);
497 private void SplitterEndMove (Point location
, bool cancel
)
500 // Resize the affected window
502 affected
.Height
= CalculateSplitPosition();
504 affected
.Width
= CalculateSplitPosition();
507 this.Parent
.Refresh (); // to clean up the drag handle artifacts from all controls
508 SplitterEventArgs args
= new SplitterEventArgs (location
.X
, location
.Y
,
509 splitter_rectangle_moving
.X
,
510 splitter_rectangle_moving
.Y
);
511 OnSplitterMoved (args
);
514 protected virtual void OnSplitterMoved(SplitterEventArgs sevent
) {
515 SplitterEventHandler eh
= (SplitterEventHandler
)(Events
[SplitterMovedEvent
]);
520 protected virtual void OnSplitterMoving(SplitterEventArgs sevent
) {
521 SplitterEventHandler eh
= (SplitterEventHandler
)(Events
[SplitterMovingEvent
]);
526 protected override void SetBoundsCore(int x
, int y
, int width
, int height
, BoundsSpecified specified
) {
527 // enforce our width / height
529 splitter_size
= height
;
530 if (splitter_size
< 1) {
533 base.SetBoundsCore (x
, y
, width
, splitter_size
, specified
);
535 splitter_size
= width
;
536 if (splitter_size
< 1) {
539 base.SetBoundsCore (x
, y
, splitter_size
, height
, specified
);
542 #endregion // Protected Instance Methods
544 #region Private Properties and Methods
545 private Control AffectedControl
{
550 // Doc says the first control preceeding us in the zorder
551 for (int i
= Parent
.Controls
.GetChildIndex(this) + 1; i
< Parent
.Controls
.Count
; i
++) {
554 if (Top
== Parent
.Controls
[i
].Bottom
)
555 return Parent
.Controls
[i
];
557 case DockStyle
.Bottom
:
558 if (Bottom
== Parent
.Controls
[i
].Top
)
559 return Parent
.Controls
[i
];
562 if (Left
== Parent
.Controls
[i
].Right
)
563 return Parent
.Controls
[i
];
565 case DockStyle
.Right
:
566 if (Right
== Parent
.Controls
[i
].Left
)
567 return Parent
.Controls
[i
];
575 private int CalculateSplitPosition() {
577 if (Dock
== DockStyle
.Top
)
578 return splitter_rectangle_moving
.Y
- affected
.Top
;
580 return affected
.Bottom
- splitter_rectangle_moving
.Y
- splitter_size
;
582 if (Dock
== DockStyle
.Left
)
583 return splitter_rectangle_moving
.X
- affected
.Left
;
585 return affected
.Right
- splitter_rectangle_moving
.X
- splitter_size
;
589 internal override void OnPaintInternal (PaintEventArgs e
) {
590 e
.Graphics
.FillRectangle(ThemeEngine
.Current
.ResPool
.GetSolidBrush(this.BackColor
), e
.ClipRectangle
);
593 private void LayoutSplitter(object sender
, LayoutEventArgs e
) {
594 affected
= AffectedControl
;
595 if (split_requested
!= -1) {
596 SplitPosition
= split_requested
;
597 split_requested
= -1;
601 private void ReparentSplitter(object sender
, EventArgs e
) {
605 #endregion // Private Properties and Methods
609 [EditorBrowsable(EditorBrowsableState
.Never
)]
610 public new event EventHandler BackgroundImageChanged
{
611 add { base.BackgroundImageChanged += value; }
612 remove { base.BackgroundImageChanged -= value; }
616 [EditorBrowsable (EditorBrowsableState
.Never
)]
617 public new event EventHandler BackgroundImageLayoutChanged
619 add { base.BackgroundImageLayoutChanged += value; }
620 remove { base.BackgroundImageLayoutChanged -= value; }
624 [EditorBrowsable(EditorBrowsableState
.Never
)]
625 public new event EventHandler Enter
{
626 add { base.Enter += value; }
627 remove { base.Enter -= value; }
631 [EditorBrowsable(EditorBrowsableState
.Never
)]
632 public new event EventHandler FontChanged
{
633 add { base.FontChanged += value; }
634 remove { base.FontChanged -= value; }
638 [EditorBrowsable(EditorBrowsableState
.Never
)]
639 public new event EventHandler ForeColorChanged
{
640 add { base.ForeColorChanged += value; }
641 remove { base.ForeColorChanged -= value; }
645 [EditorBrowsable(EditorBrowsableState
.Never
)]
646 public new event EventHandler ImeModeChanged
{
647 add { base.ImeModeChanged += value; }
648 remove { base.ImeModeChanged -= value; }
652 [EditorBrowsable(EditorBrowsableState
.Never
)]
653 public new event KeyEventHandler KeyDown
{
654 add { base.KeyDown += value; }
655 remove { base.KeyDown -= value; }
659 [EditorBrowsable(EditorBrowsableState
.Never
)]
660 public new event KeyPressEventHandler KeyPress
{
661 add { base.KeyPress += value; }
662 remove { base.KeyPress -= value; }
666 [EditorBrowsable(EditorBrowsableState
.Never
)]
667 public new event KeyEventHandler KeyUp
{
668 add { base.KeyUp += value; }
669 remove { base.KeyUp -= value; }
673 [EditorBrowsable(EditorBrowsableState
.Never
)]
674 public new event EventHandler Leave
{
675 add { base.Leave += value; }
676 remove { base.Leave -= value; }
680 [EditorBrowsable(EditorBrowsableState
.Never
)]
681 public new event EventHandler TabStopChanged
{
682 add { base.TabStopChanged += value; }
683 remove { base.TabStopChanged -= value; }
687 [EditorBrowsable(EditorBrowsableState
.Never
)]
688 public new event EventHandler TextChanged
{
689 add { base.TextChanged += value; }
690 remove { base.TextChanged -= value; }
693 static object SplitterMovedEvent
= new object ();
694 static object SplitterMovingEvent
= new object ();
696 public event SplitterEventHandler SplitterMoved
{
697 add { Events.AddHandler (SplitterMovedEvent, value); }
698 remove { Events.RemoveHandler (SplitterMovedEvent, value); }
701 public event SplitterEventHandler SplitterMoving
{
702 add { Events.AddHandler (SplitterMovingEvent, value); }
703 remove { Events.RemoveHandler (SplitterMovingEvent, value); }