티스토리 뷰
WPF에는 Canvas나 WrapPanel, Grid 등과 같은 패널들이 존재하죠 ~
이러한 패널들을 상속받아 저만의 패널을 만들어 보는 방법을 소개합니다 ^^
Fish Eye라고 하면.. 물고기 눈이죠~
만들고자 했던 패널은
마우스 움직임을 감지하여 마우스 주변의 아이템들의 사이즈를 늘리는 패널입니다.
WrapPanel을 상속받아 사용해 보았습니다.
나름 상세한 주석을 달아놓았으니 부연설명은 필요 없을것입니다.
using System;
using System.Collections.Generic;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media;
using System.Windows.Input;
namespace PanelTest
{
class FishEyePanel : WrapPanel
{
List<FrameworkElement> childList; // 아이템들의 ZIndex를 조절하기 위해 만든 리스트
double ItemLength = -1; // 아이템(Chilren객체의 대각선 길이)
double maxArrange = 2; // 아이템에 마우스가 다가갔을 때의 Max크기(기본값: 2배)
public double MaxArrange
{
set { maxArrange = value; }
}
#region EventHandler
/// <summary>
/// MouseMove 이벤트 핸들러
/// </summary>
/// <param name="e"></param>
protected override void OnMouseMove(MouseEventArgs e)
{
AnimateChilds();
base.OnMouseMove(e);
}
/// <summary>
/// MouseLeave 이벤트 핸들러
/// </summary>
/// <param name="e"></param>
protected override void OnMouseLeave(MouseEventArgs e)
{
foreach (UIElement child in Children)
{
FrameworkElement content = child as FrameworkElement;
ScaleAnimate(content.RenderTransform as ScaleTransform, 1, 1);
}
base.OnMouseLeave(e);
}
#endregion
#region Methods
/// <summary>
/// Children 객체들의 크기 조정
/// Children 객체들의 ZIndex 조정
/// </summary>
private void AnimateChilds()
{
if (childList == null) childList = new List<FrameworkElement>();
childList.Clear();
foreach (UIElement child in Children)
{
FrameworkElement content = child as FrameworkElement;
//
// 크기를 조정하기 위해 아이템의 대각선 길이를 가져옴
//
if (ItemLength == -1) ItemLength = GetLength(content.Width, content.Height);
//
// ScaleTransform이 지정되어있지 않다면 생성
// RenderTransform이 아닌 LayoutTransform으로 할 경우 애니메이션에 해당하지 않는 객체들까지 움직이게 할 수 있음
//
if (content.RenderTransform as ScaleTransform == null)
{
content.RenderTransform = new ScaleTransform(1, 1, content.Width / 2, content.Height / 2);
}
//
// 현재 마우스 위치와 아이템간의 거리를 구하고 아이템 크기의 maxScale배수 이하일 때 크기를 조정
//
double length = GetLength(Mouse.GetPosition(content).X - (content.Width / 2), Mouse.GetPosition(content).Y - (content.Height / 2));
length = maxArrange - (length / (maxArrange * ItemLength));
if (length < 1) length = 1;
//
// 크기 조정
//
ScaleTransform st = content.RenderTransform as ScaleTransform;
ScaleAnimate(st, length, length);
//
// ZIndex를 조절하기 위해 리스트에 추가
//
childList.Add(content);
}
//
// ZIndex를 조절하기 위해 메소드 호출
//
SetChildZIndex();
}
/// <summary>
/// 아이템들의 ZIndex를 조절
/// 마우스에 가까운 아이템이 상단에 올라오도록
/// </summary>
private void SetChildZIndex()
{
//
// ScaleTransform의 ScaleX를 가지고 정렬
//
childList.Sort(new CompScale());
//
// Scale이 클수록 ZIndex에 큰 값이 들어감
//
for (int i = 0; i < childList.Count; i++)
{
Panel.SetZIndex(childList[i], i);
}
}
/// <summary>
/// 거리를 재기 위해 만든 메소드
/// </summary>
/// <param name="x">x포지션</param>
/// <param name="y">y포지션</param>
/// <returns>(0, 0)부터 (x, y)좌표 까지의 길이</returns>
private double GetLength(double x, double y)
{
return Math.Sqrt(x * x + y * y );
}
/// <summary>
/// ScaleTransform의 ScaleX와 ScaleY를 셋팅
/// </summary>
/// <param name="st">ScaleTranfrom객체</param>
/// <param name="scaleX">X Scale값(배수)</param>
/// <param name="scaleY">Y Scale값(배수)</param>
private void ScaleAnimate(ScaleTransform st, double scaleX, double scaleY)
{
st.ScaleX = scaleX;
st.ScaleY = scaleY;
}
#endregion
}
/// <summary>
/// ScaleTransform의 ScaleX를 이용해 리스트를 정렬시키기 위해 IComparer를 구현한 클래스
/// </summary>
class CompScale : IComparer<FrameworkElement>
{
#region IComparer<FrameworkElement> 멤버
public int Compare(FrameworkElement x, FrameworkElement y)
{
return (x.RenderTransform as ScaleTransform).ScaleX.CompareTo((y.RenderTransform as ScaleTransform).ScaleX);
}
#endregion
}
}
FishEyePanel이라는 클래스를 WrapPanel을 상속받아 만들어보았습니다.
사용 예제입니다.
=== xaml
<Window x:Class="PanelTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PanelTest"
Title="Window1" Height="300" Width="300">
<local:FishEyePanel x:Name="LayoutRoot" />
</Window>
== 코드비하인드
public Window1()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Window1_Loaded);
}
void Window1_Loaded(object sender, RoutedEventArgs e)
{
LayoutRoot.MaxArrange = 2.5; // 이건 옵션
for (int i = 0; i < 400; i++)
{
Rectangle rect = new Rectangle();
rect.Fill = new SolidColorBrush(Color.FromArgb(30, 0, 125, 255));
rect.Stroke = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
rect.Margin = new Thickness(5);
rect.Width = 50;
rect.Height = 50;
LayoutRoot.Children.Add(rect);
}
}