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-2006 Novell, Inc. (http://www.novell.com)
23 // Peter Dennis Bartok (pbartok@novell.com)
32 using System
.ComponentModel
;
34 using System
.Reflection
;
35 using System
.Runtime
.InteropServices
;
37 namespace System
.Windows
.Forms
{
38 [DefaultEvent("SplitterMoved")]
39 [Designer("System.Windows.Forms.Design.SplitterDesigner, " + Consts
.AssemblySystem_Design
, "System.ComponentModel.Design.IDesigner")]
40 [DefaultProperty("Dock")]
41 public class Splitter
: Control
47 private enum DrawType
{
54 #region Local Variables
55 static private Cursor splitter_ns
;
56 static private Cursor splitter_we
;
57 // XXX this "new" shouldn't be here. Control shouldn't define border_style as internal.
58 new private BorderStyle border_style
;
59 private int min_extra
;
61 private int split_position
; // Current splitter position
62 private int prev_split_position
; // Previous splitter position, only valid during drag
63 private int click_offset
; // Click offset from border of splitter control
64 private int splitter_size
; // Size (width or height) of our splitter control
65 private bool horizontal
; // true if we've got a horizontal splitter
66 private Control affected
; // The control that the splitter resizes
67 private Control filler
; // The control that MinExtra prevents from being shrunk to 0 size
68 private SplitterEventArgs sevent
; // We cache the object, prevents fragmentation
69 private int limit_min
; // The max we're allowed to move the splitter left/up
70 private int limit_max
; // The max we're allowed to move the splitter right/down
71 private int split_requested
; // If the user requests a position before we have ever laid out the doc
72 #endregion // Local Variables
76 splitter_ns
= Cursors
.HSplit
;
77 splitter_we
= Cursors
.VSplit
;
88 sevent
= new SplitterEventArgs(0, 0, 0, 0);
90 SetStyle(ControlStyles
.Selectable
, false);
91 Anchor
= AnchorStyles
.None
;
92 Dock
= DockStyle
.Left
;
94 Layout
+= new LayoutEventHandler(LayoutSplitter
);
95 this.ParentChanged
+= new EventHandler(ReparentSplitter
);
98 #endregion // Constructors
100 #region Public Instance Properties
102 [EditorBrowsable(EditorBrowsableState
.Never
)]
103 public override bool AllowDrop
{
105 return base.AllowDrop
;
109 base.AllowDrop
= value;
114 [DefaultValue(AnchorStyles
.None
)]
115 [EditorBrowsable(EditorBrowsableState
.Never
)]
116 public override AnchorStyles Anchor
{
118 return AnchorStyles
.None
;
122 ; // MS doesn't set it
127 [EditorBrowsable(EditorBrowsableState
.Never
)]
128 public override Image BackgroundImage
{
130 return base.BackgroundImage
;
134 base.BackgroundImage
= value;
139 [DefaultValue (BorderStyle
.None
)]
140 [MWFDescription("Sets the border style for the splitter")]
141 [MWFCategory("Appearance")]
142 public BorderStyle BorderStyle
{
148 border_style
= value;
151 case BorderStyle
.FixedSingle
:
152 splitter_size
= 4; // We don't get motion events for 1px wide windows on X11. sigh.
155 case BorderStyle
.Fixed3D
:
156 value = BorderStyle
.None
;
160 case BorderStyle
.None
:
165 throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
168 base.InternalBorderStyle
= value;
172 [DefaultValue(DockStyle
.Left
)]
174 public override DockStyle Dock
{
180 if (!Enum
.IsDefined (typeof (DockStyle
), value) || (value == DockStyle
.None
) || (value == DockStyle
.Fill
)) {
181 throw new ArgumentException("Splitter must be docked left, top, bottom or right");
184 if ((value == DockStyle
.Top
) || (value == DockStyle
.Bottom
)) {
186 Cursor
= splitter_ns
;
189 Cursor
= splitter_we
;
196 [EditorBrowsable(EditorBrowsableState
.Never
)]
197 public override Font Font
{
208 [EditorBrowsable(EditorBrowsableState
.Never
)]
209 public override Color ForeColor
{
211 return base.ForeColor
;
215 base.ForeColor
= value;
220 [EditorBrowsable(EditorBrowsableState
.Never
)]
221 public new ImeMode ImeMode
{
227 base.ImeMode
= value;
233 [MWFDescription("Sets minimum size of undocked window")]
234 [MWFCategory("Behaviour")]
235 public int MinExtra
{
247 [MWFDescription("Sets minimum size of the resized control")]
248 [MWFCategory("Behaviour")]
261 [DesignerSerializationVisibility(DesignerSerializationVisibility
.Hidden
)]
262 [MWFDescription("Current splitter position")]
263 [MWFCategory("Layout")]
264 public int SplitPosition
{
266 affected
= AffectedControl
;
267 if (affected
== null) {
272 return CalculateSplitPosition();
276 return affected
.Height
;
278 return affected
.Width
;
283 affected
= AffectedControl
;
285 if (affected
== null) {
286 split_requested
= value;
290 affected
.Height
= value;
292 affected
.Width
= value;
299 [EditorBrowsable(EditorBrowsableState
.Never
)]
300 public new bool TabStop
{
301 get { return base.TabStop; }
302 set { base.TabStop = value; }
307 [DesignerSerializationVisibility(DesignerSerializationVisibility
.Hidden
)]
308 [EditorBrowsable(EditorBrowsableState
.Never
)]
309 public override string Text
{
319 #endregion // Public Instance Properties
321 #region Protected Instance Properties
322 protected override CreateParams CreateParams
{
324 return base.CreateParams
;
328 protected override ImeMode DefaultImeMode
{
330 return ImeMode
.Disable
;
334 protected override Size DefaultSize
{
336 return new Size (3, 3);
339 #endregion // Protected Instance Properties
341 #region Public Instance Methods
343 public bool PreFilterMessage(ref Message m
) {
348 public override string ToString() {
349 return base.ToString () + String
.Format(", MinExtra: {0}, MinSize: {1}", min_extra
, min_size
);
351 #endregion // Public Instance Methods
353 #region Protected Instance Methods
354 protected override void OnKeyDown(KeyEventArgs e
) {
356 if (Capture
&& (e
.KeyCode
== Keys
.Escape
)) {
358 DrawDragHandle(DrawType
.Finish
);
362 protected override void OnMouseDown(MouseEventArgs e
) {
365 base.OnMouseDown (e
);
367 // Only allow if we are set up properly
368 if ((affected
== null) || (filler
== null)) {
369 affected
= AffectedControl
;
370 filler
= FillerControl
;
373 if (affected
== null || e
.Button
!= MouseButtons
.Left
) {
381 if (filler
!= null) {
383 if (Dock
== DockStyle
.Top
) {
384 limit_min
= affected
.Bounds
.Top
+ min_size
;
385 limit_max
= filler
.Bounds
.Bottom
- min_extra
+ this.bounds
.Top
- filler
.Bounds
.Top
;
387 limit_min
= filler
.Bounds
.Top
+ min_extra
+ this.bounds
.Top
- filler
.Bounds
.Bottom
;
388 limit_max
= affected
.Bounds
.Bottom
- min_size
- this.Height
;
391 if (Dock
== DockStyle
.Left
) {
392 limit_min
= affected
.Bounds
.Left
+ min_size
;
393 limit_max
= filler
.Bounds
.Right
- min_extra
+ this.bounds
.Left
- filler
.Bounds
.Left
;
395 limit_min
= filler
.Bounds
.Left
+ min_extra
+ this.bounds
.Left
- filler
.Bounds
.Right
;
396 limit_max
= affected
.Bounds
.Right
- min_size
- this.Width
;
402 limit_max
= affected
.Parent
.Height
;
404 limit_max
= affected
.Parent
.Width
;
409 Console
.WriteLine("Sizing limits: Min:{0}, Max:{1}", limit_min
, limit_max
);
412 pt
= PointToScreen(Parent
.PointToClient(new Point(e
.X
, e
.Y
)));
415 split_position
= pt
.Y
;
416 if (Dock
== DockStyle
.Top
) {
422 split_position
= pt
.X
;
423 if (Dock
== DockStyle
.Left
) {
430 // We need to set this, in case we never get a mouse move
431 prev_split_position
= split_position
;
434 Console
.WriteLine("Click-offset: {0} MouseDown split position: {1}", click_offset
, split_position
);
437 // Draw our initial handle
438 DrawDragHandle(DrawType
.Initial
);
441 protected override void OnMouseMove(MouseEventArgs e
) {
444 base.OnMouseMove (e
);
446 if (!Capture
|| e
.Button
!= MouseButtons
.Left
|| affected
== null) {
450 // We need our mouse coordinates relative to our parent
451 pt
= PointToScreen(Parent
.PointToClient(new Point(e
.X
, e
.Y
)));
453 // Grab our new coordinates
454 prev_split_position
= split_position
;
456 int candidate
= horizontal
? pt
.Y
: pt
.X
;
458 // Enforce limit on what we send to the event
459 if (candidate
< limit_min
)
460 candidate
= limit_min
;
461 else if (candidate
> limit_max
)
462 candidate
= limit_max
;
466 sevent
.split_x
= horizontal
? 0 : candidate
;
467 sevent
.split_y
= horizontal
? candidate
: 0;
470 OnSplitterMoving(sevent
);
472 split_position
= horizontal
? sevent
.split_y
: sevent
.split_x
;
475 if (split_position
< limit_min
) {
477 Console
.WriteLine("SplitPosition {0} less than minimum {1}, setting to minimum", split_position
, limit_min
);
479 split_position
= limit_min
;
480 } else if (split_position
> limit_max
) {
482 Console
.WriteLine("SplitPosition {0} more than maximum {1}, setting to maximum", split_position
, limit_max
);
484 split_position
= limit_max
;
487 // Update our handle location
488 DrawDragHandle(DrawType
.Redraw
);
491 protected override void OnMouseUp(MouseEventArgs e
) {
492 if (!Capture
|| e
.Button
!= MouseButtons
.Left
|| affected
== null) {
498 DrawDragHandle(DrawType
.Finish
);
500 // Resize the affected window
502 affected
.Height
= CalculateSplitPosition() - click_offset
;
504 Console
.WriteLine("Setting height of affected control to {0}", CalculateSplitPosition() - click_offset
);
507 affected
.Width
= CalculateSplitPosition() - click_offset
;
509 Console
.WriteLine("Setting width of affected control to {0}", CalculateSplitPosition() - click_offset
);
515 // It seems that MS is sending some data that doesn't quite make sense
516 // In this event. It tried to match their stuff.., not sure about split_x...
521 sevent
.y
= split_position
;
522 sevent
.split_x
= 200;
523 sevent
.split_y
= split_position
;
525 sevent
.x
= split_position
;
527 sevent
.split_x
= split_position
;
528 sevent
.split_y
= 200;
533 OnSplitterMoved(sevent
);
536 protected virtual void OnSplitterMoved(SplitterEventArgs sevent
) {
537 SplitterEventHandler eh
= (SplitterEventHandler
)(Events
[SplitterMovedEvent
]);
542 protected virtual void OnSplitterMoving(SplitterEventArgs sevent
) {
543 SplitterEventHandler eh
= (SplitterEventHandler
)(Events
[SplitterMovingEvent
]);
548 protected override void SetBoundsCore(int x
, int y
, int width
, int height
, BoundsSpecified specified
) {
549 // enforce our width / height
551 splitter_size
= height
;
552 if (splitter_size
< 1) {
555 base.SetBoundsCore (x
, y
, width
, splitter_size
, specified
);
557 splitter_size
= width
;
558 if (splitter_size
< 1) {
561 base.SetBoundsCore (x
, y
, splitter_size
, height
, specified
);
564 #endregion // Protected Instance Methods
566 #region Private Properties and Methods
567 private Control AffectedControl
{
572 // Doc says the first control preceeding us in the zorder
573 for (int i
= Parent
.Controls
.GetChildIndex(this) + 1; i
< Parent
.Controls
.Count
; i
++) {
576 if (Top
== Parent
.Controls
[i
].Bottom
)
577 return Parent
.Controls
[i
];
579 case DockStyle
.Bottom
:
580 if (Bottom
== Parent
.Controls
[i
].Top
)
581 return Parent
.Controls
[i
];
584 if (Left
== Parent
.Controls
[i
].Right
)
585 return Parent
.Controls
[i
];
587 case DockStyle
.Right
:
588 if (Right
== Parent
.Controls
[i
].Left
)
589 return Parent
.Controls
[i
];
597 private Control FillerControl
{
602 // Doc says the first control preceeding us in the zorder
603 for (int i
= Parent
.Controls
.GetChildIndex(this) - 1; i
>= 0; i
--) {
604 if (Parent
.Controls
[i
].Dock
== DockStyle
.Fill
) {
605 return Parent
.Controls
[i
];
612 private int CalculateSplitPosition() {
614 if (Dock
== DockStyle
.Top
)
615 return split_position
- affected
.Top
;
617 return affected
.Bottom
- split_position
- splitter_size
;
619 if (Dock
== DockStyle
.Left
)
620 return split_position
- affected
.Left
;
622 return affected
.Right
- split_position
- splitter_size
;
626 internal override void OnPaintInternal (PaintEventArgs e
) {
627 e
.Graphics
.FillRectangle(ThemeEngine
.Current
.ResPool
.GetSolidBrush(this.BackColor
), e
.ClipRectangle
);
630 private void LayoutSplitter(object sender
, LayoutEventArgs e
) {
631 affected
= AffectedControl
;
632 filler
= FillerControl
;
633 if (split_requested
!= -1) {
634 SplitPosition
= split_requested
;
635 split_requested
= -1;
639 private void ReparentSplitter(object sender
, EventArgs e
) {
644 private void DrawDragHandle(DrawType type
) {
649 prev
= new Rectangle(Location
.X
, prev_split_position
- click_offset
+ 1, Width
, 0);
650 current
= new Rectangle(Location
.X
, split_position
- click_offset
+ 1, Width
, 0);
652 prev
= new Rectangle(prev_split_position
- click_offset
+ 1, Location
.Y
, 0, Height
);
653 current
= new Rectangle(split_position
- click_offset
+ 1, Location
.Y
, 0, Height
);
657 case DrawType
.Initial
:
658 XplatUI
.DrawReversibleRectangle(Parent
.window
.Handle
, current
, 3);
661 case DrawType
.Redraw
:
662 if (prev
.X
== current
.X
&& prev
.Y
== current
.Y
)
665 XplatUI
.DrawReversibleRectangle(Parent
.window
.Handle
, prev
, 3);
666 XplatUI
.DrawReversibleRectangle(Parent
.window
.Handle
, current
, 3);
669 case DrawType
.Finish
:
670 XplatUI
.DrawReversibleRectangle(Parent
.window
.Handle
, current
, 3);
674 #endregion // Private Properties and Methods
678 [EditorBrowsable(EditorBrowsableState
.Never
)]
679 public new event EventHandler BackgroundImageChanged
{
680 add { base.BackgroundImageChanged += value; }
681 remove { base.BackgroundImageChanged -= value; }
685 [EditorBrowsable(EditorBrowsableState
.Never
)]
686 public new event EventHandler Enter
{
687 add { base.Enter += value; }
688 remove { base.Enter -= value; }
692 [EditorBrowsable(EditorBrowsableState
.Never
)]
693 public new event EventHandler FontChanged
{
694 add { base.FontChanged += value; }
695 remove { base.FontChanged -= value; }
699 [EditorBrowsable(EditorBrowsableState
.Never
)]
700 public new event EventHandler ForeColorChanged
{
701 add { base.ForeColorChanged += value; }
702 remove { base.ForeColorChanged -= value; }
706 [EditorBrowsable(EditorBrowsableState
.Never
)]
707 public new event EventHandler ImeModeChanged
{
708 add { base.ImeModeChanged += value; }
709 remove { base.ImeModeChanged -= value; }
713 [EditorBrowsable(EditorBrowsableState
.Never
)]
714 public new event KeyEventHandler KeyDown
{
715 add { base.KeyDown += value; }
716 remove { base.KeyDown -= value; }
720 [EditorBrowsable(EditorBrowsableState
.Never
)]
721 public new event KeyPressEventHandler KeyPress
{
722 add { base.KeyPress += value; }
723 remove { base.KeyPress -= value; }
727 [EditorBrowsable(EditorBrowsableState
.Never
)]
728 public new event KeyEventHandler KeyUp
{
729 add { base.KeyUp += value; }
730 remove { base.KeyUp -= value; }
734 [EditorBrowsable(EditorBrowsableState
.Never
)]
735 public new event EventHandler Leave
{
736 add { base.Leave += value; }
737 remove { base.Leave -= value; }
741 [EditorBrowsable(EditorBrowsableState
.Never
)]
742 public new event EventHandler TabStopChanged
{
743 add { base.TabStopChanged += value; }
744 remove { base.TabStopChanged -= value; }
748 [EditorBrowsable(EditorBrowsableState
.Never
)]
749 public new event EventHandler TextChanged
{
750 add { base.TextChanged += value; }
751 remove { base.TextChanged -= value; }
754 static object SplitterMovedEvent
= new object ();
755 static object SplitterMovingEvent
= new object ();
757 public event SplitterEventHandler SplitterMoved
{
758 add { Events.AddHandler (SplitterMovedEvent, value); }
759 remove { Events.RemoveHandler (SplitterMovedEvent, value); }
762 public event SplitterEventHandler SplitterMoving
{
763 add { Events.AddHandler (SplitterMovingEvent, value); }
764 remove { Events.RemoveHandler (SplitterMovingEvent, value); }