■ 움직이는 랩 패널(Wrap Panel) 사용하기

------------------------------------------------------------------------------------------------------------------------


TestProject.zip


AnimatedWrapPanel.cs

 

 

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Controls.Primitives;

using System.Windows.Media;

using System.Windows.Media.Animation;

 

namespace TestProject

{

    /// <summary>

    /// 움직이는 랩 패널

    /// </summary>

    public class AnimatedWrapPanel : Panel, IScrollInfo

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// 무한 크기

        /// </summary>

        private static Size _infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Instance

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// 라인 크기

        /// </summary>

        private const double LINE_SIZE = 16;

 

        /// <summary>

        /// 휠 크기

        /// </summary>

        private const double WHEEL_SIZE = 3 * LINE_SIZE;

 

        /// <summary>

        /// 수평 스크롤 가능 여부

        /// </summary>

        private bool canHorizontallyScroll;

 

        /// <summary>

        /// 수직 스크롤 가능 여부

        /// </summary>

        private bool canVerticallyScroll;

 

        /// <summary>

        /// 스크롤 소유자

        /// </summary>

        private ScrollViewer scrollOwner;

 

        /// <summary>

        /// 오프셋 벡터

        /// </summary>

        private Vector offsetVector;

 

        /// <summary>

        /// 범위 크기

        /// </summary>

        private Size extentSize;

 

        /// <summary>

        /// 뷰포트 크기

        /// </summary>

        private Size viewportSize;

 

        /// <summary>

        /// 애니메이션 시간 범위

        /// </summary>

        private TimeSpan animationTimeSpan = TimeSpan.FromMilliseconds(200);

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 스크롤 소유자 - ScrollOwner

 

        /// <summary>

        /// 스크롤 소유자

        /// </summary>

        public ScrollViewer ScrollOwner

        {

            get

            {

                return this.scrollOwner;

            }

            set

            {

                this.scrollOwner = value;

            }

        }

 

        #endregion

        #region 수평 스크롤 가능 여부 - CanHorizontallyScroll

 

        /// <summary>

        /// 수평 스크롤 가능 여부

        /// </summary>

        public bool CanHorizontallyScroll

        {

            get

            {

                return this.canHorizontallyScroll;

            }

            set

            {

                this.canHorizontallyScroll = value;

            }

        }

 

        #endregion

        #region 수직 스크롤 가능 여부 - CanVerticallyScroll

 

        /// <summary>

        /// 수직 스크롤 가능 여부

        /// </summary>

        public bool CanVerticallyScroll

        {

            get

            {

                return this.canVerticallyScroll;

            }

            set

            {

                this.canVerticallyScroll = value;

            }

        }

 

        #endregion

        #region 범위 너비 - ExtentWidth

 

        /// <summary>

        /// 범위 너비

        /// </summary>

        public double ExtentWidth

        {

            get

            {

                return this.extentSize.Width;

            }

        }

 

        #endregion

        #region 범위 높이 - ExtentHeight

 

        /// <summary>

        /// 범위 높이

        /// </summary>

        public double ExtentHeight

        {

            get

            {

                return this.extentSize.Height;

            }

        }

 

        #endregion

        #region 수평 오프셋 - HorizontalOffset

 

        /// <summary>

        /// 수평 오프셋

        /// </summary>

        public double HorizontalOffset

        {

            get

            {

                return this.offsetVector.X;

            }

        }

 

        #endregion

        #region 수직 오프셋 - VerticalOffset

 

        /// <summary>

        /// 수직 오프셋

        /// </summary>

        public double VerticalOffset

        {

            get

            {

                return this.offsetVector.Y;

            }

        }

 

        #endregion

        #region 뷰포트 너비 - ViewportWidth

 

        /// <summary>

        /// 뷰포트 너비

        /// </summary>

        public double ViewportWidth

        {

            get

            {

                return this.viewportSize.Width;

            }

        }

 

        #endregion

