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, IMessageFilter {
43 private enum DrawType {
50 #region Local Variables
51 static private Cursor splitter_ns;
52 static private Cursor splitter_we;
53 private BorderStyle border_style;
54 private int min_extra;
56 private int split_position; // Current splitter position
57 private int prev_split_position; // Previous splitter position, only valid during drag
58 private int click_offset; // Click offset from border of splitter control
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 Control filler; // The control that MinExtra prevents from being shrunk to 0 size
63 private SplitterEventArgs sevent; // We cache the object, prevents fragmentation
64 private int limit_min; // The max we're allowed to move the splitter left/up
65 private int limit_max; // The max we're allowed to move the splitter right/down
66 private int split_requested; // If the user requests a position before we have ever laid out the doc
67 #endregion // Local Variables
71 splitter_ns = Cursors.HSplit;
72 splitter_we = Cursors.VSplit;
83 sevent = new SplitterEventArgs(0, 0, 0, 0);
85 SetStyle(ControlStyles.Selectable, false);
86 Anchor = AnchorStyles.None;
87 Dock = DockStyle.Left;
89 Layout += new LayoutEventHandler(LayoutSplitter);
90 this.ParentChanged += new EventHandler(ReparentSplitter);
93 #endregion // Constructors
95 #region Public Instance Properties
97 [EditorBrowsable(EditorBrowsableState.Never)]
98 public override bool AllowDrop {
100 return base.AllowDrop;
104 base.AllowDrop = value;
109 [DefaultValue(AnchorStyles.None)]
110 [EditorBrowsable(EditorBrowsableState.Never)]
111 public override AnchorStyles Anchor {
113 return AnchorStyles.None;
117 ; // MS doesn't set it
122 [EditorBrowsable(EditorBrowsableState.Never)]
123 public override Image BackgroundImage {
125 return base.BackgroundImage;
129 base.BackgroundImage = value;
134 [DefaultValue (BorderStyle.None)]
135 [MWFDescription("Sets the border style for the splitter")]
136 [MWFCategory("Appearance")]
137 public BorderStyle BorderStyle {
143 border_style = value;
146 case BorderStyle.FixedSingle: {
147 splitter_size = 4; // We don't get motion events for 1px wide windows on X11. sigh.
151 case BorderStyle.Fixed3D: {
152 value = BorderStyle.None;
157 case BorderStyle.None: {
163 throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
167 base.InternalBorderStyle = value;
171 [DefaultValue(DockStyle.Left)]
173 public override DockStyle Dock {
179 if (!Enum.IsDefined (typeof (DockStyle), value) || (value == DockStyle.None) || (value == DockStyle.Fill)) {
180 throw new ArgumentException("Splitter must be docked left, top, bottom or right");
183 if ((value == DockStyle.Top) || (value == DockStyle.Bottom)) {
185 Cursor = splitter_ns;
188 Cursor = splitter_we;
195 [EditorBrowsable(EditorBrowsableState.Never)]
196 public override Font Font {
207 [EditorBrowsable(EditorBrowsableState.Never)]
208 public override Color ForeColor {
210 return base.ForeColor;
214 base.ForeColor = value;
219 [EditorBrowsable(EditorBrowsableState.Never)]
220 public new ImeMode ImeMode {
226 base.ImeMode = value;
232 [MWFDescription("Sets minimum size of undocked window")]
233 [MWFCategory("Behaviour")]
234 public int MinExtra {
246 [MWFDescription("Sets minimum size of the resized control")]
247 [MWFCategory("Behaviour")]
260 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
261 [MWFDescription("Current splitter position")]
262 [MWFCategory("Layout")]
263 public int SplitPosition {
265 affected = AffectedControl;
266 if (affected == null) {
271 return CalculateSplitPosition();
275 return affected.Height;
277 return affected.Width;
282 affected = AffectedControl;
284 if (affected == null) {
285 split_requested = value;
288 if (Capture || (affected == null)) {
293 affected.Height = value;
295 affected.Width = value;
301 [EditorBrowsable(EditorBrowsableState.Never)]
302 public bool TabStop {
308 base.TabStop = value;
314 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
315 [EditorBrowsable(EditorBrowsableState.Never)]
316 public override string Text {
326 #endregion // Public Instance Properties
328 #region Protected Instance Properties
329 protected override CreateParams CreateParams {
331 return base.CreateParams;
335 protected override ImeMode DefaultImeMode {
337 return ImeMode.Disable;
341 protected override Size DefaultSize {
343 return new Size (3, 3);
346 #endregion // Protected Instance Properties
348 #region Public Instance Methods
349 public bool PreFilterMessage(ref Message m) {
353 public override string ToString() {
354 return base.ToString () + String.Format(", MinExtra: {0}, MinSize: {1}", min_extra, min_size);
356 #endregion // Public Instance Methods
358 #region Protected Instance Methods
359 protected override void OnKeyDown(KeyEventArgs e) {
361 if (Capture && (e.KeyCode == Keys.Escape)) {
363 DrawDragHandle(DrawType.Finish);
367 protected override void OnMouseDown(MouseEventArgs e) {
370 base.OnMouseDown (e);
372 // Only allow if we are set up properly
373 if ((affected == null) || (filler == null)) {
374 affected = AffectedControl;
375 filler = FillerControl;
378 if (affected == null || e.Button != MouseButtons.Left) {
386 if (filler != null) {
388 if (dock_style == DockStyle.Top) {
389 limit_min = affected.Bounds.Top + min_size;
390 limit_max = filler.Bounds.Bottom - min_extra + this.bounds.Top - filler.Bounds.Top;
392 limit_min = filler.Bounds.Top + min_extra + this.bounds.Top - filler.Bounds.Bottom;
393 limit_max = affected.Bounds.Bottom - min_size - this.Height;
396 if (dock_style == DockStyle.Left) {
397 limit_min = affected.Bounds.Left + min_size;
398 limit_max = filler.Bounds.Right - min_extra + this.bounds.Left - filler.Bounds.Left;
400 limit_min = filler.Bounds.Left + min_extra + this.bounds.Left - filler.Bounds.Right;
401 limit_max = affected.Bounds.Right - min_size - this.Width;
407 limit_max = affected.Parent.Height;
409 limit_max = affected.Parent.Width;
414 Console.WriteLine("Sizing limits: Min:{0}, Max:{1}", limit_min, limit_max);
417 pt = PointToScreen(parent.PointToClient(new Point(e.X, e.Y)));
420 split_position = pt.Y;
421 if (dock_style == DockStyle.Top) {
427 split_position = pt.X;
428 if (dock_style == DockStyle.Left) {
435 // We need to set this, in case we never get a mouse move
436 prev_split_position = split_position;
439 Console.WriteLine("Click-offset: {0} MouseDown split position: {1}", click_offset, split_position);
442 // Draw our initial handle
443 DrawDragHandle(DrawType.Initial);
446 protected override void OnMouseMove(MouseEventArgs e) {
449 base.OnMouseMove (e);
451 if (!Capture || e.Button != MouseButtons.Left || affected == null) {
455 // We need our mouse coordinates relative to our parent
456 pt = PointToScreen(parent.PointToClient(new Point(e.X, e.Y)));
458 // Grab our new coordinates
459 prev_split_position = split_position;
461 int candidate = horizontal ? pt.Y : pt.X;
463 // Enforce limit on what we send to the event
464 if (candidate < limit_min)
465 candidate = limit_min;
466 else if (candidate > limit_max)
467 candidate = limit_max;
471 sevent.split_x = horizontal ? 0 : candidate;
472 sevent.split_y = horizontal ? candidate : 0;
475 OnSplitterMoving(sevent);
477 split_position = horizontal ? sevent.split_y : sevent.split_x;
480 if (split_position < limit_min) {
482 Console.WriteLine("SplitPosition {0} less than minimum {1}, setting to minimum", split_position, limit_min);
484 split_position = limit_min;
485 } else if (split_position > limit_max) {
487 Console.WriteLine("SplitPosition {0} more than maximum {1}, setting to maximum", split_position, limit_max);
489 split_position = limit_max;
492 // Don't waste cycles
493 if (prev_split_position != split_position) {
494 // Update our handle location
495 DrawDragHandle(DrawType.Redraw);
499 protected override void OnMouseUp(MouseEventArgs e) {
500 if (!Capture || e.Button != MouseButtons.Left || affected == null) {
506 DrawDragHandle(DrawType.Finish);
508 // Resize the affected window
510 affected.Height = CalculateSplitPosition() - click_offset;
512 Console.WriteLine("Setting height of affected control to {0}", CalculateSplitPosition() - click_offset);
515 affected.Width = CalculateSplitPosition() - click_offset;
517 Console.WriteLine("Setting width of affected control to {0}", CalculateSplitPosition() - click_offset);
523 // It seems that MS is sending some data that doesn't quite make sense
524 // In this event. It tried to match their stuff.., not sure about split_x...
529 sevent.y = split_position;
530 sevent.split_x = 200;
531 sevent.split_y = split_position;
533 sevent.x = split_position;
535 sevent.split_x = split_position;
536 sevent.split_y = 200;
541 OnSplitterMoved(sevent);
544 protected virtual void OnSplitterMoved(SplitterEventArgs sevent) {
545 if (SplitterMoved != null) {
546 SplitterMoved(this, sevent);
550 protected virtual void OnSplitterMoving(SplitterEventArgs sevent) {
551 if (SplitterMoving != null) {
552 SplitterMoving(this, sevent);
556 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
557 // enforce our width / height
559 splitter_size = height;
560 if (splitter_size < 1) {
563 base.SetBoundsCore (x, y, width, splitter_size, specified);
565 splitter_size = width;
566 if (splitter_size < 1) {
569 base.SetBoundsCore (x, y, splitter_size, height, specified);
572 #endregion // Protected Instance Methods
574 #region Private Properties and Methods
575 private Control AffectedControl {
577 if (parent == null) {
581 // Doc says the first control preceeding us in the zorder
582 for (int i = parent.Controls.GetChildIndex(this) + 1; i < parent.Controls.Count; i++) {
584 case DockStyle.Top: {
585 if (Top == parent.Controls[i].Bottom) {
586 return parent.Controls[i];
591 case DockStyle.Bottom: {
592 if (Bottom == parent.Controls[i].Top) {
593 return parent.Controls[i];
598 case DockStyle.Left: {
599 if (Left == parent.Controls[i].Right) {
600 return parent.Controls[i];
605 case DockStyle.Right: {
606 if (Right == parent.Controls[i].Left) {
607 return parent.Controls[i];
617 private Control FillerControl {
619 if (parent == null) {
623 // Doc says the first control preceeding us in the zorder
624 for (int i = parent.Controls.GetChildIndex(this) - 1; i >= 0; i--) {
625 if (parent.Controls[i].Dock == DockStyle.Fill) {
626 return parent.Controls[i];
633 private int CalculateSplitPosition() {
635 if (dock_style == DockStyle.Top) {
636 return split_position - affected.Top;
638 return affected.Bottom - split_position - splitter_size;
641 if (dock_style == DockStyle.Left) {
642 return split_position - affected.Left;
644 return affected.Right - split_position - splitter_size;
649 internal override void OnPaintInternal (PaintEventArgs e) {
650 e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(this.BackColor), e.ClipRectangle);
653 private void LayoutSplitter(object sender, LayoutEventArgs e) {
654 affected = AffectedControl;
655 filler = FillerControl;
656 if (split_requested != -1) {
657 SplitPosition = split_requested;
658 split_requested = -1;
662 private void ReparentSplitter(object sender, EventArgs e) {
667 private void DrawDragHandle(DrawType type) {
672 prev = new Rectangle(Location.X, prev_split_position - click_offset + 1, Width, 0);
673 current = new Rectangle(Location.X, split_position - click_offset + 1, Width, 0);
675 prev = new Rectangle(prev_split_position - click_offset + 1, Location.Y, 0, Height);
676 current = new Rectangle(split_position - click_offset + 1, Location.Y, 0, Height);
680 case DrawType.Initial: {
681 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
685 case DrawType.Redraw: {
686 if (prev.X == current.X && prev.Y == current.Y) {
690 XplatUI.DrawReversibleRectangle(Parent.window.Handle, prev, 3);
691 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
695 case DrawType.Finish: {
696 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
701 #endregion // Private Properties and Methods
705 [EditorBrowsable(EditorBrowsableState.Never)]
706 public new event EventHandler BackgroundImageChanged {
707 add { base.BackgroundImageChanged += value; }
708 remove { base.BackgroundImageChanged -= value; }
712 [EditorBrowsable(EditorBrowsableState.Never)]
713 public new event EventHandler Enter {
714 add { base.Enter += value; }
715 remove { base.Enter -= value; }
719 [EditorBrowsable(EditorBrowsableState.Never)]
720 public new event EventHandler FontChanged {
721 add { base.FontChanged += value; }
722 remove { base.FontChanged -= value; }
726 [EditorBrowsable(EditorBrowsableState.Never)]
727 public new event EventHandler ForeColorChanged {
728 add { base.ForeColorChanged += value; }
729 remove { base.ForeColorChanged -= value; }
733 [EditorBrowsable(EditorBrowsableState.Never)]
734 public new event EventHandler ImeModeChanged {
735 add { base.ImeModeChanged += value; }
736 remove { base.ImeModeChanged -= value; }
740 [EditorBrowsable(EditorBrowsableState.Never)]
741 public new event KeyEventHandler KeyDown {
742 add { base.KeyDown += value; }
743 remove { base.KeyDown -= value; }
747 [EditorBrowsable(EditorBrowsableState.Never)]
748 public new event KeyPressEventHandler KeyPress {
749 add { base.KeyPress += value; }
750 remove { base.KeyPress -= value; }
754 [EditorBrowsable(EditorBrowsableState.Never)]
755 public new event KeyEventHandler KeyUp {
756 add { base.KeyUp += value; }
757 remove { base.KeyUp -= value; }
761 [EditorBrowsable(EditorBrowsableState.Never)]
762 public new event EventHandler Leave {
763 add { base.Leave += value; }
764 remove { base.Leave -= value; }
768 [EditorBrowsable(EditorBrowsableState.Never)]
769 public new event EventHandler TabStopChanged {
770 add { base.TabStopChanged += value; }
771 remove { base.TabStopChanged -= value; }
775 [EditorBrowsable(EditorBrowsableState.Never)]
776 public new event EventHandler TextChanged {
777 add { base.TextChanged += value; }
778 remove { base.TextChanged -= value; }
781 public event SplitterEventHandler SplitterMoved;
782 public event SplitterEventHandler SplitterMoving;