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 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 #endregion // Local Variables
71 splitter_ns = new Cursor(typeof(Splitter), "SpliterNS.cur");
74 catch (System.IO.FileNotFoundException) {
75 splitter_ns = Cursors.SizeNS;
79 splitter_we = new Cursor(typeof(Splitter), "SplitterWE.cur");
82 catch (System.IO.FileNotFoundException) {
83 splitter_we = Cursors.SizeWE;
94 sevent = new SplitterEventArgs(0, 0, 0, 0);
96 SetStyle(ControlStyles.Selectable, false);
97 Anchor = AnchorStyles.None;
99 Paint += new PaintEventHandler(PaintSplitter);
100 Layout += new LayoutEventHandler(LayoutSplitter);
101 Cursor = splitter_we;
103 #endregion // Constructors
105 #region Public Instance Properties
107 [EditorBrowsable(EditorBrowsableState.Never)]
108 public override bool AllowDrop {
110 return base.AllowDrop;
114 base.AllowDrop = value;
119 [DefaultValue(AnchorStyles.None)]
120 [EditorBrowsable(EditorBrowsableState.Never)]
121 public override AnchorStyles Anchor {
123 return AnchorStyles.None;
127 ; // MS doesn't set it
132 [EditorBrowsable(EditorBrowsableState.Never)]
133 public override Image BackgroundImage {
135 return base.BackgroundImage;
139 base.BackgroundImage = value;
144 [DefaultValue (BorderStyle.None)]
145 [MWFDescription("Sets the border style for the splitter")]
146 [MWFCategory("Appearance")]
147 public BorderStyle BorderStyle {
153 border_style = value;
156 case BorderStyle.FixedSingle: {
157 splitter_size = 4; // We don't get motion events for 1px wide windows on X11. sigh.
161 case BorderStyle.Fixed3D: {
162 value = BorderStyle.None;
167 case BorderStyle.None: {
173 throw new InvalidEnumArgumentException (string.Format("Enum argument value '{0}' is not valid for BorderStyle", value));
177 base.InternalBorderStyle = value;
181 [DefaultValue(DockStyle.Left)]
183 public override DockStyle Dock {
189 if (!Enum.IsDefined (typeof (DockStyle), value) || (value == DockStyle.None) || (value == DockStyle.Fill)) {
190 throw new ArgumentException("Splitter must be docked left, top, bottom or right");
193 if ((value == DockStyle.Top) || (value == DockStyle.Bottom)) {
195 Cursor = splitter_ns;
198 Cursor = splitter_we;
205 [EditorBrowsable(EditorBrowsableState.Never)]
206 public override Font Font {
217 [EditorBrowsable(EditorBrowsableState.Never)]
218 public override Color ForeColor {
220 return base.ForeColor;
224 base.ForeColor = value;
229 [EditorBrowsable(EditorBrowsableState.Never)]
230 public new ImeMode ImeMode {
236 base.ImeMode = value;
242 [MWFDescription("Sets minimum size of undocked window")]
243 [MWFCategory("Behaviour")]
244 public int MinExtra {
256 [MWFDescription("Sets minimum size of the resized control")]
257 [MWFCategory("Behaviour")]
270 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
271 [MWFDescription("Current splitter position")]
272 [MWFCategory("Layout")]
273 public int SplitPosition {
275 if (affected == null) {
280 return CalculateSplitPosition();
284 return affected.Height;
286 return affected.Width;
291 if (Capture || (affected == null)) {
296 affected.Height = value;
298 affected.Width = value;
304 [EditorBrowsable(EditorBrowsableState.Never)]
305 public bool TabStop {
311 base.TabStop = value;
317 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
318 [EditorBrowsable(EditorBrowsableState.Never)]
319 public override string Text {
329 #endregion // Public Instance Properties
331 #region Protected Instance Properties
332 protected override CreateParams CreateParams {
334 return base.CreateParams;
338 protected override ImeMode DefaultImeMode {
340 return ImeMode.Disable;
344 protected override Size DefaultSize {
346 return new Size (3, 3);
349 #endregion // Protected Instance Properties
351 #region Public Instance Methods
352 public bool PreFilterMessage(ref Message m) {
356 public override string ToString() {
357 return base.ToString () + String.Format(", MinExtra: {0}, MinSize: {1}", min_extra, min_size);
359 #endregion // Public Instance Methods
361 #region Protected Instance Methods
362 protected override void OnKeyDown(KeyEventArgs e) {
364 if (Capture && (e.KeyCode == Keys.Escape)) {
366 DrawDragHandle(DrawType.Finish);
370 protected override void OnMouseDown(MouseEventArgs e) {
373 base.OnMouseDown (e);
375 // Only allow if we are set up properly
376 if (affected == null || e.Button != MouseButtons.Left) {
384 if (filler != null) {
386 if (dock_style == DockStyle.Top) {
387 limit_min = affected.Bounds.Top + min_size;
388 limit_max = filler.Bounds.Bottom - min_extra + this.bounds.Top - filler.Bounds.Top;
390 limit_min = filler.Bounds.Top + min_extra + this.bounds.Top - filler.Bounds.Bottom;
391 limit_max = affected.Bounds.Bottom - min_size - this.Height;
394 if (dock_style == DockStyle.Left) {
395 limit_min = affected.Bounds.Left + min_size;
396 limit_max = filler.Bounds.Right - min_extra + this.bounds.Left - filler.Bounds.Left;
398 limit_min = filler.Bounds.Left + min_extra + this.bounds.Left - filler.Bounds.Right;
399 limit_max = affected.Bounds.Right - min_size - this.Width;
405 limit_max = affected.Parent.Height;
407 limit_max = affected.Parent.Width;
412 Console.WriteLine("Sizing limits: Min:{0}, Max:{1}", limit_min, limit_max);
415 pt = PointToScreen(parent.PointToClient(new Point(e.X, e.Y)));
418 split_position = pt.Y;
419 if (dock_style == DockStyle.Top) {
425 split_position = pt.X;
426 if (dock_style == DockStyle.Left) {
433 // We need to set this, in case we never get a mouse move
434 prev_split_position = split_position;
437 Console.WriteLine("Click-offset: {0} MouseDown split position: {1}", click_offset, split_position);
440 // Draw our initial handle
441 DrawDragHandle(DrawType.Initial);
444 protected override void OnMouseMove(MouseEventArgs e) {
447 base.OnMouseMove (e);
449 if (!Capture || e.Button != MouseButtons.Left) {
453 // We need our mouse coordinates relative to our parent
454 pt = PointToScreen(parent.PointToClient(new Point(e.X, e.Y)));
456 // Grab our new coordinates
457 prev_split_position = split_position;
459 split_position = pt.Y;
461 split_position = pt.X;
464 if (split_position < limit_min) {
466 Console.WriteLine("SplitPosition {0} less than minimum {1}, setting to minimum", split_position, limit_min);
468 split_position = limit_min;
469 } else if (split_position > limit_max) {
471 Console.WriteLine("SplitPosition {0} more than maximum {1}, setting to maximum", split_position, limit_max);
473 split_position = limit_max;
476 // Don't waste cycles
477 if (prev_split_position != split_position) {
478 // Update our handle location
479 DrawDragHandle(DrawType.Redraw);
485 sevent.split_y = split_position;
487 sevent.split_x = split_position;
495 OnSplitterMoving(sevent);
498 protected override void OnMouseUp(MouseEventArgs e) {
499 if (!Capture || e.Button != MouseButtons.Left) {
505 DrawDragHandle(DrawType.Finish);
507 // Resize the affected window
509 affected.Height = CalculateSplitPosition() - click_offset;
511 Console.WriteLine("Setting height of affected control to {0}", CalculateSplitPosition() - click_offset);
514 affected.Width = CalculateSplitPosition() - click_offset;
516 Console.WriteLine("Setting width of affected control to {0}", CalculateSplitPosition() - click_offset);
522 // It seems that MS is sending some data that doesn't quite make sense
523 // In this event. It tried to match their stuff.., not sure about split_x...
528 sevent.y = split_position;
529 sevent.split_x = 200;
530 sevent.split_y = split_position;
532 sevent.x = split_position;
534 sevent.split_x = split_position;
535 sevent.split_y = 200;
540 OnSplitterMoved(sevent);
543 protected virtual void OnSplitterMoved(SplitterEventArgs sevent) {
544 if (SplitterMoved != null) {
545 SplitterMoved(this, sevent);
549 protected virtual void OnSplitterMoving(SplitterEventArgs sevent) {
550 if (SplitterMoving != null) {
551 SplitterMoving(this, sevent);
555 protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
556 // enforce our width / height
558 base.SetBoundsCore (x, y, width, splitter_size, specified);
560 base.SetBoundsCore (x, y, splitter_size, height, specified);
563 #endregion // Protected Instance Methods
565 #region Private Properties and Methods
566 private Control AffectedControl {
568 if (parent == null) {
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++) {
575 case DockStyle.Top: {
576 if (Top == parent.Controls[i].Bottom) {
577 return parent.Controls[i];
582 case DockStyle.Bottom: {
583 if (Bottom == parent.Controls[i].Top) {
584 return parent.Controls[i];
589 case DockStyle.Left: {
590 if (Left == parent.Controls[i].Right) {
591 return parent.Controls[i];
596 case DockStyle.Right: {
597 if (Right == parent.Controls[i].Left) {
598 return parent.Controls[i];
608 private Control FillerControl {
610 if (parent == null) {
614 // Doc says the first control preceeding us in the zorder
615 for (int i = parent.Controls.GetChildIndex(this) - 1; i >= 0; i--) {
616 if (parent.Controls[i].Dock == DockStyle.Fill) {
617 return parent.Controls[i];
624 private int CalculateSplitPosition() {
626 if (dock_style == DockStyle.Top) {
627 return split_position;
629 return affected.Bottom - split_position - splitter_size;
632 if (dock_style == DockStyle.Left) {
633 return split_position;
635 return affected.Right - split_position - splitter_size;
640 private void PaintSplitter(object sender, PaintEventArgs e) {
641 e.Graphics.FillRectangle(ThemeEngine.Current.ResPool.GetSolidBrush(this.BackColor), e.ClipRectangle);
644 private void LayoutSplitter(object sender, LayoutEventArgs e) {
645 affected = AffectedControl;
646 filler = FillerControl;
649 private void DrawDragHandle(DrawType type) {
654 prev = new Rectangle(0, prev_split_position - click_offset + 1, Width, 0);
655 current = new Rectangle(0, split_position - click_offset + 1, Width, 0);
657 prev = new Rectangle(prev_split_position - click_offset + 1, 0, 0, Height);
658 current = new Rectangle(split_position - click_offset + 1, 0, 0, Height);
662 case DrawType.Initial: {
663 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
667 case DrawType.Redraw: {
668 if (prev.X == current.X && prev.Y == current.Y) {
672 XplatUI.DrawReversibleRectangle(Parent.window.Handle, prev, 3);
673 XplatUI.DrawReversibleRectangle(Parent.window.Handle, current, 3);
677 case DrawType.Finish: {
678 XplatUI.DrawReversibleRectangle(Parent.window.Handle, prev, 3);
683 #endregion // Private Properties and Methods
687 [EditorBrowsable(EditorBrowsableState.Never)]
688 public new event EventHandler BackgroundImageChanged;
691 [EditorBrowsable(EditorBrowsableState.Never)]
692 public new event EventHandler Enter;
695 [EditorBrowsable(EditorBrowsableState.Never)]
696 public new event EventHandler FontChanged;
699 [EditorBrowsable(EditorBrowsableState.Never)]
700 public new event EventHandler ForeColorChanged;
703 [EditorBrowsable(EditorBrowsableState.Never)]
704 public new event EventHandler ImeModeChanged;
707 [EditorBrowsable(EditorBrowsableState.Never)]
708 public new event KeyEventHandler KeyDown;
711 [EditorBrowsable(EditorBrowsableState.Never)]
712 public new event KeyPressEventHandler KeyPress;
715 [EditorBrowsable(EditorBrowsableState.Never)]
716 public new event KeyEventHandler KeyUp;
719 [EditorBrowsable(EditorBrowsableState.Never)]
720 public new event EventHandler Leave;
723 [EditorBrowsable(EditorBrowsableState.Never)]
724 public new event EventHandler TabStopChanged;
727 [EditorBrowsable(EditorBrowsableState.Never)]
728 public new event EventHandler TextChanged;
730 public event SplitterEventHandler SplitterMoved;
731 public event SplitterEventHandler SplitterMoving;