        #region 뷰포트 높이 - ViewportHeight

 

        /// <summary>

        /// 뷰포트 높이

        /// </summary>

        public double ViewportHeight

        {

            get

            {

                return this.viewportSize.Height;

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 한 줄 왼쪽으로 이동하기 - LineLeft()

 

        /// <summary>

        /// 한 줄 왼쪽으로 이동하기

        /// </summary>

        public void LineLeft()

        {

            SetHorizontalOffset(HorizontalOffset - LINE_SIZE);

        }

 

        #endregion

        #region 한 줄 위로 이동하기 - LineUp()

 

        /// <summary>

        /// 한 줄 위로 이동하기

        /// </summary>

        public void LineUp()

        {

            SetVerticalOffset(VerticalOffset - LINE_SIZE);

        }

 

        #endregion

        #region 한 줄 오른쪽으로 이동하기 - LineRight()

 

        /// <summary>

        /// 한 줄 오른쪽으로 이동하기

        /// </summary>

        public void LineRight()

        {

            SetHorizontalOffset(HorizontalOffset + LINE_SIZE);

        }

 

        #endregion

        #region 한 줄 아래로 이동하기 - LineDown()

 

        /// <summary>

        /// 한 줄 아래로 이동하기

        /// </summary>

        public void LineDown()

        {

            SetVerticalOffset(VerticalOffset + LINE_SIZE);

        }

 

        #endregion

 

        #region 마우스 휠 왼쪽으로 이동하기 - MouseWheelLeft()

 

        /// <summary>

        /// 마우스 휠 왼쪽으로 이동하기

        /// </summary>

        public void MouseWheelLeft()

        {

            SetHorizontalOffset(HorizontalOffset - WHEEL_SIZE);

        }

 

        #endregion

        #region 마우스 휠 위로 이동하기 - MouseWheelUp()

 

        /// <summary>

        /// 마우스 휠 위로 이동하기

        /// </summary>

        public void MouseWheelUp()

        {

            SetVerticalOffset(VerticalOffset - WHEEL_SIZE);

        }

 

        #endregion

        #region 마우스 휠 오른쪽으로 이동하기 - MouseWheelRight()

 

        /// <summary>

        /// 마우스 휠 오른쪽으로 이동하기

        /// </summary>

        public void MouseWheelRight()

        {

            SetHorizontalOffset(HorizontalOffset + WHEEL_SIZE);

        }

 

        #endregion

        #region 마우스 휠 아래로 이동하기 - MouseWheelDown()

 

        /// <summary>

        /// 마우스 휠 아래로 이동하기

        /// </summary>

        public void MouseWheelDown()

        {

            SetVerticalOffset(VerticalOffset + WHEEL_SIZE);

        }

 

        #endregion

 

        #region 페이지 왼쪽으로 이동하기 - PageLeft()

 

        /// <summary>

        /// 페이지 왼쪽으로 이동하기

        /// </summary>

        public void PageLeft()

        {

            SetHorizontalOffset(HorizontalOffset - ViewportWidth);

        }

 

        #endregion

        #region 페이지 위로 이동하기 - PageUp()

 

        /// <summary>

        /// 페이지 위로 이동하기

        /// </summary>

        public void PageUp()

        {

            SetVerticalOffset(VerticalOffset - ViewportHeight);

        }

 

        #endregion

        #region 페이지 오른쪽으로 이동하기 - PageRight()

 

        /// <summary>

        /// 페이지 오른쪽으로 이동하기

        /// </summary>

        public void PageRight()

        {

            SetHorizontalOffset(HorizontalOffset + ViewportWidth);

        }

 

        #endregion

        #region 페이지 아래로 이동하기 - PageDown()

 

        /// <summary>

        /// 페이지 아래로 이동하기

        /// </summary>

        public void PageDown()

        {

            SetVerticalOffset(VerticalOffset + ViewportHeight);

        }

 

        #endregion

 

        #region 표시하기 - MakeVisible(visual, rectangle)

 

        /// <summary>

        /// 표시하기

        /// </summary>

        /// <param name="visual">비주얼 객체</param>

        /// <param name="rectangle">사각형</param>

        /// <returns>사각형</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);

 

            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;

        }

 

        #endregion

 

        #region 수평 오프셋 설정하기 - SetHorizontalOffset(offset)

 

        /// <summary>

        /// 수평 오프셋 설정하기

        /// </summary>

        /// <param name="offset">오프셋</param>

        public void SetHorizontalOffset(double offset)

        {

            offset = Math.Max(0, Math.Min(offset, ExtentWidth - ViewportWidth));

 

            if(offset != this.offsetVector.Y)

            {

                this.offsetVector.X = offset;

 

                InvalidateArrange();

            }

        }

 

        #endregion

        #region 수직 오프셋 설정하기 - SetVerticalOffset(offset)

 

        /// <summary>

        /// 수직 오프셋 설정하기

        /// </summary>

        /// <param name="offset">오프셋</param>

        public void SetVerticalOffset(double offset)

        {

            offset = Math.Max(0, Math.Min(offset, ExtentHeight - ViewportHeight));

 

            if(offset != this.offsetVector.Y)

            {

                this.offsetVector.Y = offset;

 

                InvalidateArrange();

            }

        }

 

        #endregion

 

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

 

        #region 측정하기 (오버라이드) - MeasureOverride(availableSize)

 

        /// <summary>

        /// 측정하기 (오버라이드)

        /// </summary>

        /// <param name="availableSize">이용 가능한 크기</param>

        /// <returns>측정 크기</returns>

        protected override Size MeasureOverride(Size availableSize)

        {

            double currentX          = 0;

            double currentY          = 0;

            double currentLineHeight = 0;

            double maximumLineWidth  = 0;

 

            foreach(UIElement child in Children)

            {

                child.Measure(_infiniteSize);

 

                if(currentX + child.DesiredSize.Width > availableSize.Width)

                {

                    currentY += currentLineHeight;

 

                    currentX = 0;

 

                    currentLineHeight = 0;

                }

 

                currentX += child.DesiredSize.Width;

 

                if(child.DesiredSize.Height > currentLineHeight)

                {

                    currentLineHeight = child.DesiredSize.Height;

                }

        

                if(currentX > maximumLineWidth)

                {

                    maximumLineWidth = currentX;

                }

            }

 

            currentY += currentLineHeight;

 

            VerifyScrollData(availableSize, new Size(maximumLineWidth, currentY));

 

            return this.viewportSize;

        }

 

        #endregion

        #region 배치하기 (오버라이드) - ArrangeOverride(finalSize)

 

        /// <summary>

        /// 배치하기 (오버라이드)

        /// </summary>

        /// <param name="finalSize">최종 크기</param>

        /// <returns>최종 크기</returns>

        protected override Size ArrangeOverride(Size finalSize)

        {

            if(Children == null || Children.Count == 0)

            {

                return finalSize;

            }

 

            TranslateTransform transform = null;

 

            double currentX          = 0;

            double currentY          = 0;

            double currentLineHeight = 0;

            double maximumLineWidth  = 0;

 

            foreach(UIElement child in Children)

            {

                transform = child.RenderTransform as TranslateTransform;

 

                if(transform == null)

                {

                    child.RenderTransformOrigin = new Point(0, 0);

 

                    transform = new TranslateTransform();

 

                    child.RenderTransform = transform;

                }

 

                if(currentX + child.DesiredSize.Width > finalSize.Width)

                {

                    currentY += currentLineHeight;

 

                    currentX = 0;

 

                    currentLineHeight = 0;

                }

 

                child.Arrange(new Rect(0, 0, child.DesiredSize.Width, child.DesiredSize.Height));

 

                transform.BeginAnimation

                (

                    TranslateTransform.XProperty,