■ 움직이는 랩 패널(Wrap Panel) 사용하기
------------------------------------------------------------------------------------------------------------------------
▶ 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();
  |