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 auto_vscroll;
39 private bool auto_hscroll;
40 private bool hscroll_visible;
41 private bool vscroll_visible;
42 private bool force_hscroll_visible;
43 private bool force_vscroll_visible;
44 private bool auto_scroll;
45 private Size auto_scroll_margin;
46 private Size auto_scroll_min_size;
47 private Point scroll_position;
48 private DockPaddingEdges dock_padding;
49 private SizeGrip sizegrip;
50 private ImplicitHScrollBar hscrollbar;
51 private ImplicitVScrollBar vscrollbar;
52 private Size canvas_size;
53 private Rectangle display_rectangle;
54 #endregion // Local Variables
56 [TypeConverter(typeof(ScrollableControl.DockPaddingEdgesConverter))]
57 #region Subclass DockPaddingEdges
58 public class DockPaddingEdges : ICloneable {
59 #region DockPaddingEdges Local Variables
65 private Control owner;
66 #endregion // DockPaddingEdges Local Variables
68 #region DockPaddingEdges Constructor
69 internal DockPaddingEdges(Control owner) {
77 #endregion // DockPaddingEdges Constructor
79 #region DockPaddingEdges Public Instance Properties
80 [RefreshProperties(RefreshProperties.All)]
93 owner.PerformLayout();
97 [RefreshProperties(RefreshProperties.All)]
107 owner.PerformLayout();
111 [RefreshProperties(RefreshProperties.All)]
121 owner.PerformLayout();
125 [RefreshProperties(RefreshProperties.All)]
135 owner.PerformLayout();
139 [RefreshProperties(RefreshProperties.All)]
149 owner.PerformLayout();
152 #endregion // DockPaddingEdges Public Instance Properties
154 // Public Instance Methods
155 public override bool Equals(object other) {
156 if (! (other is DockPaddingEdges)) {
160 if ( (this.all == ((DockPaddingEdges)other).all) && (this.left == ((DockPaddingEdges)other).left) &&
161 (this.right == ((DockPaddingEdges)other).right) && (this.top == ((DockPaddingEdges)other).top) &&
162 (this.bottom == ((DockPaddingEdges)other).bottom)) {
169 public override int GetHashCode() {
170 return all*top*bottom*right*left;
173 public override string ToString() {
174 return "All = "+all.ToString()+" Top = "+top.ToString()+" Left = "+left.ToString()+" Bottom = "+bottom.ToString()+" Right = "+right.ToString();
177 internal void Scale(float dx, float dy) {
178 left = (int) (left * dx);
179 right = (int) (right * dx);
180 top = (int) (top * dy);
181 bottom = (int) (bottom * dy);
184 object ICloneable.Clone() {
185 DockPaddingEdges padding_edge;
187 padding_edge=new DockPaddingEdges(owner);
189 padding_edge.all=all;
190 padding_edge.left=left;
191 padding_edge.right=right;
192 padding_edge.top=top;
193 padding_edge.bottom=bottom;
198 #endregion // Subclass DockPaddingEdges
200 #region Subclass DockPaddingEdgesConverter
201 public class DockPaddingEdgesConverter : System.ComponentModel.TypeConverter {
202 // Public Constructors
203 public DockPaddingEdgesConverter() {
206 // Public Instance Methods
207 public override PropertyDescriptorCollection GetProperties(System.ComponentModel.ITypeDescriptorContext context, object value, Attribute[] attributes) {
208 return TypeDescriptor.GetProperties(typeof(DockPaddingEdges), attributes);
211 public override bool GetPropertiesSupported(System.ComponentModel.ITypeDescriptorContext context) {
215 #endregion // Subclass DockPaddingEdgesConverter
217 #region Public Constructors
218 public ScrollableControl() {
219 SetStyle(ControlStyles.ContainerControl, true);
220 SetStyle(ControlStyles.AllPaintingInWmPaint, false);
222 auto_hscroll = false;
223 auto_vscroll = false;
224 hscroll_visible = false;
225 vscroll_visible = false;
226 force_hscroll_visible = false;
227 force_vscroll_visible = false;
228 auto_scroll_margin = new Size(0, 0);
229 auto_scroll_min_size = new Size(0, 0);
230 scroll_position = new Point(0, 0);
231 dock_padding = new DockPaddingEdges(this);
232 SizeChanged +=new EventHandler(Recalculate);
233 VisibleChanged += new EventHandler(Recalculate);
235 #endregion // Public Constructors
237 #region Protected Static Fields
238 protected const int ScrollStateAutoScrolling = 1;
239 protected const int ScrollStateFullDrag = 16;
240 protected const int ScrollStateHScrollVisible = 2;
241 protected const int ScrollStateUserHasScrolled = 8;
242 protected const int ScrollStateVScrollVisible = 4;
243 #endregion // Protected Static Fields
245 #region Public Instance Properties
246 [DefaultValue(false)]
248 [MWFCategory("Layout")]
249 public virtual bool AutoScroll {
255 if (auto_scroll == value) {
263 Controls.RemoveImplicit (hscrollbar);
264 hscrollbar.Dispose();
266 hscroll_visible = false;
268 Controls.RemoveImplicit (vscrollbar);
269 vscrollbar.Dispose();
271 vscroll_visible = false;
273 Controls.RemoveImplicit (sizegrip);
281 hscrollbar = new ImplicitHScrollBar();
282 hscrollbar.Visible = false;
283 hscrollbar.ValueChanged += new EventHandler(HandleScrollBar);
284 hscrollbar.Height = SystemInformation.HorizontalScrollBarHeight;
285 this.Controls.AddImplicit (hscrollbar);
287 vscrollbar = new ImplicitVScrollBar();
288 vscrollbar.Visible = false;
289 vscrollbar.ValueChanged += new EventHandler(HandleScrollBar);
290 vscrollbar.Width = SystemInformation.VerticalScrollBarWidth;
291 this.Controls.AddImplicit (vscrollbar);
293 sizegrip = new SizeGrip();
294 sizegrip.Visible = false;
295 this.Controls.AddImplicit (sizegrip);
303 [MWFCategory("Layout")]
304 public Size AutoScrollMargin {
306 return auto_scroll_margin;
310 if (value.Width < 0) {
311 throw new ArgumentException("Width is assigned less than 0", "value.Width");
314 if (value.Height < 0) {
315 throw new ArgumentException("Height is assigned less than 0", "value.Height");
318 auto_scroll_margin = value;
323 [MWFCategory("Layout")]
324 public Size AutoScrollMinSize {
326 return auto_scroll_min_size;
330 auto_scroll_min_size = value;
336 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
337 public Point AutoScrollPosition {
339 return new Point(-scroll_position.X, -scroll_position.Y);
343 if ((value.X != scroll_position.X) || (value.Y != scroll_position.Y)) {
349 if (hscroll_visible) {
350 shift_x = value.X - scroll_position.X;
353 if (vscroll_visible) {
354 shift_y = value.Y - scroll_position.Y;
357 ScrollWindow(shift_x, shift_y);
359 if (hscroll_visible) {
360 hscrollbar.Value = scroll_position.X;
363 if (vscroll_visible) {
364 vscrollbar.Value = scroll_position.Y;
371 public override Rectangle DisplayRectangle {
377 return base.DisplayRectangle;
380 if (canvas_size.Width <= base.DisplayRectangle.Width) {
381 width = base.DisplayRectangle.Width;
382 if (vscroll_visible) {
383 width -= vscrollbar.Width;
386 width = canvas_size.Width;
389 if (canvas_size.Height <= base.DisplayRectangle.Height) {
390 height = base.DisplayRectangle.Height;
391 if (hscroll_visible) {
392 height -= hscrollbar.Height;
395 height = canvas_size.Height;
398 display_rectangle.X = -scroll_position.X + dock_padding.Left;
399 display_rectangle.Y = -scroll_position.Y + dock_padding.Top;
400 display_rectangle.Width = Math.Max(auto_scroll_min_size.Width, width) - dock_padding.Left - dock_padding.Right;
401 display_rectangle.Height = Math.Max(auto_scroll_min_size.Height, height) - dock_padding.Top - dock_padding.Bottom;
403 return display_rectangle;
407 [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
409 [MWFCategory("Layout")]
410 public DockPaddingEdges DockPadding {
415 #endregion // Public Instance Properties
417 #region Protected Instance Methods
418 protected override CreateParams CreateParams {
420 return base.CreateParams;
424 protected bool HScroll {
426 return hscroll_visible;
430 if (hscroll_visible != value) {
431 force_hscroll_visible = value;
432 Recalculate(this, EventArgs.Empty);
437 protected bool VScroll {
439 return vscroll_visible;
443 if (vscroll_visible != value) {
444 force_vscroll_visible = value;
445 Recalculate(this, EventArgs.Empty);
449 #endregion // Protected Instance Methods
451 #region Public Instance Methods
452 public void ScrollControlIntoView(Control activeControl) {
458 if (!AutoScroll || (!hscroll_visible && !vscroll_visible)) {
462 if (!Contains(activeControl)) {
466 x = activeControl.Left;
467 y = activeControl.Top;
469 // Translate into coords relative to us
470 if (activeControl.parent != this) {
471 activeControl.PointToScreen(ref x, ref y);
472 PointToClient(ref x, ref y);
475 x += scroll_position.X;
476 y += scroll_position.Y;
478 // Don't scroll if already visible
479 if ((activeControl.Left >= scroll_position.X) && (activeControl.Left < (scroll_position.X + client_size.Width)) &&
480 (activeControl.Top >= scroll_position.Y) && (activeControl.Top < (scroll_position.Y + client_size.Height))) {
485 corner_x = Math.Max(0, x + activeControl.Width / 2 - client_size.Width / 2);
486 corner_y = Math.Max(0, y + activeControl.Height / 2- client_size.Height / 2);
488 if (hscroll_visible && (corner_x > hscrollbar.Maximum)) {
489 corner_x = Math.Max(0, hscrollbar.Maximum - client_size.Width);
492 if (vscroll_visible && (corner_y > vscrollbar.Maximum)) {
493 corner_y = Math.Max(0, vscrollbar.Maximum - client_size.Height);
495 if ((corner_x == scroll_position.X) && (corner_y == scroll_position.Y)) {
499 //this.SetDisplayRectLocation(-corner_x, -corner_y);
500 hscrollbar.Value = corner_x;
501 vscrollbar.Value = corner_y;
504 public void SetAutoScrollMargin(int x, int y) {
513 auto_scroll_margin = new Size(x, y);
514 Recalculate(this, EventArgs.Empty);
516 #endregion // Public Instance Methods
518 #region Protected Instance Methods
519 [EditorBrowsable(EditorBrowsableState.Advanced)]
520 protected virtual void AdjustFormScrollbars(bool displayScrollbars) {
521 Recalculate(this, EventArgs.Empty);
524 [EditorBrowsable(EditorBrowsableState.Advanced)]
525 protected bool GetScrollState(int bit) {
530 [EditorBrowsable(EditorBrowsableState.Advanced)]
531 protected override void OnLayout(LayoutEventArgs levent) {
532 CalculateCanvasSize();
534 AdjustFormScrollbars(AutoScroll); // Dunno what the logic is. Passing AutoScroll seems to match MS behaviour
535 base.OnLayout(levent);
538 [EditorBrowsable(EditorBrowsableState.Advanced)]
539 protected override void OnMouseWheel(MouseEventArgs e) {
540 if (vscroll_visible) {
542 if (vscrollbar.Minimum < (vscrollbar.Value - vscrollbar.LargeChange)) {
543 vscrollbar.Value -= vscrollbar.LargeChange;
545 vscrollbar.Value = vscrollbar.Minimum;
548 if (vscrollbar.Maximum > (vscrollbar.Value + vscrollbar.LargeChange)) {
549 vscrollbar.Value += vscrollbar.LargeChange;
551 vscrollbar.Value = vscrollbar.Maximum;
555 base.OnMouseWheel(e);
558 [EditorBrowsable(EditorBrowsableState.Advanced)]
559 protected override void OnVisibleChanged(EventArgs e) {
563 base.OnVisibleChanged(e);
566 protected override void ScaleCore(float dx, float dy) {
567 dock_padding.Scale(dx, dy);
568 base.ScaleCore(dx, dy);
571 protected void SetDisplayRectLocation(int x, int y) {
572 // This method is weird. MS documents that the scrollbars are not
573 // updated. We need to move stuff, but leave the scrollbars as is
583 ScrollWindow(scroll_position.X - x , scroll_position.Y - y);
586 protected void SetScrollState(int bit, bool value) {
587 //throw new NotImplementedException();
590 [EditorBrowsable(EditorBrowsableState.Advanced)]
591 protected override void WndProc(ref Message m) {
594 #endregion // Protected Instance Methods
596 #region Internal & Private Methods
597 private Size Canvas {
599 if (!canvas_size.IsEmpty) {
602 CalculateCanvasSize();
607 private void CalculateCanvasSize() {
615 num_of_children = child_controls.Count;
618 extra_width = dock_padding.Right;
619 extra_height = dock_padding.Bottom;
621 for (int i = 0; i < num_of_children; i++) {
622 child = child_controls[i];
623 if (child.Dock == DockStyle.Right) {
624 extra_width += child.Width;
625 } else if (child.Dock == DockStyle.Bottom) {
626 extra_height += child.Height;
630 if (!auto_scroll_min_size.IsEmpty) {
631 width = auto_scroll_min_size.Width;
632 height = auto_scroll_min_size.Height;
635 for (int i = 0; i < num_of_children; i++) {
636 child = child_controls[i];
639 case DockStyle.Left: {
640 if ((child.Right + extra_width) > width) {
641 width = child.Right + extra_width;
646 case DockStyle.Top: {
647 if ((child.Bottom + extra_height) > height) {
648 height = child.Bottom + extra_height;
654 case DockStyle.Right:
655 case DockStyle.Bottom: {
662 anchor = child.Anchor;
664 if (((anchor & AnchorStyles.Left) != 0) && ((anchor & AnchorStyles.Right) == 0)) {
665 if ((child.Right + extra_width) > width) {
666 width = child.Right + extra_width;
670 if (((anchor & AnchorStyles.Top) != 0) || ((anchor & AnchorStyles.Bottom) == 0)) {
671 if ((child.Bottom + extra_height) > height) {
672 height = child.Bottom + extra_height;
679 width += scroll_position.X;
680 height += scroll_position.Y;
682 canvas_size.Width = width;
683 canvas_size.Height = height;
686 private void Recalculate (object sender, EventArgs e) {
687 if (!auto_scroll && !force_hscroll_visible && !force_vscroll_visible) {
691 Size canvas = canvas_size;
692 Size client = ClientSize;
694 canvas.Width += auto_scroll_margin.Width;
695 canvas.Height += auto_scroll_margin.Height;
697 int right_edge = client.Width;
698 int bottom_edge = client.Height;
700 int prev_bottom_edge;
703 prev_right_edge = right_edge;
704 prev_bottom_edge = bottom_edge;
706 if ((force_hscroll_visible || canvas.Width > right_edge) && client.Width > 0) {
707 hscroll_visible = true;
708 bottom_edge = client.Height - SystemInformation.HorizontalScrollBarHeight;
710 hscroll_visible = false;
711 bottom_edge = client.Height;
714 if ((force_vscroll_visible || canvas.Height > bottom_edge) && client.Height > 0) {
715 vscroll_visible = true;
716 right_edge = client.Width - SystemInformation.VerticalScrollBarWidth;
718 vscroll_visible = false;
719 right_edge = client.Width;
722 } while (right_edge != prev_right_edge || bottom_edge != prev_bottom_edge);
724 if (right_edge < 0) right_edge = 0;
725 if (bottom_edge < 0) bottom_edge = 0;
727 Rectangle hscroll_bounds;
728 Rectangle vscroll_bounds;
730 hscroll_bounds = new Rectangle (0, client.Height - SystemInformation.HorizontalScrollBarHeight,
731 ClientRectangle.Width, SystemInformation.HorizontalScrollBarHeight);
732 vscroll_bounds = new Rectangle (client.Width - SystemInformation.VerticalScrollBarWidth, 0,
733 SystemInformation.VerticalScrollBarWidth, ClientRectangle.Height);
735 /* the ScrollWindow calls here are needed
736 * because (this explanation sucks):
738 * when we transition from having a scrollbar to
739 * not having one, we won't receive a scrollbar
740 * moved (value changed) event, so we need to
741 * manually scroll the canvas.
743 * if you can fix this without requiring the
744 * ScrollWindow calls, pdb and toshok will each
747 if (hscroll_visible) {
748 hscrollbar.LargeChange = right_edge;
749 hscrollbar.SmallChange = 5;
750 hscrollbar.Maximum = canvas.Width - 1;
752 if (hscrollbar.Visible) {
753 ScrollWindow (- scroll_position.X, 0);
755 scroll_position.X = 0;
758 if (vscroll_visible) {
759 vscrollbar.LargeChange = bottom_edge;
760 vscrollbar.SmallChange = 5;
761 vscrollbar.Maximum = canvas.Height - 1;
763 if (vscrollbar.Visible) {
764 ScrollWindow (0, - scroll_position.Y);
766 scroll_position.Y = 0;
769 if (hscroll_visible && vscroll_visible) {
770 hscroll_bounds.Width -= SystemInformation.VerticalScrollBarWidth;
771 vscroll_bounds.Height -= SystemInformation.HorizontalScrollBarHeight;
773 sizegrip.Bounds = new Rectangle (hscroll_bounds.Right,
774 vscroll_bounds.Bottom,
775 SystemInformation.VerticalScrollBarWidth,
776 SystemInformation.HorizontalScrollBarHeight);
779 hscrollbar.Bounds = hscroll_bounds;
780 vscrollbar.Bounds = vscroll_bounds;
781 hscrollbar.Visible = hscroll_visible;
782 vscrollbar.Visible = vscroll_visible;
783 sizegrip.Visible = hscroll_visible && vscroll_visible;
786 private void HandleScrollBar(object sender, EventArgs e) {
787 if (sender == vscrollbar) {
788 ScrollWindow(0, vscrollbar.Value- scroll_position.Y);
790 ScrollWindow(hscrollbar.Value - scroll_position.X, 0);
794 private void ScrollWindow(int XOffset, int YOffset) {
797 if (XOffset == 0 && YOffset == 0) {
803 num_of_children = child_controls.Count;
805 for (int i = 0; i < num_of_children; i++) {
806 child_controls[i].Left -= XOffset;
807 child_controls[i].Top -= YOffset;
808 // Is this faster? child_controls[i].Location -= new Size(XOffset, YOffset);
811 scroll_position.X += XOffset;
812 scroll_position.Y += YOffset;
814 // Should we call XplatUI.ScrollWindow??? If so, we need to position our windows by other means above
815 // Since we're already causing a redraw above
819 #endregion // Internal & Private Methods