첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestSolution.zip
0.03MB

 

[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
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요