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) 2004 Novell, Inc.
23 // Peter Bartok pbartok@novell.com
30 using System.ComponentModel;
31 using System.ComponentModel.Design;
34 namespace System.Windows.Forms {
35 [Designer ("System.Windows.Forms.Design.ScrollableControlDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
36 public class ScrollableControl : Control {
37 #region Local Variables
38 private bool hscroll_visible;
39 private bool vscroll_visible;
40 private bool force_hscroll_visible;
41 private bool force_vscroll_visible;
42 private bool auto_scroll;
43 private Size auto_scroll_margin;
44 private Size auto_scroll_min_size;
45 private Point scroll_position;
46 private DockPaddingEdges dock_padding;
47 private SizeGrip sizegrip;
48 private ImplicitHScrollBar hscrollbar;
49 private ImplicitVScrollBar vscrollbar;
50 private Size canvas_size;
51 private Rectangle display_rectangle;
52 #endregion // Local Variables
54 [TypeConverter(typeof(ScrollableControl.DockPaddingEdgesConverter))]
55 #region Subclass DockPaddingEdges
56 public class DockPaddingEdges : ICloneable {
57 #region DockPaddingEdges Local Variables
63 private Control owner;
64 #endregion // DockPaddingEdges Local Variables
66 #region DockPaddingEdges Constructor
67 internal DockPaddingEdges(Control owner) {
75 #endregion // DockPaddingEdges Constructor
77 #region DockPaddingEdges Public Instance Properties
78 [RefreshProperties(RefreshProperties.All)]
91 owner.PerformLayout();
95 [RefreshProperties(RefreshProperties.All)]
105 owner.PerformLayout();
109 [RefreshProperties(RefreshProperties.All)]
119 owner.PerformLayout();
123 [RefreshProperties(RefreshProperties.All)]
133 owner.PerformLayout();
137 [RefreshProperties(RefreshProperties.All)]
147 owner.PerformLayout();
150 #endregion // DockPaddingEdges Public Instance Properties
152 // Public Instance Methods
153 public override bool Equals(object other) {
154 if (! (other is DockPaddingEdges)) {
158 if ( (this.all == ((DockPaddingEdges)other).all) && (this.left == ((DockPaddingEdges)other).left) &&
159 (this.right == ((DockPaddingEdges)other).right) && (this.top == ((DockPaddingEdges)other).top) &&
160 (this.bottom == ((DockPaddingEdges)other).bottom)) {
167 public override int GetHashCode() {
168 return all*top*bottom*right*left;
171 public override string ToString() {
172 return "All = "+all.ToString()+" Top = "+top.ToString()+" Left = "+left.ToString()+" Bottom = "+bottom.ToString()+" Right = "+right.ToString();
175 internal void Scale(float dx, float dy) {
176 left = (int) (left * dx);
177 right = (int) (right * dx);
178 top = (int) (top * dy);
179 bottom = (int) (bottom * dy);
182 object ICloneable.Clone() {
183 DockPaddingEdges padding_edge;
185 padding_edge=new DockPaddingEdges(owner);
187 padding_edge.all=all;
188 padding_edge.left=left;
189 padding_edge.right=right;
190 padding_edge.top=top;
191 padding_edge.bottom=bottom;
196 #endregion // Subclass DockPaddingEdges
198 #region Subclass DockPaddingEdgesConverter
199 public class DockPaddingEdgesConverter : System.ComponentModel.TypeConverter {
200 // Public Constructors
201 public DockPaddingEdgesConverter() {
204 // Public Instance Methods
205 public override PropertyDescriptorCollection GetProperties(System.ComponentModel.ITypeDescriptorContext context, object value, Attribute[] attributes) {
206 return TypeDescriptor.GetProperties(typeof(DockPaddingEdges), attributes);
209 public override bool GetPropertiesSupported(System.ComponentModel.ITypeDescriptorContext context) {
213 #endregion // Subclass DockPaddingEdgesConverter
215 #region Public Constructors
216 public ScrollableControl() {
217 SetStyle(ControlStyles.ContainerControl, true);
218 SetStyle(ControlStyles.AllPaintingInWmPaint, false);
220 hscroll_visible = false;
221 vscroll_visible = false;
222 force_hscroll_visible = false;
223 force_vscroll_visible = false;
224 auto_scroll_margin = new Size(0, 0);
225 auto_scroll_min_size = new Size(0, 0);
226 scroll_position = new Point(0, 0);
227 dock_padding = new DockPaddingEdges(this);
228 SizeChanged +=new EventHandler(Recalculate);
229 VisibleChanged += new EventHandler(Recalculate);
231 #endregion // Public Constructors
233 #region Protected Static Fields
234 protected const int ScrollStateAutoScrolling = 1;
235 protected const int ScrollStateFullDrag = 16;
236 protected const int ScrollStateHScrollVisible = 2;
237 protected const int ScrollStateUserHasScrolled = 8;
238 protected const int ScrollStateVScrollVisible = 4;
239 #endregion // Protected Static Fields
241 #region Public Instance Properties
242 [DefaultValue(false)]
244 [MWFCategory("Layout")]
245 public virtual bool AutoScroll {
251 if (auto_scroll == value) {
259 Controls.RemoveImplicit (hscrollbar);
260 hscrollbar.Dispose();
262 hscroll_visible = false;
264 Controls.RemoveImplicit (vscrollbar);
265 vscrollbar.Dispose();
267 vscroll_visible = false;
269 Controls.RemoveImplicit (sizegrip);
277 hscrollbar = new ImplicitHScrollBar();
278 hscrollbar.Visible = false;
279 hscrollbar.ValueChanged += new EventHandler(HandleScrollBar);
280 hscrollbar.Height = SystemInformation.HorizontalScrollBarHeight;
281 this.Controls.AddImplicit (hscrollbar);
283 vscrollbar = new ImplicitVScrollBar();
284 vscrollbar.Visible = false;
285 vscrollbar.ValueChanged += new EventHandler(HandleScrollBar);
286 vscrollbar.Width = SystemInformation.VerticalScrollBarWidth;
287 this.Controls.AddImplicit (vscrollbar);
289 sizegrip = new SizeGrip();
290 sizegrip.Visible = false;
291 this.Controls.AddImplicit (sizegrip);
299 [MWFCategory("Layout")]
300 public Size AutoScrollMargin {
302 return auto_scroll_margin;
306 if (value.Width < 0) {
307 throw new ArgumentException("Width is assigned less than 0", "value.Width");
310 if (value.Height < 0) {
311 throw new ArgumentException("Height is assigned less than 0", "value.Height");
314 auto_scroll_margin = value;
319 [MWFCategory("Layout")]
320 public Size AutoScrollMinSize {
322 return auto_scroll_min_size;
326 if (value != auto_scroll_min_size) {
327 auto_scroll_min_size = value;
334 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
335 public Point AutoScrollPosition {
337 return new Point(-scroll_position.X, -scroll_position.Y);
341 if ((value.X != scroll_position.X) || (value.Y != scroll_position.Y)) {
347 if (hscroll_visible) {
348 shift_x = value.X - scroll_position.X;
351 if (vscroll_visible) {
352 shift_y = value.Y - scroll_position.Y;
355 ScrollWindow(shift_x, shift_y);
357 if (hscroll_visible) {
358 hscrollbar.Value = scroll_position.X;
361 if (vscroll_visible) {
362 vscrollbar.Value = scroll_position.Y;
369 public override Rectangle DisplayRectangle {
375 if (canvas_size.Width <= base.DisplayRectangle.Width) {
376 width = base.DisplayRectangle.Width;
377 if (vscroll_visible) {
378 width -= vscrollbar.Width;
381 width = canvas_size.Width;
384 if (canvas_size.Height <= base.DisplayRectangle.Height) {
385 height = base.DisplayRectangle.Height;
386 if (hscroll_visible) {
387 height -= hscrollbar.Height;
390 height = canvas_size.Height;
393 display_rectangle.X = -scroll_position.X;
394 display_rectangle.Y = -scroll_position.Y;
395 display_rectangle.Width = Math.Max(auto_scroll_min_size.Width, width);
396 display_rectangle.Height = Math.Max(auto_scroll_min_size.Height, height);
399 display_rectangle = base.DisplayRectangle;
402 display_rectangle.X += dock_padding.Left;
403 display_rectangle.Y += dock_padding.Top;
404 display_rectangle.Width -= dock_padding.Left + dock_padding.Right;
405 display_rectangle.Height -= dock_padding.Top + dock_padding.Bottom;
407 return display_rectangle;
411 [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
413 [MWFCategory("Layout")]
414 public DockPaddingEdges DockPadding {
419 #endregion // Public Instance Properties
421 #region Protected Instance Methods
422 protected override CreateParams CreateParams {
424 return base.CreateParams;
428 protected bool HScroll {
430 return hscroll_visible;
434 if (hscroll_visible != value) {
435 force_hscroll_visible = value;
436 Recalculate(this, EventArgs.Empty);
441 protected bool VScroll {
443 return vscroll_visible;
447 if (vscroll_visible != value) {
448 force_vscroll_visible = value;
449 Recalculate(this, EventArgs.Empty);
453 #endregion // Protected Instance Methods
455 #region Public Instance Methods
456 public void ScrollControlIntoView(Control activeControl) {
462 if (!AutoScroll || (!hscroll_visible && !vscroll_visible)) {
466 if (!Contains(activeControl)) {
470 x = activeControl.Left;
471 y = activeControl.Top;
473 // Translate into coords relative to us
474 if (activeControl.Parent != this) {
475 activeControl.PointToScreen(ref x, ref y);
476 PointToClient(ref x, ref y);
479 x += scroll_position.X;
480 y += scroll_position.Y;
482 // Don't scroll if already visible
483 if ((activeControl.Left >= scroll_position.X) && (activeControl.Left < (scroll_position.X + ClientSize.Width)) &&
484 (activeControl.Top >= scroll_position.Y) && (activeControl.Top < (scroll_position.Y + ClientSize.Height))) {
489 corner_x = Math.Max(0, x + activeControl.Width / 2 - ClientSize.Width / 2);
490 corner_y = Math.Max(0, y + activeControl.Height / 2 - ClientSize.Height / 2);
492 if (hscroll_visible && (corner_x > hscrollbar.Maximum)) {
493 corner_x = Math.Max(0, hscrollbar.Maximum - ClientSize.Width);
496 if (vscroll_visible && (corner_y > vscrollbar.Maximum)) {
497 corner_y = Math.Max(0, vscrollbar.Maximum - ClientSize.Height);
499 if ((corner_x == scroll_position.X) && (corner_y == scroll_position.Y)) {
503 //this.SetDisplayRectLocation(-corner_x, -corner_y);
504 hscrollbar.Value = corner_x;
505 vscrollbar.Value = corner_y;
508 public void SetAutoScrollMargin(int x, int y) {
517 auto_scroll_margin = new Size(x, y);
518 Recalculate(this, EventArgs.Empty);
520 #endregion // Public Instance Methods
522 #region Protected Instance Methods
523 [EditorBrowsable(EditorBrowsableState.Advanced)]
524 protected virtual void AdjustFormScrollbars(bool displayScrollbars) {
525 Recalculate(this, EventArgs.Empty);
528 [EditorBrowsable(EditorBrowsableState.Advanced)]
529 protected bool GetScrollState(int bit) {
534 [EditorBrowsable(EditorBrowsableState.Advanced)]
535 protected override void OnLayout(LayoutEventArgs levent) {
536 CalculateCanvasSize();
538 AdjustFormScrollbars(AutoScroll); // Dunno what the logic is. Passing AutoScroll seems to match MS behaviour
539 base.OnLayout(levent);
542 [EditorBrowsable(EditorBrowsableState.Advanced)]
543 protected override void OnMouseWheel(MouseEventArgs e) {
544 if (vscroll_visible) {
546 if (vscrollbar.Minimum < (vscrollbar.Value - vscrollbar.LargeChange)) {
547 vscrollbar.Value -= vscrollbar.LargeChange;
549 vscrollbar.Value = vscrollbar.Minimum;
552 if (vscrollbar.Maximum > (vscrollbar.Value + vscrollbar.LargeChange)) {
553 vscrollbar.Value += vscrollbar.LargeChange;
555 vscrollbar.Value = vscrollbar.Maximum;
559 base.OnMouseWheel(e);
562 [EditorBrowsable(EditorBrowsableState.Advanced)]
563 protected override void OnVisibleChanged(EventArgs e) {
567 base.OnVisibleChanged(e);
570 protected override void ScaleCore(float dx, float dy) {
571 dock_padding.Scale(dx, dy);
572 base.ScaleCore(dx, dy);
575 protected void SetDisplayRectLocation(int x, int y) {
576 // This method is weird. MS documents that the scrollbars are not
577 // updated. We need to move stuff, but leave the scrollbars as is
587 ScrollWindow(scroll_position.X - x , scroll_position.Y - y);
590 protected void SetScrollState(int bit, bool value) {
591 //throw new NotImplementedException();
594 [EditorBrowsable(EditorBrowsableState.Advanced)]
595 protected override void WndProc(ref Message m) {
598 #endregion // Protected Instance Methods
600 #region Internal & Private Methods
601 private void CalculateCanvasSize() {
609 num_of_children = Controls.Count;
612 extra_width = dock_padding.Right;
613 extra_height = dock_padding.Bottom;
615 for (int i = 0; i < num_of_children; i++) {
617 if (child.Dock == DockStyle.Right) {
618 extra_width += child.Width;
619 } else if (child.Dock == DockStyle.Bottom) {
620 extra_height += child.Height;
624 if (!auto_scroll_min_size.IsEmpty) {
625 width = auto_scroll_min_size.Width;
626 height = auto_scroll_min_size.Height;
629 for (int i = 0; i < num_of_children; i++) {
633 case DockStyle.Left: {
634 if ((child.Right + extra_width) > width) {
635 width = child.Right + extra_width;
640 case DockStyle.Top: {
641 if ((child.Bottom + extra_height) > height) {
642 height = child.Bottom + extra_height;
648 case DockStyle.Right:
649 case DockStyle.Bottom: {
656 anchor = child.Anchor;
658 if (((anchor & AnchorStyles.Left) != 0) && ((anchor & AnchorStyles.Right) == 0)) {
659 if ((child.Right + extra_width) > width) {
660 width = child.Right + extra_width;
664 if (((anchor & AnchorStyles.Top) != 0) || ((anchor & AnchorStyles.Bottom) == 0)) {
665 if ((child.Bottom + extra_height) > height) {
666 height = child.Bottom + extra_height;
673 width += scroll_position.X;
674 height += scroll_position.Y;
676 canvas_size.Width = width;
677 canvas_size.Height = height;
680 private void Recalculate (object sender, EventArgs e) {
681 if (!auto_scroll && !force_hscroll_visible && !force_vscroll_visible) {
685 Size canvas = canvas_size;
686 Size client = ClientSize;
688 canvas.Width += auto_scroll_margin.Width;
689 canvas.Height += auto_scroll_margin.Height;
691 int right_edge = client.Width;
692 int bottom_edge = client.Height;
694 int prev_bottom_edge;
697 prev_right_edge = right_edge;
698 prev_bottom_edge = bottom_edge;
700 if ((force_hscroll_visible || canvas.Width > right_edge) && client.Width > 0) {
701 hscroll_visible = true;
702 bottom_edge = client.Height - SystemInformation.HorizontalScrollBarHeight;
704 hscroll_visible = false;
705 bottom_edge = client.Height;
708 if ((force_vscroll_visible || canvas.Height > bottom_edge) && client.Height > 0) {
709 vscroll_visible = true;
710 right_edge = client.Width - SystemInformation.VerticalScrollBarWidth;
712 vscroll_visible = false;
713 right_edge = client.Width;
716 } while (right_edge != prev_right_edge || bottom_edge != prev_bottom_edge);
718 if (right_edge < 0) right_edge = 0;
719 if (bottom_edge < 0) bottom_edge = 0;
721 Rectangle hscroll_bounds;
722 Rectangle vscroll_bounds;
724 hscroll_bounds = new Rectangle (0, client.Height - SystemInformation.HorizontalScrollBarHeight,
725 ClientRectangle.Width, SystemInformation.HorizontalScrollBarHeight);
726 vscroll_bounds = new Rectangle (client.Width - SystemInformation.VerticalScrollBarWidth, 0,
727 SystemInformation.VerticalScrollBarWidth, ClientRectangle.Height);
729 /* the ScrollWindow calls here are needed
730 * because (this explanation sucks):
732 * when we transition from having a scrollbar to
733 * not having one, we won't receive a scrollbar
734 * moved (value changed) event, so we need to
735 * manually scroll the canvas.
737 * if you can fix this without requiring the
738 * ScrollWindow calls, pdb and toshok will each
741 if (hscroll_visible) {
742 hscrollbar.LargeChange = right_edge;
743 hscrollbar.SmallChange = 5;
744 hscrollbar.Maximum = canvas.Width - 1;
746 if (hscrollbar.Visible) {
747 ScrollWindow (- scroll_position.X, 0);
749 scroll_position.X = 0;
752 if (vscroll_visible) {
753 vscrollbar.LargeChange = bottom_edge;
754 vscrollbar.SmallChange = 5;
755 vscrollbar.Maximum = canvas.Height - 1;
757 if (vscrollbar.Visible) {
758 ScrollWindow (0, - scroll_position.Y);
760 scroll_position.Y = 0;
763 if (hscroll_visible && vscroll_visible) {
764 hscroll_bounds.Width -= SystemInformation.VerticalScrollBarWidth;
765 vscroll_bounds.Height -= SystemInformation.HorizontalScrollBarHeight;
767 sizegrip.Bounds = new Rectangle (hscroll_bounds.Right,
768 vscroll_bounds.Bottom,
769 SystemInformation.VerticalScrollBarWidth,
770 SystemInformation.HorizontalScrollBarHeight);
773 hscrollbar.Bounds = hscroll_bounds;
774 vscrollbar.Bounds = vscroll_bounds;
775 hscrollbar.Visible = hscroll_visible;
776 vscrollbar.Visible = vscroll_visible;
777 sizegrip.Visible = hscroll_visible && vscroll_visible;
780 private void HandleScrollBar(object sender, EventArgs e) {
781 if (sender == vscrollbar) {
782 ScrollWindow(0, vscrollbar.Value- scroll_position.Y);
784 ScrollWindow(hscrollbar.Value - scroll_position.X, 0);
788 private void ScrollWindow(int XOffset, int YOffset) {
791 if (XOffset == 0 && YOffset == 0) {
797 num_of_children = Controls.Count;
799 for (int i = 0; i < num_of_children; i++) {
800 Controls[i].Left -= XOffset;
801 Controls[i].Top -= YOffset;
802 // Is this faster? Controls[i].Location -= new Size(XOffset, YOffset);
805 scroll_position.X += XOffset;
806 scroll_position.Y += YOffset;
808 // Should we call XplatUI.ScrollWindow??? If so, we need to position our windows by other means above
809 // Since we're already causing a redraw above
813 #endregion // Internal & Private Methods
816 static object OnScrollEvent = new object ();
818 protected virtual void OnScroll (ScrollEventArgs se)
820 EventHandler eh = (EventHandler) (Events [OnScrollEvent]);
825 protected override void OnPaintBackground (PaintEventArgs e)
827 base.OnPaintBackground (e);
830 protected override void OnRightToLeftChanged (EventArgs e)
832 base.OnRightToLeftChanged (e);
835 public event ScrollEventHandler Scroll {
836 add { Events.AddHandler (OnScrollEvent, value); }
837 remove { Events.RemoveHandler (OnScrollEvent, value); }