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 {
40 [ClassInterface (ClassInterfaceType.AutoDispatch)]
42 [DefaultEvent("SplitterMoved")]
43 [Designer("System.Windows.Forms.Design.SplitterDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
44 [DefaultProperty("Dock")]
45 public class Splitter : Control
51 private enum DrawType {
58 #region Local Variables
59 static private Cursor splitter_ns;
60 static private Cursor splitter_we;
61 // XXX this "new" shouldn't be here. Control shouldn't define border_style as internal.
62 new private BorderStyle border_style;
63 private int min_extra;
65 private int split_position; // Current splitter position
66 private int prev_split_position; // Previous splitter position, only valid during drag
67 private int click_offset; // Click offset from border of splitter control
68 private int splitter_size; // Size (width or height) of our splitter control
69 private bool horizontal; // true if we've got a horizontal splitter
70 private Control affected; // The control that the splitter resizes
71 private Control filler; // The control that MinExtra prevents from being shrunk to 0 size
72 private SplitterEventArgs sevent; // We cache the object, prevents fragmentation
73 private int limit_min; // The max we're allowed to move the splitter left/up
74 private int limit_max; // The max we're allowed to move the splitter right/down
75 private int split_requested; // If the user requests a position before we have ever laid out the doc
76 #endregion // Local Variables
80 splitter_ns = Cursors.HSplit;
81 splitter_we = Cursors.VSplit;
92 sevent = new SplitterEventArgs(0, 0, 0, 0);
94 SetStyle(ControlStyles.Selectable, false);
95 Anchor = AnchorStyles.None;
96 Dock = DockStyle.Left;
98 Layout += new LayoutEventHandler(LayoutSplitter);
99 this.ParentChanged += new EventHandler(ReparentSplitter);
100 Cursor = splitter_we;
102 #endregion // Constructors
104 #region Public Instance Properties
106 [EditorBrowsable(EditorBrowsableState.Never)]
107 public override bool AllowDrop {
109 return base.AllowDrop;
113 base.AllowDrop = value;
118 [DefaultValue(AnchorStyles.None)]
119 [EditorBrowsable(EditorBrowsableState.Never)]
120 public override AnchorStyles Anchor {
122 return AnchorStyles.None;
126 ; // MS doesn't set it
131 [EditorBrowsable(EditorBrowsableState.Never)]
132 public override Image BackgroundImage {
134 return base.BackgroundImage;
138 base.BackgroundImage = value;
144 [EditorBrowsable (EditorBrowsableState.Never)]
145 public override ImageLayout BackgroundImageLayout {
146 get { return base.BackgroundImageLayout; }
147 set { base.BackgroundImageLayout = value; }
152 [DefaultValue (BorderStyle.None)]
153 [MWFDescription("Sets the border style for the splitter")]
154 [MWFCategory("Appearance")]
155 public BorderStyle BorderStyle {
161 border_style = value;
164 case BorderStyle.FixedSingle:
165 splitter_size = 4; // We don't get motion events for 1px wide windows on X11. sigh.
168 case BorderStyle.Fixed3D:
169 value = BorderStyle.None;
173 case BorderStyle.None:
178 throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
181 base.InternalBorderStyle = value;
185 [DefaultValue(DockStyle.Left)]
187 public override DockStyle Dock {
193 if (!Enum.IsDefined (typeof (DockStyle), value) || (value == DockStyle.None) || (value == DockStyle.Fill)) {
194 throw new ArgumentException("Splitter must be docked left, top, bottom or right");
197 if ((value == DockStyle.Top) || (value == DockStyle.Bottom)) {
199 Cursor = splitter_ns;
202 Cursor = splitter_we;
209 [EditorBrowsable(EditorBrowsableState.Never)]
210 public override Font Font {
221 [EditorBrowsable(EditorBrowsableState.Never)]
222 public override Color ForeColor {
224 return base.ForeColor;
228 base.ForeColor = value;
233 [EditorBrowsable(EditorBrowsableState.Never)]
234 public new ImeMode ImeMode {
240 base.ImeMode = value;
246 [MWFDescription("Sets minimum size of undocked window")]
247 [MWFCategory("Behaviour")]
248 public int MinExtra {
260 [MWFDescription("Sets minimum size of the resized control")]
261 [MWFCategory("Behaviour")]
274 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
275 [MWFDescription("Current splitter position")]
276 [MWFCategory("Layout")]
277 public int SplitPosition {
279 affected = AffectedControl;
280 if (affected == null) {
285 return CalculateSplitPosition();
289 return affected.Height;
291 return affected.Width;
296 affected = AffectedControl;
298 if (affected == null) {
299 split_requested = value;
303 affected.Height = value;
305 affected.Width = value;
312 [EditorBrowsable(EditorBrowsableState.Never)]
313 public new bool TabStop {
314 get { return base.TabStop; }
315 set { base.TabStop = value; }
320 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
321 [EditorBrowsable(EditorBrowsableState.Never)]
322 public override string Text {
332 #endregion // Public Instance Properties
334 #region Protected Instance Properties
335 protected override CreateParams CreateParams {
337 return base.CreateParams;
342 protected override Cursor DefaultCursor {
343 get { return base.DefaultCursor; }
347 protected override ImeMode DefaultImeMode {
349 return ImeMode.Disable;
353 protected override Size DefaultSize {
355 return new Size (3, 3);
358 #endregion // Protected Instance Properties
360 #region Public Instance Methods
362 public bool PreFilterMessage(ref Message m) {
367 public override string ToString() {
368 return base.ToString () + String.Format(", MinExtra: {0}, MinSize: {1}", min_extra, min_size);
370 #endregion // Public Instance Methods
372 #region Protected Instance Methods
373 protected override void OnKeyDown(KeyEventArgs e) {
375 if (Capture && (e.KeyCode == Keys.Escape)) {
377 DrawDragHandle(DrawType.Finish);
381 protected override void OnMouseDown(MouseEventArgs e) {
384 base.OnMouseDown (e);
386 // Only allow if we are set up properly
387 if ((affected == null) || (filler == null)) {
388 affected = AffectedControl;
389 filler = FillerControl;
392 if (affected == null || e.Button != MouseButtons.Left) {
400 if (filler != null) {
402 if (Dock == DockStyle.Top) {
403 limit_min = affected.Bounds.Top + min_size;
404 limit_max = filler.Bounds.Bottom - min_extra + this.bounds.Top - filler.Bounds.Top;
406 limit_min = filler.Bounds.Top + min_extra + this.bounds.Top - filler.Bounds.Bottom;
407 limit_max = affected.Bounds.Bottom - min_size - this.Height;
410 if (Dock == DockStyle.Left) {
411 limit_min = affected.Bounds.Left + min_size;
412 limit_max = filler.Bounds.Right - min_extra + this.bounds.Left - filler.Bounds.Left;
414 limit_min = filler.Bounds.Left + min_extra + this.bounds.Left - filler.Bounds.Right;
415 limit_max = affected.Bounds.Right - min_size - this.Width;
421 limit_max = affected.Parent.Height;
423 limit_max = affected.Parent.Width;
428 Console.WriteLine("Sizing limits: Min:{0}, Max:{1}", limit_min, limit_max);
431 pt = PointToScreen(Parent.PointToClient(new Point(e.X, e.Y)));
434 split_position = pt.Y;
435 if (Dock == DockStyle.Top) {
441 split_position = pt.X;
442 if (Dock == DockStyle.Left) {
449 // We need to set this, in case we never get a mouse move
450 prev_split_position = split_position;
453 Console.WriteLine("Click-offset: {0} MouseDown split position: {1}", click_offset, split_position);
456 // Draw our initial handle
457 DrawDragHandle(DrawType.Initial);
460 protected override void OnMouseMove(MouseEventArgs e) {
463 base.OnMouseMove (e);
465 if (!Capture || e.Button != MouseButtons.Left || affected == null) {
469 // We need our mouse coordinates relative to our parent
470 pt = PointToScreen(Parent.PointToClient(new Point(e.X, e.Y)));
472 // Grab our new coordinates
473 prev_split_position = split_position;
475 int candidate = horizontal ? pt.Y : pt.X;
477 // Enforce limit on what we send to the event
478 if (candidate < limit_min)
479 candidate = limit_min;
480 else if (candidate > limit_max)
481 candidate = limit_max;
485 sevent.split_x = horizontal ? 0 : candidate;
486 sevent.split_y = horizontal ? candidate : 0;
489 OnSplitterMoving(sevent);
491 split_position = horizontal ? sevent.split_y : sevent.split_x;
494 if (split_position < limit_min) {
496 Console.WriteLine("SplitPosition {0} less than minimum {1}, setting to minimum", split_position, limit_min);
498 split_position = limit_min;
499 } else if (split_position > limit_max) {
501 Console.WriteLine("SplitPosition {0} more than maximum {1}, setting to maximum", split_position, limit_max);
503 split_position = limit_max;
506 // Update our handle location
507 DrawDragHandle(DrawType.Redraw);
510 protected override void OnMouseUp(MouseEventArgs e) {
511 if (!Capture || e.Button != MouseButtons.Left || affected == null) {
517 DrawDragHandle(DrawType.Finish);
519 // Resize the affected window
521 affected.Height = CalculateSplitPosition() - click_offset;
523 Console.WriteLine("Setting height of affected control to {0}", CalculateSplitPosition() - click_offset);
526 affected.Width = CalculateSplitPosition() - click_offset;
528 Console.WriteLine("Setting width of affected control to {0}", CalculateSplitPosition() - click_offset);
534 // It seems that MS is sending some data that doesn't quite make sense
535 // In this event. It tried to match their stuff.., not sure about split_x...
540 sevent.y = split_position;
541 sevent.split_x = 200;
542 sevent.split_y = split_position;
544 sevent.x = split_position;
546 sevent.split_x = split_position;
547 sevent.split_y = 200;
552 OnSplitterMoved(sevent);
555 protected virtual void OnSplitterMoved(SplitterEventArgs sevent) {
556 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovedEvent]);
561 protected virtual void OnSplitterMoving(SplitterEventArgs sevent) {
562 SplitterEventHandler eh = (SplitterEventHandler)(Events [SplitterMovingEvent]);
567 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
568 // enforce our width / height
570 splitter_size = height;
571 if (splitter_size < 1) {
574 base.SetBoundsCore (x, y, width, splitter_size, specified);
576 splitter_size = width;
577 if (splitter_size < 1) {
580 base.SetBoundsCore (x, y, splitter_size, height, specified);
583 #endregion // Protected Instance Methods
585 #region Private Properties and Methods
586 private Control AffectedControl {
591 // Doc says the first control preceeding us in the zorder
592 for (int i = Parent.Controls.GetChildIndex(this) + 1; i < Parent.Controls.Count; i++) {
595 if (Top == Parent.Controls[i].Bottom)
596 return Parent.Controls[i];
598 case DockStyle.Bottom:
599 if (Bottom == Parent.Controls[i].Top)
600 return Parent.Controls[i];
603 if (Left == Parent.Controls[i].Right)
604 return Parent.Controls[i];
606 case DockStyle.Right:
607 if (Right == Parent.Controls[i].Left)
608 return Parent.Controls[i];
616 private Control FillerControl {
621 // Doc says the first control preceeding us in the zorder
622 for (int i = Parent.Controls.GetChildIndex(this) - 1; i >= 0; i--) {
623 if (Parent.Controls[i].Dock == DockStyle.Fill) {
624 return Parent.Controls[i];
631 private int CalculateSplitPosition() {
633 if (Dock == DockStyle.Top)
634 return split_position - affected.Top;
636 return affected.Bottom - split_position - splitter_size;
638 if (Dock == DockStyle.Left)
639 return split_position - affected.Left;
641 return affected.Right - split_position - splitter_size;
645 internal override void OnPaintInternal (PaintEventArgs e) {
646 e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(this.BackColor), e.ClipRectangle);
649 private void LayoutSplitter(object sender, LayoutEventArgs e) {
650 affected = AffectedControl;
651 filler = FillerControl;
652 if (split_requested != -1) {
653 SplitPosition = split_requested;
654 split_requested = -1;
658 private void ReparentSplitter(object sender, EventArgs e) {
663 private void DrawDragHandle(DrawType type) {
668 prev = new Rectangle(Location.X, prev_split_position - click_offset + 1, Width, 0);
669 current = new Rectangle(Location.X, split_position - click_offset + 1, Width, 0);
671 prev = new Rectangle(prev_split_position - click_offset + 1, Location.Y, 0, Height);
672 current = new Rectangle(split_position - click_offset + 1, Location.Y, 0, Height);
676 case DrawType.Initial:
677 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
680 case DrawType.Redraw:
681 if (prev.X == current.X && prev.Y == current.Y)
684 XplatUI.DrawReversibleRectangle(Parent.window.Handle, prev, 3);
685 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
688 case DrawType.Finish:
689 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
693 #endregion // Private Properties and Methods
697 [EditorBrowsable(EditorBrowsableState.Never)]
698 public new event EventHandler BackgroundImageChanged {
699 add { base.BackgroundImageChanged += value; }
700 remove { base.BackgroundImageChanged -= value; }
705 [EditorBrowsable (EditorBrowsableState.Never)]
706 public new event EventHandler BackgroundImageLayoutChanged
708 add { base.BackgroundImageLayoutChanged += value; }
709 remove { base.BackgroundImageLayoutChanged -= value; }
715 [EditorBrowsable(EditorBrowsableState.Never)]
716 public new event EventHandler Enter {
717 add { base.Enter += value; }
718 remove { base.Enter -= value; }
722 [EditorBrowsable(EditorBrowsableState.Never)]
723 public new event EventHandler FontChanged {
724 add { base.FontChanged += value; }
725 remove { base.FontChanged -= value; }
729 [EditorBrowsable(EditorBrowsableState.Never)]
730 public new event EventHandler ForeColorChanged {
731 add { base.ForeColorChanged += value; }
732 remove { base.ForeColorChanged -= value; }
736 [EditorBrowsable(EditorBrowsableState.Never)]
737 public new event EventHandler ImeModeChanged {
738 add { base.ImeModeChanged += value; }
739 remove { base.ImeModeChanged -= value; }
743 [EditorBrowsable(EditorBrowsableState.Never)]
744 public new event KeyEventHandler KeyDown {
745 add { base.KeyDown += value; }
746 remove { base.KeyDown -= value; }
750 [EditorBrowsable(EditorBrowsableState.Never)]
751 public new event KeyPressEventHandler KeyPress {
752 add { base.KeyPress += value; }
753 remove { base.KeyPress -= value; }
757 [EditorBrowsable(EditorBrowsableState.Never)]
758 public new event KeyEventHandler KeyUp {
759 add { base.KeyUp += value; }
760 remove { base.KeyUp -= value; }
764 [EditorBrowsable(EditorBrowsableState.Never)]
765 public new event EventHandler Leave {
766 add { base.Leave += value; }
767 remove { base.Leave -= value; }
771 [EditorBrowsable(EditorBrowsableState.Never)]
772 public new event EventHandler TabStopChanged {
773 add { base.TabStopChanged += value; }
774 remove { base.TabStopChanged -= value; }
778 [EditorBrowsable(EditorBrowsableState.Never)]
779 public new event EventHandler TextChanged {
780 add { base.TextChanged += value; }
781 remove { base.TextChanged -= value; }
784 static object SplitterMovedEvent = new object ();
785 static object SplitterMovingEvent = new object ();
787 public event SplitterEventHandler SplitterMoved {
788 add { Events.AddHandler (SplitterMovedEvent, value); }
789 remove { Events.RemoveHandler (SplitterMovedEvent, value); }
792 public event SplitterEventHandler SplitterMoving {
793 add { Events.AddHandler (SplitterMovingEvent, value); }
794 remove { Events.RemoveHandler (SplitterMovingEvent, value); }