123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- using System;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Controls.Primitives;
- using System.Windows.Media;
- using System.Windows.Media.Animation;
- namespace MediaBrowser.UI.Controls
- {
- /// <summary>
- /// This started from:
- /// http://www.switchonthecode.com/tutorials/wpf-tutorial-implementing-iscrollinfo
- /// Then, after implementing this, content was being displayed in stack panel like manner.
- /// I then reviewed the source code of ScrollContentPresenter and updated MeasureOverride and ArrangeOverride to match.
- /// </summary>
- public class ScrollingPanel : Grid, IScrollInfo
- {
- /// <summary>
- /// The infinite size
- /// </summary>
- private static Size InfiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
- /// <summary>
- /// The line size
- /// </summary>
- private const double LineSize = 16;
- /// <summary>
- /// The wheel size
- /// </summary>
- private const double WheelSize = 3 * LineSize;
- /// <summary>
- /// The _ offset
- /// </summary>
- private Vector _Offset;
- /// <summary>
- /// The _ extent
- /// </summary>
- private Size _Extent;
- /// <summary>
- /// The _ viewport
- /// </summary>
- private Size _Viewport;
- /// <summary>
- /// The _ animation length
- /// </summary>
- private TimeSpan _AnimationLength = TimeSpan.FromMilliseconds(125);
- /// <summary>
- /// When overridden in a derived class, measures the size in layout required for child elements and determines a size for the <see cref="T:System.Windows.FrameworkElement" />-derived class.
- /// </summary>
- /// <param name="availableSize">The available size that this element can give to child elements. Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
- /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
- protected override Size MeasureOverride(Size availableSize)
- {
- if (Children == null || Children.Count == 0)
- {
- return availableSize;
- }
- var constraint2 = availableSize;
- if (CanHorizontallyScroll)
- {
- constraint2.Width = double.PositiveInfinity;
- }
- if (CanVerticallyScroll)
- {
- constraint2.Height = double.PositiveInfinity;
- }
- var uiElement = Children[0];
- uiElement.Measure(constraint2);
- var size = uiElement.DesiredSize;
- VerifyScrollData(availableSize, size);
- size.Width = Math.Min(availableSize.Width, size.Width);
- size.Height = Math.Min(availableSize.Height, size.Height);
- return size;
- }
- /// <summary>
- /// Arranges the content of a <see cref="T:System.Windows.Controls.Grid" /> element.
- /// </summary>
- /// <param name="arrangeSize">Specifies the size this <see cref="T:System.Windows.Controls.Grid" /> element should use to arrange its child elements.</param>
- /// <returns><see cref="T:System.Windows.Size" /> that represents the arranged size of this Grid element and its children.</returns>
- protected override Size ArrangeOverride(Size arrangeSize)
- {
- this.VerifyScrollData(arrangeSize, _Extent);
- if (this.Children == null || this.Children.Count == 0)
- {
- return arrangeSize;
- }
- TranslateTransform trans = null;
- var uiElement = Children[0];
- var finalRect = new Rect(uiElement.DesiredSize);
- // ScrollContentPresenter sets these to 0 - current offset
- // We need to set it to zero in order to make the animation work
- finalRect.X = 0;
- finalRect.Y = 0;
- finalRect.Width = Math.Max(finalRect.Width, arrangeSize.Width);
- finalRect.Height = Math.Max(finalRect.Height, arrangeSize.Height);
- trans = uiElement.RenderTransform as TranslateTransform;
- if (trans == null)
- {
- uiElement.RenderTransformOrigin = new Point(0, 0);
- trans = new TranslateTransform();
- uiElement.RenderTransform = trans;
- }
- uiElement.Arrange(finalRect);
- trans.BeginAnimation(TranslateTransform.XProperty,
- GetAnimation(0 - HorizontalOffset),
- HandoffBehavior.Compose);
- trans.BeginAnimation(TranslateTransform.YProperty,
- GetAnimation(0 - VerticalOffset),
- HandoffBehavior.Compose);
- return arrangeSize;
- }
- /// <summary>
- /// Gets the animation.
- /// </summary>
- /// <param name="toValue">To value.</param>
- /// <returns>DoubleAnimation.</returns>
- private DoubleAnimation GetAnimation(double toValue)
- {
- var animation = new DoubleAnimation(toValue, _AnimationLength);
- animation.EasingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut };
- return animation;
- }
- #region Movement Methods
- /// <summary>
- /// Scrolls down within content by one logical unit.
- /// </summary>
- public void LineDown()
- { SetVerticalOffset(VerticalOffset + LineSize); }
- /// <summary>
- /// Scrolls up within content by one logical unit.
- /// </summary>
- public void LineUp()
- { SetVerticalOffset(VerticalOffset - LineSize); }
- /// <summary>
- /// Scrolls left within content by one logical unit.
- /// </summary>
- public void LineLeft()
- { SetHorizontalOffset(HorizontalOffset - LineSize); }
- /// <summary>
- /// Scrolls right within content by one logical unit.
- /// </summary>
- public void LineRight()
- { SetHorizontalOffset(HorizontalOffset + LineSize); }
- /// <summary>
- /// Scrolls down within content after a user clicks the wheel button on a mouse.
- /// </summary>
- public void MouseWheelDown()
- { SetVerticalOffset(VerticalOffset + WheelSize); }
- /// <summary>
- /// Scrolls up within content after a user clicks the wheel button on a mouse.
- /// </summary>
- public void MouseWheelUp()
- { SetVerticalOffset(VerticalOffset - WheelSize); }
- /// <summary>
- /// Scrolls left within content after a user clicks the wheel button on a mouse.
- /// </summary>
- public void MouseWheelLeft()
- { SetHorizontalOffset(HorizontalOffset - WheelSize); }
- /// <summary>
- /// Scrolls right within content after a user clicks the wheel button on a mouse.
- /// </summary>
- public void MouseWheelRight()
- { SetHorizontalOffset(HorizontalOffset + WheelSize); }
- /// <summary>
- /// Scrolls down within content by one page.
- /// </summary>
- public void PageDown()
- { SetVerticalOffset(VerticalOffset + ViewportHeight); }
- /// <summary>
- /// Scrolls up within content by one page.
- /// </summary>
- public void PageUp()
- { SetVerticalOffset(VerticalOffset - ViewportHeight); }
- /// <summary>
- /// Scrolls left within content by one page.
- /// </summary>
- public void PageLeft()
- { SetHorizontalOffset(HorizontalOffset - ViewportWidth); }
- /// <summary>
- /// Scrolls right within content by one page.
- /// </summary>
- public void PageRight()
- { SetHorizontalOffset(HorizontalOffset + ViewportWidth); }
- #endregion
- /// <summary>
- /// Gets or sets a <see cref="T:System.Windows.Controls.ScrollViewer" /> element that controls scrolling behavior.
- /// </summary>
- /// <value>The scroll owner.</value>
- /// <returns>A <see cref="T:System.Windows.Controls.ScrollViewer" /> element that controls scrolling behavior. This property has no default value.</returns>
- public ScrollViewer ScrollOwner { get; set; }
- /// <summary>
- /// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible.
- /// </summary>
- /// <value><c>true</c> if this instance can horizontally scroll; otherwise, <c>false</c>.</value>
- /// <returns>true if scrolling is possible; otherwise, false. This property has no default value.</returns>
- public bool CanHorizontallyScroll { get; set; }
- /// <summary>
- /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible.
- /// </summary>
- /// <value><c>true</c> if this instance can vertically scroll; otherwise, <c>false</c>.</value>
- /// <returns>true if scrolling is possible; otherwise, false. This property has no default value.</returns>
- public bool CanVerticallyScroll { get; set; }
- /// <summary>
- /// Gets the vertical size of the extent.
- /// </summary>
- /// <value>The height of the extent.</value>
- /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical size of the extent.This property has no default value.</returns>
- public double ExtentHeight
- { get { return _Extent.Height; } }
- /// <summary>
- /// Gets the horizontal size of the extent.
- /// </summary>
- /// <value>The width of the extent.</value>
- /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal size of the extent. This property has no default value.</returns>
- public double ExtentWidth
- { get { return _Extent.Width; } }
- /// <summary>
- /// Gets the horizontal offset of the scrolled content.
- /// </summary>
- /// <value>The horizontal offset.</value>
- /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal offset. This property has no default value.</returns>
- public double HorizontalOffset
- { get { return _Offset.X; } }
- /// <summary>
- /// Gets the vertical offset of the scrolled content.
- /// </summary>
- /// <value>The vertical offset.</value>
- /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical offset of the scrolled content. Valid values are between zero and the <see cref="P:System.Windows.Controls.Primitives.IScrollInfo.ExtentHeight" /> minus the <see cref="P:System.Windows.Controls.Primitives.IScrollInfo.ViewportHeight" />. This property has no default value.</returns>
- public double VerticalOffset
- { get { return _Offset.Y; } }
- /// <summary>
- /// Gets the vertical size of the viewport for this content.
- /// </summary>
- /// <value>The height of the viewport.</value>
- /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the vertical size of the viewport for this content. This property has no default value.</returns>
- public double ViewportHeight
- { get { return _Viewport.Height; } }
- /// <summary>
- /// Gets the horizontal size of the viewport for this content.
- /// </summary>
- /// <value>The width of the viewport.</value>
- /// <returns>A <see cref="T:System.Double" /> that represents, in device independent pixels, the horizontal size of the viewport for this content. This property has no default value.</returns>
- public double ViewportWidth
- { get { return _Viewport.Width; } }
- /// <summary>
- /// Forces content to scroll until the coordinate space of a <see cref="T:System.Windows.Media.Visual" /> object is visible.
- /// </summary>
- /// <param name="visual">A <see cref="T:System.Windows.Media.Visual" /> that becomes visible.</param>
- /// <param name="rectangle">A bounding rectangle that identifies the coordinate space to make visible.</param>
- /// <returns>A <see cref="T:System.Windows.Rect" /> that is visible.</returns>
- public Rect MakeVisible(Visual visual, Rect rectangle)
- {
- if (rectangle.IsEmpty || visual == null
- || visual == this || !base.IsAncestorOf(visual))
- { return Rect.Empty; }
- rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);
- //rectangle.Inflate(50, 50);
- rectangle.Scale(1.2, 1.2);
- Rect viewRect = new Rect(HorizontalOffset,
- VerticalOffset, ViewportWidth, ViewportHeight);
- rectangle.X += viewRect.X;
- rectangle.Y += viewRect.Y;
- viewRect.X = CalculateNewScrollOffset(viewRect.Left,
- viewRect.Right, rectangle.Left, rectangle.Right);
- viewRect.Y = CalculateNewScrollOffset(viewRect.Top,
- viewRect.Bottom, rectangle.Top, rectangle.Bottom);
- SetHorizontalOffset(viewRect.X);
- SetVerticalOffset(viewRect.Y);
- rectangle.Intersect(viewRect);
- rectangle.X -= viewRect.X;
- rectangle.Y -= viewRect.Y;
- return rectangle;
- }
- /// <summary>
- /// Calculates the new scroll offset.
- /// </summary>
- /// <param name="topView">The top view.</param>
- /// <param name="bottomView">The bottom view.</param>
- /// <param name="topChild">The top child.</param>
- /// <param name="bottomChild">The bottom child.</param>
- /// <returns>System.Double.</returns>
- private static double CalculateNewScrollOffset(double topView,
- double bottomView, double topChild, double bottomChild)
- {
- bool offBottom = topChild < topView && bottomChild < bottomView;
- bool offTop = bottomChild > bottomView && topChild > topView;
- bool tooLarge = (bottomChild - topChild) > (bottomView - topView);
- if (!offBottom && !offTop)
- { return topView; } //Don't do anything, already in view
- if ((offBottom && !tooLarge) || (offTop && tooLarge))
- { return topChild; }
- return (bottomChild - (bottomView - topView));
- }
- /// <summary>
- /// Verifies the scroll data.
- /// </summary>
- /// <param name="viewport">The viewport.</param>
- /// <param name="extent">The extent.</param>
- protected void VerifyScrollData(Size viewport, Size extent)
- {
- if (double.IsInfinity(viewport.Width))
- { viewport.Width = extent.Width; }
- if (double.IsInfinity(viewport.Height))
- { viewport.Height = extent.Height; }
- _Extent = extent;
- _Viewport = viewport;
- _Offset.X = Math.Max(0,
- Math.Min(_Offset.X, ExtentWidth - ViewportWidth));
- _Offset.Y = Math.Max(0,
- Math.Min(_Offset.Y, ExtentHeight - ViewportHeight));
- if (ScrollOwner != null)
- { ScrollOwner.InvalidateScrollInfo(); }
- }
- /// <summary>
- /// Sets the amount of horizontal offset.
- /// </summary>
- /// <param name="offset">The degree to which content is horizontally offset from the containing viewport.</param>
- public void SetHorizontalOffset(double offset)
- {
- offset = Math.Max(0,
- Math.Min(offset, ExtentWidth - ViewportWidth));
- if (!offset.Equals(_Offset.X))
- {
- _Offset.X = offset;
- InvalidateArrange();
- }
- }
- /// <summary>
- /// Sets the amount of vertical offset.
- /// </summary>
- /// <param name="offset">The degree to which content is vertically offset from the containing viewport.</param>
- public void SetVerticalOffset(double offset)
- {
- offset = Math.Max(0,
- Math.Min(offset, ExtentHeight - ViewportHeight));
- if (!offset.Equals(_Offset.Y))
- {
- _Offset.Y = offset;
- InvalidateArrange();
- }
- }
- }
- }
|