728x90
반응형
728x170
[TestLibrary 프로젝트]
▶ DependencyObjectExtension.cs
using System.Windows;
using System.Windows.Media;
namespace TestLibrary
{
/// <summary>
/// 의존 객체 확장
/// </summary>
public static class DependencyObjectExtension
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 부모 구하기 - GetParent<T>(source)
/// <summary>
/// 부모 구하기
/// </summary>
/// <typeparam name="TParent">부모 타입</typeparam>
/// <param name="source">소스</param>
/// <returns>부모</returns>
public static TParent GetParent<TParent>(this DependencyObject source) where TParent : DependencyObject
{
if(source == null)
{
return null;
}
DependencyObject parent = VisualTreeHelper.GetParent(source);
if(parent is TParent)
{
return parent as TParent;
}
return parent.GetParent<TParent>();
}
#endregion
}
}
728x90
▶ FrameworkElementExtension.cs
using Microsoft.CSharp.RuntimeBinder;
using System;
using System.Windows;
namespace TestLibrary
{
/// <summary>
/// 프레임워크 엘리먼트 확장
/// </summary>
public static class FrameworkElementExtension
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 엘리먼트 너비 소유 보장하기 - EnsureElementHasWidth(element)
/// <summary>
/// 엘리먼트 너비 소유 보장하기
/// </summary>
/// <param name="element">프레임워크 엘리먼트</param>
public static void EnsureElementHasWidth(this FrameworkElement element)
{
if(element.Width.Equals(double.NaN))
{
element.Width = element.ActualWidth;
}
}
#endregion
#region 엘리먼트 높이 소유 보장하기 - EnsureElementHasHeight(element)
/// <summary>
/// 엘리먼트 높이 소유 보장하기
/// </summary>
/// <param name="element">프레임워크 엘리먼트</param>
public static void EnsureElementHasHeight(this FrameworkElement element)
{
if(element.Height.Equals(double.NaN))
{
element.Height = element.ActualHeight;
}
}
#endregion
#region 엘리먼트 크기 소유 보장하기 - EnsureElementHasSize(element)
/// <summary>
/// 엘리먼트 크기 소유 보장하기
/// </summary>
/// <param name="element">프레임워크 엘리먼트</param>
public static void EnsureElementHasSize(this FrameworkElement element)
{
element.EnsureElementHasWidth();
element.EnsureElementHasHeight();
}
#endregion
#region 실제 최소 너비 구하기 - GetActualMinimumWidth(element)
/// <summary>
/// 실제 최소 너비 구하기
/// </summary>
/// <param name="element">프레임워크 엘리먼트</param>
/// <returns>실제 최소 너비</returns>
public static double GetActualMinimumWidth(this FrameworkElement element)
{
double actualMinimumWidth;
try
{
dynamic control = element;
actualMinimumWidth = Math.Max(element.MinWidth, control.Padding.Left + control.Padding.Right);
}
catch(RuntimeBinderException)
{
actualMinimumWidth = element.MinWidth;
}
return actualMinimumWidth;
}
#endregion
#region 실제 최소 높이 구하기 - GetActualMinimumHeight(element)
/// <summary>
/// 실제 최소 높이 구하기
/// </summary>
/// <param name="element">프레임워크 엘리먼트</param>
/// <returns>실제 최소 높이</returns>
public static double GetActualMinimumHeight(this FrameworkElement element)
{
double actualMinimumHeight;
try
{
dynamic control = element;
actualMinimumHeight = Math.Max(element.MinHeight, control.Padding.Top + control.Padding.Bottom);
}
catch(RuntimeBinderException)
{
actualMinimumHeight = element.MinHeight;
}
return actualMinimumHeight;
}
#endregion
}
}
300x250
▶ Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestLibrary">
<Style TargetType="{x:Type local:RectangleThumb}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="SteelBlue" />
<Setter Property="Background" Value="LightSteelBlue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:RectangleThumb}">
<Rectangle
StrokeThickness="{Binding Path=BorderThickness.Left, RelativeSource={RelativeSource TemplatedParent}}"
Stroke="{TemplateBinding BorderBrush}"
Fill="{TemplateBinding Background}">
</Rectangle>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:SelectableCanvas}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SelectableCanvas}">
<Border
Margin="{TemplateBinding Margin}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}">
<ItemsPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<local:AutoResizingCanvas />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
▶ RectangleThumb.cs
using System.Windows;
using System.Windows.Controls.Primitives;
namespace TestLibrary
{
/// <summary>
/// 사각형 THUMB
/// </summary>
public class RectangleThumb : Thumb
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Static
#region 생성자 - RectangleThumb()
/// <summary>
/// 생성자
/// </summary>
static RectangleThumb()
{
DefaultStyleKeyProperty.OverrideMetadata
(
typeof(RectangleThumb),
new FrameworkPropertyMetadata(typeof(RectangleThumb))
);
}
#endregion
}
}
▶ HatchedBorderThumb.cs
using System;
using System.Windows;
using System.Windows.Media;
namespace TestLibrary
{
/// <summary>
/// 해시 테두리 THUMB
/// </summary>
public class HatchedBorderThumb : RectangleThumb
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 해시 펜
/// </summary>
private Pen hatchingPen;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - HatchedBorderThumb()
/// <summary>
/// 생성자
/// </summary>
public HatchedBorderThumb()
{
Loaded += RectangleThumb_Loaded;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 렌더링시 처리하기 - OnRender(drawingContext)
/// <summary>
/// 렌더링시 처리하기
/// </summary>
/// <param name="drawingContext">드로잉 컨텍스트</param>
protected override void OnRender(DrawingContext drawingContext)
{
Rect outerRectangle = new Rect(0, 0, ActualWidth, ActualHeight);
Rect innerRectangle = new Rect
(
BorderThickness.Left,
BorderThickness.Top,
ActualWidth - BorderThickness.Left - BorderThickness.Right,
ActualHeight - BorderThickness.Top - BorderThickness.Bottom
);
CombinedGeometry geometry = new CombinedGeometry
(
GeometryCombineMode.Exclude,
new RectangleGeometry(outerRectangle),
new RectangleGeometry(innerRectangle)
);
drawingContext.PushClip(geometry);
drawingContext.DrawRectangle(Brushes.Transparent, null, outerRectangle);
double limit = 2 * Math.Max(ActualWidth, ActualHeight);
for(double i = 0; i < limit; i += 5)
{
drawingContext.DrawLine
(
this.hatchingPen,
new Point(0, i),
new Point(i, 0)
);
}
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
#region 사각형 THUMB 로드시 처리하기 - RectangleThumb_Loaded(sender, e)
/// <summary>
/// 사각형 THUMB 로드시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void RectangleThumb_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= RectangleThumb_Loaded;
this.hatchingPen = new Pen(BorderBrush, 1);
BorderBrush = null;
Background = null;
InvalidateVisual();
}
#endregion
}
}
▶ CustomPositionChangedEventArgs.cs
using System.Windows;
namespace TestLibrary
{
/// <summary>
/// 커스텀 위치 변경시 이벤트 인자
/// </summary>
public class CustomPositionChangedEventArgs : RoutedEventArgs
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 이전 위치
/// </summary>
private readonly Point previousPosition;
/// <summary>
/// 신규 위치
/// </summary>
private readonly Point newPosition;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 이전 위치 - PreviousPosition
/// <summary>
/// 이전 위치
/// </summary>
public Point PreviousPosition
{
get
{
return this.previousPosition;
}
}
#endregion
#region 신규 위치 - NewPosition
/// <summary>
/// 신규 위치
/// </summary>
public Point NewPosition
{
get
{
return this.newPosition;
}
}
#endregion
#region X 오프셋 - XOffset
/// <summary>
/// X 오프셋
/// </summary>
public double XOffset
{
get
{
return NewPosition.X - PreviousPosition.X;
}
}
#endregion
#region Y 오프셋 - YOffset
/// <summary>
/// Y 오프셋
/// </summary>
public double YOffset
{
get
{
return NewPosition.Y - PreviousPosition.Y;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - CustomPositionChangedEventArgs(previousPosition, newPosition)
/// <summary>
/// 생성자
/// </summary>
/// <param name="previousPosition">이전 위치</param>
/// <param name="newPosition">신규 위치</param>
public CustomPositionChangedEventArgs(Point previousPosition, Point newPosition)
{
this.previousPosition = previousPosition;
this.newPosition = newPosition;
}
#endregion
}
}
▶ CustomSizeChangedEventArgs.cs
using System.Windows;
namespace TestLibrary
{
/// <summary>
/// 커스텀 크기 변경시 이벤트 인자
/// </summary>
public class CustomSizeChangedEventArgs : RoutedEventArgs
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 이전 크기
/// </summary>
private Size previousSize;
/// <summary>
/// 신규 크기
/// </summary>
private Size newSize;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 이전 크기 - PreviousSize
/// <summary>
/// 이전 크기
/// </summary>
public Size PreviousSize
{
get
{
return this.previousSize;
}
}
#endregion
#region 신규 크기 - NewSize
/// <summary>
/// 신규 크기
/// </summary>
public Size NewSize
{
get
{
return this.newSize;
}
}
#endregion
#region 너비 차이 - WidthDifference
/// <summary>
/// 너비 차이
/// </summary>
public double WidthDifference
{
get
{
return this.newSize.Width - this.previousSize.Width;
}
}
#endregion
#region 높이 차이 - HeightDifference
/// <summary>
/// 높이 차이
/// </summary>
public double HeightDifference
{
get
{
return this.newSize.Height - this.previousSize.Height;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - CustomSizeChangedEventArgs(previousSize, newSize)
/// <summary>
/// 생성자
/// </summary>
/// <param name="previousSize">이전 크기</param>
/// <param name="newSize">신규 크기</param>
public CustomSizeChangedEventArgs(Size previousSize, Size newSize) : base(FrameworkElement.SizeChangedEvent)
{
this.previousSize = previousSize;
this.newSize = newSize;
}
#endregion
}
}
▶ DragResizeAdorner.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace TestLibrary
{
/// <summary>
/// 드래그/크기 변경 장식자
/// </summary>
public class DragResizeAdorner : Adorner
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Delegate
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 커스텀 위치 변경시 이벤트 핸들러 - CustomPositionChangedEventHandler(sender, e)
/// <summary>
/// 커스텀 위치 변경시 이벤트 핸들러
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
public delegate void CustomPositionChangedEventHandler(object sender, CustomPositionChangedEventArgs e);
#endregion
#region 커스텀 크기 변경시 이벤트 핸들러 - CustomSizeChangedEventHandler(sender, e)
/// <summary>
/// 커스텀 크기 변경시 이벤트 핸들러
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
public delegate void CustomSizeChangedEventHandler(object sender, CustomSizeChangedEventArgs e);
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Event
////////////////////////////////////////////////////////////////////////////////////////// Public
#region PREVIEW 위치 변경시 이벤트 - PreviewPositionChanged
/// <summary>
/// PREVIEW 위치 변경시 이벤트
/// </summary>
public event CustomPositionChangedEventHandler PreviewPositionChanged;
#endregion
#region 위치 변경시 이벤트 - PositionChanged
/// <summary>
/// 위치 변경시 이벤트
/// </summary>
public event CustomPositionChangedEventHandler PositionChanged;
#endregion
#region PREVIEW 크기 변경시 이벤트 - PreviewSizeChanged
/// <summary>
/// PREVIEW 크기 변경시 이벤트
/// </summary>
public event CustomSizeChangedEventHandler PreviewSizeChanged;
#endregion
#region 크기 변경시 이벤트 - SizeChanged
/// <summary>
/// 크기 변경시 이벤트
/// </summary>
public new event CustomSizeChangedEventHandler SizeChanged;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region Field
/// <summary>
/// THUMB 크기
/// </summary>
public static readonly Size ThumbSize = new Size(8, 8);
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// THUMB 테두리 두께
/// </summary>
private const double ThumbBorderThickness = 2;
/// <summary>
/// 테두리 THUMB
/// </summary>
private readonly Thumb borderThumb;
/// <summary>
/// 좌상단 THUMB
/// </summary>
private readonly Thumb topLeftThumb;
/// <summary>
/// 상단 THUMB
/// </summary>
private readonly Thumb topThumb;
/// <summary>
/// 우상단 THUMB
/// </summary>
private readonly Thumb topRightThumb;
/// <summary>
/// 좌측 THUMB
/// </summary>
private readonly Thumb leftThumb;
/// <summary>
/// 우측 THUMB
/// </summary>
private readonly Thumb rightThumb;
/// <summary>
/// 좌하단 THUMB
/// </summary>
private readonly Thumb bottomLeftThumb;
/// <summary>
/// 하단 THUMB
/// </summary>
private readonly Thumb bottomThumb;
/// <summary>
/// 우하단 THUMB
/// </summary>
private readonly Thumb bottomRightThumb;
/// <summary>
/// 비주얼 컬렉션
/// </summary>
private readonly VisualCollection visualCollection;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 비주얼 자식 카운트 - VisualChildrenCount
/// <summary>
/// 비주얼 자식 카운트
/// </summary>
protected override int VisualChildrenCount
{
get
{
return this.visualCollection.Count;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - DragResizeAdorner(adornedElement)
/// <summary>
/// 생성자
/// </summary>
/// <param name="adornedElement">장식 엘리먼트</param>
public DragResizeAdorner(UIElement adornedElement) : base(adornedElement)
{
this.visualCollection = new VisualCollection(this);
this.borderThumb = new HatchedBorderThumb
{
BorderBrush = Brushes.Black,
BorderThickness = new Thickness
(
ThumbSize.Width,
ThumbSize.Height,
ThumbSize.Width,
ThumbSize.Height
),
Cursor = Cursors.SizeAll
};
this.borderThumb.DragDelta += borderThumb_DragDelta;
this.visualCollection.Add(this.borderThumb);
this.topLeftThumb = GetThumb(Cursors.SizeNWSE, topLeftThumb_DragDelta );
this.topThumb = GetThumb(Cursors.SizeNS , topThumb_DragDelta );
this.topRightThumb = GetThumb(Cursors.SizeNESW, topRightThumb_DragDelta );
this.leftThumb = GetThumb(Cursors.SizeWE , leftThumb_DragDelta );
this.rightThumb = GetThumb(Cursors.SizeWE , rightThumb_DragDelta );
this.bottomLeftThumb = GetThumb(Cursors.SizeNESW, bottomLeftThumb_DragDelta );
this.bottomThumb = GetThumb(Cursors.SizeNS , bottomThumb_DragDelta );
this.bottomRightThumb = GetThumb(Cursors.SizeNWSE, bottomRightThumb_DragDelta);
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 비주얼 자식 구하기 - GetVisualChild(index)
/// <summary>
/// 비주얼 자식 구하기
/// </summary>
/// <param name="index">인덱스</param>
/// <returns>비주얼 자식</returns>
protected override Visual GetVisualChild(int index)
{
return this.visualCollection[index];
}
#endregion
#region 배열하기 (오버라이드) - ArrangeOverride(finalSize)
/// <summary>
/// 배열하기 (오버라이드)
/// </summary>
/// <param name="finalSize">최종 크기</param>
/// <returns>크기</returns>
protected override Size ArrangeOverride(Size finalSize)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement != null)
{
Rect borderRectangle = new Rect
(
new Point(-ThumbSize.Width, -ThumbSize.Height),
new Size
(
adornedElement.ActualWidth + ThumbSize.Width * 2,
adornedElement.ActualHeight + ThumbSize.Height * 2
)
);
this.borderThumb.Arrange(borderRectangle);
Rect topLeftThumbRectangle = new Rect
(
new Point(-ThumbSize.Width, -ThumbSize.Height),
ThumbSize
);
Rect topThumbRectangle = new Rect
(
new Point(adornedElement.ActualWidth / 2 - ThumbSize.Width / 2, -ThumbSize.Height),
ThumbSize
);
Rect topRightThumbRectangle = new Rect
(
new Point(adornedElement.ActualWidth, -ThumbSize.Height),
ThumbSize
);
Rect leftThumbRectangle = new Rect
(
new Point(-ThumbSize.Width, adornedElement.ActualHeight / 2 - ThumbSize.Height / 2),
ThumbSize
);
Rect rightThumbRectangle = new Rect
(
new Point(adornedElement.ActualWidth, adornedElement.ActualHeight / 2 - ThumbSize.Height / 2),
ThumbSize
);
Rect bottomLeftThumbRectangle = new Rect
(
new Point(-ThumbSize.Width, adornedElement.ActualHeight),
ThumbSize
);
Rect bottomThumbRectangle = new Rect
(
new Point(adornedElement.ActualWidth / 2 - ThumbSize.Width / 2, adornedElement.ActualHeight),
ThumbSize
);
Rect bottomRightThumbRectangle = new Rect
(
new Point(adornedElement.ActualWidth, adornedElement.ActualHeight),
ThumbSize
);
this.bottomLeftThumb.Arrange(bottomLeftThumbRectangle);
this.bottomThumb.Arrange(bottomThumbRectangle);
this.bottomRightThumb.Arrange(bottomRightThumbRectangle);
this.leftThumb.Arrange(leftThumbRectangle);
this.rightThumb.Arrange(rightThumbRectangle);
this.topLeftThumb.Arrange(topLeftThumbRectangle);
this.topThumb.Arrange(topThumbRectangle);
this.topRightThumb.Arrange(topRightThumbRectangle);
}
return finalSize;
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 테두리 THUMB 드래그 델타 처리하기 - borderThumb_DragDelta(sender, e)
/// <summary>
/// 테두리 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void borderThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
MoveElement(adornedElement, e.HorizontalChange, e.VerticalChange);
}
#endregion
#region 좌상단 THUMB 드래그 델타 처리하기 - topLeftThumb_DragDelta(sender, e)
/// <summary>
/// 좌상단 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void topLeftThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
adornedElement.EnsureElementHasSize();
ResizeElement
(
adornedElement,
-e.HorizontalChange,
-e.VerticalChange,
() => MoveElement(adornedElement, e.HorizontalChange, 0),
() => MoveElement(adornedElement, 0, e.VerticalChange)
);
}
#endregion
#region 상단 THUMB 드래그 델타 처리하기 - topThumb_DragDelta(sender, e)
/// <summary>
/// 상단 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void topThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
adornedElement.EnsureElementHasHeight();
ResizeElement
(
adornedElement,
0,
-e.VerticalChange,
null,
() => MoveElement(adornedElement, 0, e.VerticalChange)
);
}
#endregion
#region 우상단 THUMB 드래그 델타 처리하기 - topRightThumb_DragDelta(sender, e)
/// <summary>
/// 우상단 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void topRightThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
adornedElement.EnsureElementHasSize();
ResizeElement
(
adornedElement,
e.HorizontalChange,
-e.VerticalChange,
null,
() => MoveElement(adornedElement, 0, e.VerticalChange)
);
}
#endregion
#region 좌측 THUMB 드래그 델타 처리하기 - leftThumb_DragDelta(sender, e)
/// <summary>
/// 좌측 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void leftThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
adornedElement.EnsureElementHasWidth();
ResizeElement
(
adornedElement,
-e.HorizontalChange,
0,
() => MoveElement(adornedElement, e.HorizontalChange, 0)
);
}
#endregion
#region 우측 THUMB 드래그 델타 처리하기 - rightThumb_DragDelta(sender, e)
/// <summary>
/// 우측 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void rightThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
adornedElement.EnsureElementHasWidth();
ResizeElement(adornedElement, e.HorizontalChange, 0);
}
#endregion
#region 좌하단 THUMB 드래그 델타 처리하기 - bottomLeftThumb_DragDelta(sender, e)
/// <summary>
/// 좌하단 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void bottomLeftThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
adornedElement.EnsureElementHasSize();
ResizeElement
(
adornedElement,
-e.HorizontalChange,
e.VerticalChange,
() => MoveElement(adornedElement, e.HorizontalChange, 0)
);
}
#endregion
#region 하단 THUMB 드래그 델타 처리하기 - bottomThumb_DragDelta(sender, e)
/// <summary>
/// 하단 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void bottomThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
adornedElement.EnsureElementHasHeight();
ResizeElement(adornedElement, 0, e.VerticalChange);
}
#endregion
#region 우하단 THUMB 드래그 델타 처리하기 - bottomRightThumb_DragDelta(sender, e)
/// <summary>
/// 우하단 THUMB 드래그 델타 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void bottomRightThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
FrameworkElement adornedElement = AdornedElement as FrameworkElement;
if(adornedElement == null)
{
return;
}
adornedElement.EnsureElementHasSize();
ResizeElement(adornedElement, e.HorizontalChange, e.VerticalChange);
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Function
#region THUMB 구하기 - GetThumb(cursor, dragDeltaEventHandler)
/// <summary>
/// THUMB 구하기
/// </summary>
/// <param name="cursor">커서</param>
/// <param name="dragDeltaEventHandler">드래그 델타 이벤트 핸들러</param>
/// <returns>THUMB</returns>
private Thumb GetThumb(Cursor cursor, DragDeltaEventHandler dragDeltaEventHandler)
{
RectangleThumb thumb = new RectangleThumb
{
Width = ThumbSize.Width,
Height = ThumbSize.Height,
BorderThickness = new Thickness(ThumbBorderThickness),
Cursor = cursor
};
thumb.DragDelta += dragDeltaEventHandler;
this.visualCollection.Add(thumb);
return thumb;
}
#endregion
#region 엘리먼트 이동하기 - MoveElement(element, xOffset, yOffset)
/// <summary>
/// 엘리먼트 이동하기
/// </summary>
/// <param name="element">엘리먼트</param>
/// <param name="xOffset">X 오프셋</param>
/// <param name="yOffset">Y 오프셋</param>
private void MoveElement(FrameworkElement element, double xOffset, double yOffset)
{
if(xOffset == 0 && yOffset == 0)
{
return;
}
Point previousPosition = new Point
(
Canvas.GetLeft(element),
Canvas.GetTop (element)
);
Point newPosition = new Point
(
previousPosition.X + xOffset,
previousPosition.Y + yOffset
);
CustomPositionChangedEventArgs e = new CustomPositionChangedEventArgs
(
previousPosition,
newPosition
);
if(PreviewPositionChanged != null)
{
PreviewPositionChanged(this, e);
if(e.Handled)
{
return;
}
}
Canvas.SetLeft(element, newPosition.X);
Canvas.SetTop (element, newPosition.Y);
if(PositionChanged != null)
{
PositionChanged(this, e);
}
}
#endregion
#region 엘리먼트 크기 변경하기 - ResizeElement(element, widthDelta, heightDelta, changeWidthAction, changeHeightAction)
/// <summary>
/// 엘리먼트 크기 변경하기
/// </summary>
/// <param name="element">엘리먼트</param>
/// <param name="widthDelta">너비 델타</param>
/// <param name="heightDelta">높이 델타</param>
/// <param name="changeWidthAction">너비 변경 액션</param>
/// <param name="changeHeightAction">높이 변경 액션</param>
private void ResizeElement
(
FrameworkElement element,
double widthDelta,
double heightDelta,
Action changeWidthAction = null,
Action changeHeightAction = null
)
{
double newWidth = element.Width + widthDelta;
double newHeight = element.Height + heightDelta;
bool canChangeWidth = widthDelta != 0 && newWidth > element.GetActualMinimumWidth() && newWidth < element.MaxWidth;
bool canChangeHeight = heightDelta != 0 && newHeight > element.GetActualMinimumHeight() && newHeight < element.MaxHeight;
if(!canChangeWidth && !canChangeHeight)
{
return;
}
Size previousSize = new Size(element.Width, element.Height);
Size newSize = new Size
(
canChangeWidth ? newWidth : previousSize.Width,
canChangeHeight ? newHeight : previousSize.Height
);
CustomSizeChangedEventArgs e = new CustomSizeChangedEventArgs(previousSize, newSize);
if(PreviewSizeChanged != null)
{
PreviewSizeChanged(this, e);
if(e.Handled)
{
return;
}
}
if(canChangeWidth)
{
element.Width = newWidth;
if(changeWidthAction != null)
{
changeWidthAction.Invoke();
}
}
if(canChangeHeight)
{
element.Height = newHeight;
if(changeHeightAction != null)
{
changeHeightAction.Invoke();
}
}
if(SizeChanged != null)
{
SizeChanged(this, e);
}
}
#endregion
}
}
▶ AutoResizingCanvas.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace TestLibrary
{
/// <summary>
/// 자동 크기 조정 캔버스
/// </summary>
public class AutoResizingCanvas : Canvas
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 자식 리스트
/// </summary>
private List<FrameworkElement> childList;
/// <summary>
/// 항목 컨트롤
/// </summary>
private ItemsControl itemsControl;
/// <summary>
/// 스크롤 뷰어
/// </summary>
private ScrollViewer scrollViewer;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Private
#region 생성자 - AutoResizingCanvas()
/// <summary>
/// 생성자
/// </summary>
public AutoResizingCanvas()
{
Loaded += Canvas_Loaded;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 비주얼 자식 변경시 처리하기 - OnVisualChildrenChanged(visualAdded, visualRemoved)
/// <summary>
/// 비주얼 자식 변경시 처리하기
/// </summary>
/// <param name="visualAdded">추가 비주얼</param>
/// <param name="visualRemoved">제거 비주얼</param>
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
{
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
this.childList = InternalChildren.Cast<FrameworkElement>().ToList();
}
#endregion
#region 배열하기 (오버라이드) - ArrangeOverride(finalSize)
/// <summary>
/// 배열하기 (오버라이드)
/// </summary>
/// <param name="arrangeSize">최종 크기</param>
/// <returns>크기</returns>
protected override Size ArrangeOverride(Size finalSize)
{
if(this.scrollViewer != null && InternalChildren.Count != 0)
{
double minimumLeft = this.childList.Min(child => GetLeft(child));
double minimumTop = this.childList.Min(child => GetTop (child));
minimumLeft = Math.Min(0, minimumLeft);
minimumTop = Math.Min(0, minimumTop );
this.childList.ForEach(child => child.RenderTransform = new TranslateTransform(-minimumLeft, -minimumTop));
double maximumWidth = -minimumLeft + this.childList.Max(child => GetLeft(child) + child.ActualWidth );
double maximumHeight = -minimumTop + this.childList.Max(child => GetTop (child) + child.ActualHeight);
double visibleWidth = this.scrollViewer.ViewportWidth - Margin.Left - Margin.Right;
double visibleHeight = this.scrollViewer.ViewportHeight - Margin.Top - Margin.Bottom;
if(this.itemsControl != null)
{
visibleWidth -= this.itemsControl.Margin.Left + this.itemsControl.Margin.Right + this.itemsControl.Padding.Left + this.itemsControl.Padding.Right;
visibleHeight -= this.itemsControl.Margin.Top + this.itemsControl.Margin.Bottom + this.itemsControl.Padding.Top + this.itemsControl.Padding.Bottom;
}
Width = maximumWidth > visibleWidth ? maximumWidth : double.NaN;
Height = maximumHeight > visibleHeight ? maximumHeight : double.NaN;
}
return base.ArrangeOverride(finalSize);
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
#region 캔버스 로드시 처리하기 - Canvas_Loaded(sender, e)
/// <summary>
/// 캔버스 로드시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void Canvas_Loaded(object sender, RoutedEventArgs e)
{
this.scrollViewer = this.GetParent<ScrollViewer>();
this.itemsControl = ItemsControl.GetItemsOwner(this);
InvalidateArrange();
}
#endregion
}
}
▶ SelectableCanvas.cs
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
namespace TestLibrary
{
/// <summary>
/// 선택 가능한 캔버스
/// </summary>
public class SelectableCanvas : MultiSelector
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Static
#region 생성자 - SelectableCanvas()
/// <summary>
/// 생성자
/// </summary>
static SelectableCanvas()
{
DefaultStyleKeyProperty.OverrideMetadata
(
typeof(SelectableCanvas),
new FrameworkPropertyMetadata(typeof(SelectableCanvas))
);
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - SelectableCanvas()
/// <summary>
/// 생성자
/// </summary>
public SelectableCanvas()
{
Padding = new Thickness
(
DragResizeAdorner.ThumbSize.Width,
DragResizeAdorner.ThumbSize.Height,
DragResizeAdorner.ThumbSize.Width,
DragResizeAdorner.ThumbSize.Height
);
IsTabStop = false;
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Method
//////////////////////////////////////////////////////////////////////////////// Protected
#region 선택 변경시 처리하기 - OnSelectionChanged(e)
/// <summary>
/// 선택 변경시 처리하기
/// </summary>
/// <param name="e">이벤트 인자</param>
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(this);
foreach(FrameworkElement element in e.RemovedItems)
{
IEnumerable<DragResizeAdorner> adornerEnumerable = adornerLayer.GetAdorners(element).OfType<DragResizeAdorner>();
foreach(DragResizeAdorner adorner in adornerEnumerable)
{
adorner.PositionChanged -= adorner_PositionChanged;
adorner.PreviewSizeChanged -= adorner_PreviewSizeChanged;
adornerLayer.Remove(adorner);
}
}
foreach(FrameworkElement element in e.AddedItems)
{
DragResizeAdorner adorner = new DragResizeAdorner(element);
adorner.PositionChanged += adorner_PositionChanged;
adorner.PreviewSizeChanged += adorner_PreviewSizeChanged;
adornerLayer.Add(adorner);
}
}
#endregion
#region 항목용 컨테이너 준비하기 (오버라이드) - PrepareContainerForItemOverride(element, item)
/// <summary>
/// 항목용 컨테이너 준비하기 (오버라이드)
/// </summary>
/// <param name="element">엘리먼트</param>
/// <param name="item">항목</param>
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var containedElement = item as UIElement;
if(containedElement != null)
{
if(Canvas.GetLeft(containedElement).Equals(double.NaN))
{
Canvas.SetLeft(containedElement, 0);
}
if(Canvas.GetTop(containedElement).Equals(double.NaN))
{
Canvas.SetTop(containedElement, 0);
}
containedElement.Focusable = true;
containedElement.GotFocus += containedElement_GotFocus;
containedElement.PreviewMouseDown += containedElement_PreviewMouseDown;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region 장식자 위치 변경시 처리하기 - adorner_PositionChanged(sender, e)
/// <summary>
/// 장식자 위치 변경시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void adorner_PositionChanged(object sender, CustomPositionChangedEventArgs e)
{
DragResizeAdorner adorner = sender as DragResizeAdorner;
if(adorner != null)
{
IEnumerable<FrameworkElement> otherElementEnumerable = SelectedItems
.Cast<FrameworkElement>()
.Where(element => element != adorner.AdornedElement);
foreach(FrameworkElement element in otherElementEnumerable)
{
Canvas.SetLeft(element, Canvas.GetLeft(element) + e.XOffset);
Canvas.SetTop (element, Canvas.GetTop (element) + e.YOffset);
}
}
}
#endregion
#region 장식자 PREVIEW 크기 변경시 처리하기 - adorner_PreviewSizeChanged(sender, e)
/// <summary>
/// 장식자 PREVIEW 크기 변경시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void adorner_PreviewSizeChanged(object sender, CustomSizeChangedEventArgs e)
{
DragResizeAdorner adorner = sender as DragResizeAdorner;
if(adorner != null)
{
List<FrameworkElement> otherElementList = SelectedItems
.Cast<FrameworkElement>()
.Where(elem => elem != adorner.AdornedElement)
.ToList();
foreach(FrameworkElement element in otherElementList)
{
double newWidth = element.ActualWidth + e.WidthDifference;
if(newWidth < element.GetActualMinimumWidth() || newWidth > element.MaxWidth)
{
e.Handled = true;
return;
}
double newHeight = element.ActualHeight + e.HeightDifference;
if (newHeight < element.GetActualMinimumHeight() || newHeight > element.MaxHeight)
{
e.Handled = true;
return;
}
}
foreach(FrameworkElement element in otherElementList)
{
element.EnsureElementHasSize();
element.Width += e.WidthDifference;
element.Height += e.HeightDifference;
}
}
}
#endregion
#region 컨테이너 속 엘리먼트 포커스 획득시 처리하기 - containedElement_GotFocus(sender, e)
/// <summary>
/// 컨테이너 속 엘리먼트 포커스 획득시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void containedElement_GotFocus(object sender, RoutedEventArgs e)
{
UIElement element = sender as UIElement;
if(element != null)
{
if
(
Mouse.LeftButton == MouseButtonState.Pressed &&
(
Keyboard.Modifiers.HasFlag(ModifierKeys.Shift ) ||
Keyboard.Modifiers.HasFlag(ModifierKeys.Control)
)
)
{
if(!GetIsSelected(element))
{
SelectedItems.Add(element);
}
}
else
{
SelectedItems.Clear();
SelectedItems.Add(element);
}
}
}
#endregion
#region 컨테이너 속 엘리먼트 PREVIEW 마우스 DOWN 처리하기 - containedElement_PreviewMouseDown(sender, e)
/// <summary>
/// 컨테이너 속 엘리먼트 PREVIEW 마우스 DOWN 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void containedElement_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
UIElement element = sender as UIElement;
if(element != null)
{
if(e.ChangedButton == MouseButton.Left && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
{
FocusManager.SetFocusedElement(Parent, element);
}
else if(e.ChangedButton == MouseButton.Left && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && GetIsSelected(element))
{
SelectedItems.Remove(element);
if(SelectedItems.Count == 0)
{
FocusManager.SetFocusedElement(Parent, Parent as UIElement);
}
else if(FocusManager.GetFocusedElement(Parent) == element)
{
UIElement lastSelectedElement = SelectedItems
.Cast<UIElement>()
.Last();
FocusManager.SetFocusedElement(Parent, lastSelectedElement);
}
e.Handled = true;
}
else
{
if(FocusManager.GetFocusedElement(Parent) == element)
{
SelectedItems.Clear();
SelectedItems.Add(element);
}
else
{
FocusManager.SetFocusedElement(Parent, element);
}
}
}
}
#endregion
}
}
[TestProject 프로젝트]
▶ MainWindow.xaml
<Window x:Class="TestProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:library="clr-namespace:TestLibrary;assembly=TestLibrary"
Width="800"
Height="600"
Title="MultiSelector 클래스 : 선택/이동/크기 변경 가능한 캔버스 사용하기"
FontFamily="나눔고딕코딩"
FontSize="16">
<Grid>
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<library:SelectableCanvas
Background="CornflowerBlue">
<Border Canvas.Left="50" Canvas.Top="50"
Background="Green"
Padding="15">
<TextBox Text="테두리 내 텍스트 박스" />
</Border>
<Label Canvas.Left="50" Canvas.Top="150"
Content="레이블" />
<ToggleButton Canvas.Left="50" Canvas.Top="250"
Content="토글 버튼" />
<TextBox Canvas.Left="50" Canvas.Top="350"
Text="텍스트 박스" />
</library:SelectableCanvas>
</ScrollViewer>
</Grid>
</Window>
▶ MainWindow.xaml.cs
using System.Windows;
namespace TestProject
{
/// <summary>
/// 메인 윈도우
/// </summary>
public partial class MainWindow : Window
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainWindow()
/// <summary>
/// 생성자
/// </summary>
public MainWindow()
{
InitializeComponent();
}
#endregion
}
}
728x90
반응형
그리드형(광고전용)
'C# > WPF' 카테고리의 다른 글
[C#/WPF] Shape 클래스 : 커스텀 파일 슬라이스(Pie Slice) 사용하기 (0) | 2021.02.14 |
---|---|
[C#/WPF] BitmapSource 클래스 : WINFORM Bitmap 객체 구하기 (0) | 2021.02.14 |
[C#/WPF] RenderTargetBitmap 클래스 : Image 객체에서 비트맵 구하기 (0) | 2021.02.14 |
[C#/WPF] 페이지 전환 애니메이션 사용하기 (0) | 2021.02.14 |
[C#/WPF] 페이지 전환 애니메이션 사용하기 (0) | 2021.02.14 |
[C#/WPF] Canvas 클래스 : 자식 엘리먼트 드래그하기 (0) | 2021.02.13 |
[C#/WPF] ListBox 클래스 : 확장 가능한 리스트 박스 사용하기 (0) | 2021.02.12 |
[C#/WPF] 누겟 설치 : System.Windows.Interactivity.WPF (0) | 2021.02.12 |
[C#/WPF] 디자인 타임에서 리소스 딕셔너리 로드하기 (0) | 2021.02.12 |
[C#/WPF] 리소스 병합 설정하기 (0) | 2021.02.12 |
댓글을 달아 주세요