내용 보기
작성자
관리자 (IP : 172.17.0.1)
날짜
2020-07-10 03:49
제목
[WPF] 특정 컨트롤 스크롤 고정 Panel
Scroll Freeze Panel in WPF using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Media; namespace Iimaginec.Lib.Controls.Panels { public enum ScrollBehaviours { Scrollable = 0, NonScrollable, AlwaysVisible } public class BooleanToAlwaysVisibleScrollBehaviour : IValueConverter { public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var b = ( bool )value; return b ? ScrollBehaviours.AlwaysVisible : ScrollBehaviours.Scrollable; } public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return (ScrollBehaviours)value == ScrollBehaviours.AlwaysVisible; } } /// <summary> /// ScrollFreezePanel arranges elements similar to a StackPanel, but has special /// behaviour when attached to a ScrollViewer. To use this control, it must be /// nested in a ScrollViewer with CanContentScroll set to true. /// /// Children of this panel can request special scrolling behaviour via the /// ScrollBehaviour attached property: /// /// Scrollable - default behaviour /// /// NonScrollable - element will remain in place while other children scroll. /// /// AlwaysVisible - element will scroll within the visible area, but will remain /// in place once it hits the edge. /// /// NOTE: This control currently only supports horizontal scrolling and orientation. /// </summary> public class ScrollFreezePanel : Panel, IScrollInfo { #region [ Constants ] //constant for amount of scrolling when using keyboard or mouse wheel private const double LineSize = 10; #endregion #region [ Private Fields ] private Point _offset; private Size _extent = new Size(0, 0); private Size _viewport = new Size(0, 0); #endregion #region [ Attached Properties ] /// <summary> /// Orientation /// </summary> public static readonly DependencyProperty OrientationProperty = DependencyProperty.RegisterAttached( "Orientation" , typeof (Orientation), typeof (ScrollFreezePanel), new FrameworkPropertyMetadata(Orientation.Horizontal)); /// <summary> /// Gets the Orientation property /// </summary> public static Orientation GetOrientation(DependencyObject d) { return (Orientation)d.GetValue(OrientationProperty); } /// <summary> /// Sets the Orientation property /// </summary> public static void SetOrientation(DependencyObject d, Orientation value) { d.SetValue(OrientationProperty, value); } /// <summary> /// ScrollBehaviour /// </summary> public static readonly DependencyProperty ScrollBehaviourProperty = DependencyProperty.RegisterAttached( "ScrollBehaviour" , typeof (ScrollBehaviours), typeof (ScrollFreezePanel), new FrameworkPropertyMetadata(ScrollBehaviours.Scrollable)); /// <summary> /// Gets the ScrollBehaviour property /// </summary> public static ScrollBehaviours GetScrollBehaviour(DependencyObject d) { return (ScrollBehaviours)d.GetValue(ScrollBehaviourProperty); } /// <summary> /// Sets the ScrollBehaviour property /// </summary> public static void SetScrollBehaviour(DependencyObject d, ScrollBehaviours value) { d.SetValue(ScrollBehaviourProperty, value); } #endregion #region [ Overriden Methods ] /// <summary> /// Positions child elements and determines a size for the panel /// </summary> /// <param name="arrangeSize">The final area within the parent that this element /// should use to arrange itself and its children.</param> /// <returns>The actual size used.</returns> protected override Size ArrangeOverride(Size arrangeSize) { var finalRect = new Rect(arrangeSize); if (IsHorizontal) finalRect.X -= _offset.X; else finalRect.Y -= _offset.Y; double width = 0.0; double height = 0.0; double offSetLeftUsed = 0; double offSetTopUsed = 0; Rect[] elementsOffsetRight = ScrollFreezeCalculateInFront(Children); for ( int i = 0; i < Children.Count; i++) { UIElement element = Children[i]; if (IsHorizontal) finalRect.X += width; else finalRect.Y += height; width = element.DesiredSize.Width; height = element.DesiredSize.Height; finalRect.Width = IsHorizontal ? width : Math.Max(arrangeSize.Width, element.DesiredSize.Width); finalRect.Height = IsHorizontal ? Math.Max(arrangeSize.Height, element.DesiredSize.Height) : height; switch (GetScrollBehaviour(element)) { case ScrollBehaviours.NonScrollable: if (IsHorizontal ? (finalRect.X + width) >= (_viewport.Width - elementsOffsetRight[i + 1].X) : (finalRect.Y + height) >= (_viewport.Height - elementsOffsetRight[i + 1].Y) ) { SetZIndex(element, 1); Rect tempVisible = finalRect; if (IsHorizontal) tempVisible.X = _viewport.Width - elementsOffsetRight[i].X; else tempVisible.Y = _viewport.Height - elementsOffsetRight[i].Y; element.Arrange(tempVisible); } else { SetZIndex(element, 1); Rect temp = finalRect; if (IsHorizontal) temp.X += _offset.X; else temp.Y += _offset.Y; element.Arrange(temp); offSetLeftUsed += width; offSetTopUsed += height; } break ; case ScrollBehaviours.AlwaysVisible: if (IsHorizontal ? finalRect.X <= offSetLeftUsed : finalRect.Y <= offSetTopUsed) { SetZIndex(element, 1); Rect tempVisible = finalRect; if (IsHorizontal) tempVisible.X = offSetLeftUsed; else tempVisible.Y = offSetTopUsed; element.Arrange(tempVisible); offSetLeftUsed += width; offSetTopUsed += height; } else if (IsHorizontal ? (finalRect.X + width) >= (_viewport.Width - elementsOffsetRight[i + 1].X) : (finalRect.Y + height) >= (_viewport.Height - elementsOffsetRight[i + 1].Y) ) { SetZIndex(element, 1); Rect tempVisible = finalRect; if (IsHorizontal) tempVisible.X = _viewport.Width - elementsOffsetRight[i].X; else tempVisible.Y = _viewport.Height - elementsOffsetRight[i].Y; element.Arrange(tempVisible); } else { SetZIndex(element, 0); element.Arrange(finalRect); } break ; default : SetZIndex(element, 0); element.Arrange(finalRect); break ; } } return arrangeSize; } /// <summary> /// Measures the size in layout required for child elements and determines /// a size for the panel /// </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) { var size = new Size(); for ( int i = 0; i < InternalChildren.Count; i++) { UIElement element = InternalChildren[i]; element.Measure(availableSize); Size desiredSize = element.DesiredSize; if (IsHorizontal) { size.Width += desiredSize.Width; size.Height = Math.Max(size.Height, desiredSize.Height); } else { size.Width = Math.Max(size.Width, desiredSize.Width); size.Height += desiredSize.Height; } } UpdateScrollInfo(availableSize, size); return size; } #endregion #region [ Private Methods ] //calculate the horizontal offset for each element in a collection based on //the ScrollBehaviour attached property private static Rect[] ScrollFreezeCalculateInFront(UIElementCollection children) { var elementsOffsetRight = new Rect[children.Count + 1]; var calculated = new Rect(); for ( int i = children.Count - 1; i >= 0; i--) { UIElement element = children[i]; ScrollBehaviours behaviour = GetScrollBehaviour(element); if (behaviour == ScrollBehaviours.AlwaysVisible || behaviour == ScrollBehaviours.NonScrollable) { calculated.X += element.DesiredSize.Width; calculated.Y += element.DesiredSize.Height; } elementsOffsetRight[i] = calculated; } return elementsOffsetRight; } //update scroll info based on availble and actual sizes private void UpdateScrollInfo(Size available, Size actual) { if (IsHorizontal ? available.Width < actual.Width : available.Height < actual.Height) { _viewport = available; _extent = actual; if (ScrollOwner != null ) { ScrollOwner.InvalidateScrollInfo(); } else { _offset = new Point(); } } else { _viewport = actual; _extent = new Size(0, 0); _offset = new Point(); } } #endregion #region [ IScrollInfo Properties ] /// <summary> /// Gets or sets a value that indicates whether scrolling on the horizontal /// axis is possible. /// </summary> public bool CanHorizontallyScroll { get ; set ; } /// <summary> /// Gets or sets a value that indicates whether scrolling on the vertical /// axis is possible. This will always return false. /// </summary> public bool CanVerticallyScroll { get ; set ; } /// <summary> /// Gets or sets a ScrollViewer element that controls scrolling behavior. /// </summary> public ScrollViewer ScrollOwner { get ; set ; } /// <summary> /// Gets the horizontal offset of the scrolled content. /// </summary> public double HorizontalOffset { get { return _offset.X; } } /// <summary> /// Gets the vertical offset of the scrolled content. /// </summary> public double VerticalOffset { get { return _offset.Y; } } /// <summary> /// Gets the vertical size of the extent. /// </summary> public double ExtentHeight { get { return _extent.Height; } } /// <summary> /// Gets the horizontal size of the extent. /// </summary> public double ExtentWidth { get { return _extent.Width; } } /// <summary> /// Gets the vertical size of the viewport for this content. /// </summary> public double ViewportHeight { get { return _viewport.Height; } } /// <summary> /// Gets the horizontal size of the viewport for this content. /// </summary> public double ViewportWidth { get { return _viewport.Width; } } #endregion #region [ IScrollInfo Methods ] /// <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) { if (offset < 0 || _viewport.Width >= _extent.Width) { offset = 0; } else if (offset + _viewport.Width >= _extent.Width) { offset = _extent.Width - _viewport.Width; } _offset.X = offset; if (ScrollOwner != null ) { ScrollOwner.InvalidateScrollInfo(); } InvalidateArrange(); } /// <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 left within content after a user clicks the wheel button on a mouse. /// </summary> public void MouseWheelLeft() { LineLeft(); } /// <summary> /// Scrolls right within content after a user clicks the wheel button on a mouse. /// </summary> public void MouseWheelRight() { LineRight(); } /// <summary> /// Scrolls down within content after a user scrolls down the wheel on a mouse. /// In Horizontal orientation will scroll content RIGHT /// </summary> public void MouseWheelDown() { LineDown(); } /// <summary> /// Scrolls up within content after a user scrolls up the wheel on a mouse. /// In Horizontal orientation will scroll content LEFT /// </summary> public void MouseWheelUp() { LineUp(); } /// <summary> /// Scrolls down within content by one logical unit. /// </summary> public void LineDown() { SetHorizontalOffset(HorizontalOffset + LineSize); } /// <summary> /// Scrolls up within content by one logical unit. /// </summary> public void LineUp() { SetHorizontalOffset(HorizontalOffset - LineSize); } /// <summary> /// Scrolls down within content by one page. /// </summary> public void PageDown() { SetHorizontalOffset(HorizontalOffset + LineSize * 10); } /// <summary> /// Scrolls left within content by one page. /// NOT IMPLEMENTED /// </summary> public void PageLeft() { } /// <summary> /// Scrolls right within content by one page. /// NOT IMPLEMENTED /// </summary> public void PageRight() { } /// <summary> /// Scrolls up within content by one page. /// </summary> public void PageUp() { SetHorizontalOffset(HorizontalOffset - LineSize * 10); } /// <summary> /// Forces content to scroll until the coordinate space of a Visual object is visible. /// NOT IMPLEMENTED /// </summary> /// <param name="visual">A Visual that becomes visible.</param> /// <param name="rectangle">A bounding rectangle that identifies the coordinate space to /// make visible.</param> /// <returns>A Rect that is visible.</returns> public Rect MakeVisible(Visual visual, Rect rectangle) { return rectangle; //TODO:IMPLEMENT } /// <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) { if (offset < 0 || _viewport.Height >= _extent.Height) { offset = 0; } else if (offset + _viewport.Height >= _extent.Height) { offset = _extent.Height - _viewport.Height; } _offset.Y = offset; if (ScrollOwner != null ) { ScrollOwner.InvalidateScrollInfo(); } InvalidateArrange(); } #endregion #region [ Private Properties ] private bool IsHorizontal { get { return GetOrientation( this ) == Orientation.Horizontal; } } #endregion } }
|
출처1
https://iimaginec.wordpress.com/2010/06/19/scroll-freeze-panel-in-wpf/
출처2