첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
유용한 소스 코드가 있으면 icodebroker@naver.com으로 보내주시면 감사합니다.
블로그 자료는 자유롭게 사용하세요.

728x90
반응형

■ 확대/축소/이동/드래그 가능한 캔버스 만들기

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


TestProject.zip


Themes/generic.xaml

 

 

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

    xmlns:local="clr-namespace:TestProject">

    <Style TargetType="{x:Type local:DraggableCanvas}">

        <Setter Property="Template">

            <Setter.Value>

                <ControlTemplate TargetType="{x:Type TreeView}">

                    <ControlTemplate.Resources>

                        <Style TargetType="{x:Type TreeViewItem}">

                            <Setter Property="Template">

                                <Setter.Value>

                                    <ControlTemplate TargetType="{x:Type TreeViewItem}">

                                        <Canvas x:Name="PART_Canvas">

                                            <ContentPresenter

                                                ContentTemplate="{TemplateBinding HeaderTemplate}"

                                                Canvas.Left="{Binding (Canvas.Left)}"

                                                Canvas.Right="{Binding (Canvas.Right)}"

                                                Canvas.Top="{Binding (Canvas.Top)}"

                                                Canvas.Bottom="{Binding (Canvas.Bottom)}"

                                                Content="{TemplateBinding Header}">

                                                <i:Interaction.Behaviors>

                                                    <local:CanvasDragBehavior

                                                        BaseElement="{Binding RelativeSource={RelativeSource

                                                            AncestorType=local:MovableCanvas}}"

                                                        TargetElement="{Binding RelativeSource={RelativeSource

                                                            AncestorType=TreeViewItem}}" />

                                                </i:Interaction.Behaviors>

                                            </ContentPresenter>

                                            <ItemsPresenter />

                                        </Canvas>

                                    </ControlTemplate>

                                </Setter.Value>

                            </Setter>

                            <Setter Property="ItemsPanel">

                                <Setter.Value>

                                    <ItemsPanelTemplate>

                                        <Canvas />

                                    </ItemsPanelTemplate>

                                </Setter.Value>

                            </Setter>

                        </Style>

                    </ControlTemplate.Resources>

                    <local:FlottingGrid x:Name="PART_FlottingGrid"

                        GridOffsetX="{Binding ElementName=PART_RootCanvas, Path=OffsetX}"

                        GridOffsetY="{Binding ElementName=PART_RootCanvas, Path=OffsetY}"

                        ZoomRate="{Binding RelativeSource={RelativeSource AncestorType=local:DraggableCanvas}, Path=ZoomRate}"

                        Background="{TemplateBinding Background}">

                        <local:MovableCanvas x:Name="PART_RootCanvas"

                            Background="Transparent"

                            IsItemsHost="True"

                            MovingRatio="{Binding RelativeSource={RelativeSource AncestorType=local:DraggableCanvas}, Path=ZoomRate}">

                            <local:MovableCanvas.LayoutTransform>

                                <ScaleTransform

                                    ScaleX="{Binding

                                        RelativeSource={RelativeSource AncestorType=local:DraggableCanvas}, Path=ZoomRate}"

                                    ScaleY="{Binding

                                        RelativeSource={RelativeSource AncestorType=local:DraggableCanvas}, Path=ZoomRate}"/>

                            </local:MovableCanvas.LayoutTransform>

                        </local:MovableCanvas>

                    </local:FlottingGrid>

                </ControlTemplate>

            </Setter.Value>

        </Setter>

    </Style>

</ResourceDictionary>

 

 

CanvasDragBehavior.cs

 

 

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using System.Windows.Interactivity;

 

namespace TestProject

{

    /// <summary>

    /// 캔버스 드래그 동작

    /// </summary>

    public class CanvasDragBehavior : Behavior<FrameworkElement>

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property

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

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

 

        #region 기본 엘리먼트 속성 - BaseElementProperty

 

        /// <summary>

        /// 기본 엘리먼트 속성

        /// </summary>

        public static readonly DependencyProperty BaseElementProperty = DependencyProperty.Register

        (

            "BaseElement",

            typeof(FrameworkElement),

            typeof(CanvasDragBehavior),

            new PropertyMetadata(null)

        );

 

        #endregion

        #region 타겟 엘리먼트 속성 - TargetElementProperty

 

        /// <summary>

        /// 타겟 엘리먼트 속성

        /// </summary>

        public static readonly DependencyProperty TargetElementProperty = DependencyProperty.Register

        (

            "TargetElement",

            typeof(FrameworkElement),

            typeof(CanvasDragBehavior),

            new PropertyMetadata(null)

        );

 

        #endregion

 

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

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

 

        #region Field

 

        /// <summary>

        /// 드래그 여부

        /// </summary>

        private bool isDragging = false;

 

        /// <summary>

        /// 마우스 시작 위치

        /// </summary>

        private Point mouseStartPoint;

 

        /// <summary>

        /// 항목 시작 위치

        /// </summary>

        private Point itemStartPoint;

 

        #endregion

 

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

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

 

        #region 기본 엘리먼트 - BaseElement

 

        /// <summary>

        /// 기본 엘리먼트

        /// </summary>

        public FrameworkElement BaseElement

        {

            get

            {

                return (FrameworkElement)GetValue(BaseElementProperty);

            }

            set

            {

                SetValue(BaseElementProperty, value);

            }

        }

 

        #endregion

        #region 타겟 엘리먼트 - TargetElement

 

        /// <summary>

        /// 타겟 엘리먼트

        /// </summary>

        public FrameworkElement TargetElement

        {

            get

            {

                return (FrameworkElement)GetValue(TargetElementProperty);

            }

            set

            {

                SetValue(TargetElementProperty, value);

            }

        }

 

        #endregion

 

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

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

 

        #region 부착시 처리하기 - OnAttached()

 

        /// <summary>

        /// 부착시 처리하기

        /// </summary>

        protected override void OnAttached()

        {

            AssociatedObject.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;

 

            base.OnAttached();

        }

 

        #endregion

        #region 탈착시 처리하기 - OnDetaching()

 

        /// <summary>

        /// 탈착시 처리하기

        /// </summary>

        protected override void OnDetaching()

        {

            AssociatedObject.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;

 

            base.OnDetaching();

        }

 

        #endregion

 

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

 

        #region 결합 객체 마우스 왼쪽 버튼 DOWN 처리하기 - AssociatedObject_MouseLeftButtonDown(sender, e)

 

        /// <summary>

        /// 결합 객체 마우스 왼쪽 버튼 DOWN 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

        {

            if(this.isDragging == false)

            {

                FrameworkElement baseElement = (BaseElement ?? AssociatedObject);

 

                baseElement.MouseMove         += baseElement_MouseMove;

                baseElement.MouseLeftButtonUp += baseElement_MouseLeftButtonUp;

 

                FrameworkElement targetElement = (TargetElement ?? AssociatedObject);

 

                this.isDragging = true;

 

                this.mouseStartPoint = e.GetPosition(baseElement);

 

                double x = Canvas.GetLeft(targetElement);

                double y = Canvas.GetTop (targetElement);

 

                x = double.IsNaN(x) ? 0 : x;

                y = double.IsNaN(y) ? 0 : y;

 

                this.itemStartPoint = new Point(x, y);

 

                baseElement.CaptureMouse();

            }

        }

 

        #endregion

        #region 기본 엘리먼트 마우스 이동시 처리하기 - captureElement_MouseMove(sender, e)

 

        /// <summary>

        /// 기본 엘리먼트 마우스 이동시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void baseElement_MouseMove(object sender, MouseEventArgs e)

        {

            if(e.LeftButton == MouseButtonState.Pressed && this.isDragging == true)

            {

                FrameworkElement baseElement   = (this.BaseElement ?? this.AssociatedObject);

                FrameworkElement targetElement = (this.TargetElement ?? this.AssociatedObject);

 

                Vector deltaVector = e.GetPosition(baseElement) - this.mouseStartPoint;

 

                Canvas.SetLeft(targetElement, this.itemStartPoint.X + deltaVector.X);

                Canvas.SetTop (targetElement, this.itemStartPoint.Y + deltaVector.Y);

            }

        }

 

        #endregion

        #region 기본 엘리먼트 마우스 왼쪽 버튼 UP 처리하기 - baseElement_MouseLeftButtonUp(sender, e)

 

        /// <summary>

        /// 기본 엘리먼트 마우스 왼쪽 버튼 UP 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void baseElement_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

        {

            this.isDragging = false;

 

            FrameworkElement baseElement = (BaseElement ?? AssociatedObject);

 

            baseElement.ReleaseMouseCapture();

 

            baseElement.MouseMove         -= baseElement_MouseMove;

            baseElement.MouseLeftButtonUp -= baseElement_MouseLeftButtonUp;

        }

 

        #endregion

    }

}

 

 

MovableCanvas.cs

 

 

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

 

namespace TestProject

{

    /// <summary>

    /// 이동 가능한 캔버스

    /// </summary>

    public class MovableCanvas : Canvas

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property

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

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

 

        #region 이동 비율 속성 - MovingRatioProperty

 

        /// <summary>

        /// 이동 비율 속성

        /// </summary>

        public static readonly DependencyProperty MovingRatioProperty = DependencyProperty.Register

        (

            "MovingRatio",

            typeof(double),

            typeof(MovableCanvas),

            new PropertyMetadata(1.0)

        );

 

        #endregion

        #region 이동시 커서 속성 - CursorOnMoveProperty

 

        /// <summary>

        /// 이동시 커서 속성

        /// </summary>

        public static readonly DependencyProperty CursorOnMoveProperty = DependencyProperty.Register

        (

            "CursorOnMove",

            typeof(Cursor),

            typeof(MovableCanvas),

            new PropertyMetadata(Cursors.ScrollAll)

        );

 

        #endregion

        #region 오프셋 X 속성 - OffsetXProperty

 

        /// <summary>

        /// 오프셋 X 속성

        /// </summary>

        public static readonly DependencyProperty OffsetXProperty = DependencyProperty.Register

        (

            "OffsetX",

            typeof(double),

            typeof(MovableCanvas),

            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure)

        );

 

        #endregion

        #region 오프셋 Y 속성 - OffsetYProperty

 

        /// <summary>

        /// 오프셋 Y 속성

        /// </summary>

        public static readonly DependencyProperty OffsetYProperty = DependencyProperty.Register

        (

            "OffsetY",

            typeof(double),

            typeof(MovableCanvas),

            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure)

        );

 

        #endregion

 

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

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

 

        #region Field

 

        /// <summary>

        /// 드래그 여부

        /// </summary>

        private bool isDragging = false;

 

        /// <summary>

        /// 이전 커서

        /// </summary>

        private Cursor previousCursor;

 

        /// <summary>

        /// 마우스 시작 위치

        /// </summary>

        private Point mouseStartPoint;

 

        /// <summary>

        /// 항목 시작 위치

        /// </summary>

        private Point itemStartPoint;

 

        #endregion

 

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

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

 

        #region 이동 비율 - MovingRatio

 

        /// <summary>

        /// 이동 비율

        /// </summary>

        public double MovingRatio

        {

            get

            {

                return (double)GetValue(MovingRatioProperty);

            }

            set

            {

                SetValue(MovingRatioProperty, value);

            }

        }

 

        #endregion

        #region 이동시 커서 - CursorOnMove

 

        /// <summary>

        /// 이동시 커서

        /// </summary>

        public Cursor CursorOnMove

        {

            get

            {

                return (Cursor)GetValue(CursorOnMoveProperty);

            }

            set

            {

                SetValue(CursorOnMoveProperty, value);

            }

        }

 

        #endregion

        #region 오프셋 X - OffsetX

 

        /// <summary>

        /// 오프셋 X

        /// </summary>

        public double OffsetX

        {

            get

            {

                return (double)GetValue(OffsetXProperty);

            }

            set

            {

                SetValue(OffsetXProperty, value);

            }

        }

 

        #endregion

        #region 오프셋 Y - OffsetY

 

        /// <summary>

        /// 오프셋 Y

        /// </summary>

        public double OffsetY

        {

            get

            {

                return (double)GetValue(OffsetYProperty);

            }

            set

            {

                SetValue(OffsetYProperty, value);

            }

        }

 

        #endregion

 

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

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

 

        #region 마우스 DOWN 처리하기 - OnMouseDown(e)

 

        /// <summary>

        /// 마우스 DOWN 처리하기

        /// </summary>

        /// <param name="e">이벤트 인자</param>

        protected override void OnMouseDown(MouseButtonEventArgs e)

        {

            if(this.isDragging == false && e.MiddleButton == MouseButtonState.Pressed)

            {

                this.isDragging = true;

 

                this.previousCursor = Cursor;

 

                Cursor = CursorOnMove;

 

                this.mouseStartPoint = e.GetPosition(this);

 

                this.itemStartPoint = new Point(this.OffsetX, this.OffsetY);

 

                CaptureMouse();

            }

 

            base.OnMouseDown(e);

        }

 

        #endregion

        #region 마우스 이동시 처리하기 - OnMouseMove(e)

 

        /// <summary>

        /// 마우스 이동시 처리하기

        /// </summary>

        /// <param name="e">이벤트 인자</param>

        protected override void OnMouseMove(MouseEventArgs e)

        {

            if(e.MiddleButton == MouseButtonState.Pressed && this.isDragging == true)

            {

                Vector deltaVector = e.GetPosition(this) - this.mouseStartPoint;

 

                OffsetX = this.itemStartPoint.X + deltaVector.X * MovingRatio;

                OffsetY = this.itemStartPoint.Y + deltaVector.Y * MovingRatio;

            }

 

            base.OnMouseMove(e);

        }

 

        #endregion

        #region 마우스 UP 처리하기 - OnMouseUp(e)

 

        /// <summary>

        /// 마우스 UP 처리하기

        /// </summary>

        /// <param name="e">이벤트 인자</param>

        protected override void OnMouseUp(MouseButtonEventArgs e)

        {

            this.isDragging = false;

 

            Cursor = this.previousCursor;

 

            ReleaseMouseCapture();

 

            base.OnMouseUp(e);

        }

 

        #endregion

 

        #region 배열하기 (오버라이드) - ArrangeOverride(arrangeSize)

 

        /// <summary>

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

        /// </summary>

        /// <param name="arrangeSize">배열 크기</param>

        /// <returns>크기</returns>

        protected override Size ArrangeOverride(Size arrangeSize)

        {

            foreach(UIElement element in InternalChildren)

            {

                if(element != null)

                {

                    double x = 0.0;

                    double y = 0.0;

 

                    double left = Canvas.GetLeft(element);

 

                    if(!double.IsNaN(left))

                    {

                        x = left * MovingRatio;

                    }

                    else

                    {

                        double right = Canvas.GetRight(element) * MovingRatio;

 

                        if(!double.IsNaN(right))

                        {

                            x = arrangeSize.Width - element.DesiredSize.Width - right;

                        }

                    }

 

                    double top = Canvas.GetTop(element);

 

                    if(!double.IsNaN(top))

                    {

                        y = top * MovingRatio;

                    }

                    else

                    {

                        double bottom = Canvas.GetBottom(element) * MovingRatio;

 

                        if(!double.IsNaN(bottom))

                        {

                            y = arrangeSize.Height - element.DesiredSize.Height - bottom;

                        }

                    }

 

                    element.Arrange

                    (

                        new Rect

                        (

                            new Point((x + OffsetX) / MovingRatio, (y + OffsetY) / MovingRatio),

                            element.DesiredSize

                        )

                    );

                }

            }

 

            return arrangeSize;

        }

 

        #endregion

    }

}

 

 

FlottingGrid.cs

 

 

using System.Windows;

using System.Windows.Controls;

using System.Windows.Media;

 

namespace TestProject

{

    /// <summary>

    /// 플로팅 그리드

    /// </summary>

    public class FlottingGrid : Grid

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property

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

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

 

        #region 그리드 오프셋 X 속성 - GridOffsetXProperty

 

        /// <summary>

        /// 그리드 오프셋 X 속성

        /// </summary>

        public static readonly DependencyProperty GridOffsetXProperty = DependencyProperty.Register

        (

            "GridOffsetX",

            typeof(double),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

        #region 그리드 오프셋 Y 속성 - GridOffsetYProperty

 

        /// <summary>

        /// 그리드 오프셋 Y 속성

        /// </summary>

        public static readonly DependencyProperty GridOffsetYProperty = DependencyProperty.Register

        (

            "GridOffsetY",

            typeof(double),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

        #region 확대/축소 비율 속성 - ZoomRateProperty

 

        /// <summary>

        /// 확대/축소 비율 속성

        /// </summary>

        public static readonly DependencyProperty ZoomRateProperty = DependencyProperty.Register

        (

            "ZoomRate",

            typeof(double),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

        #region 확대/축소 중심 X 속성 - ZoomCenterXProperty

 

        /// <summary>

        /// 확대/축소 중심 X 속성

        /// </summary>

        public static readonly DependencyProperty ZoomCenterXProperty = DependencyProperty.Register

        (

            "ZoomCenterX",

            typeof(double),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(0.5, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

        #region 확대/축소 중심 Y 속성 - ZoomCenterYProperty

 

        /// <summary>

        /// 확대/축소 중심 Y 속성

        /// </summary>

        public static readonly DependencyProperty ZoomCenterYProperty = DependencyProperty.Register

        (

            "ZoomCenterY",

            typeof(double),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(0.5, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

        #region 셀 카운트 속성 - CellCountProperty

 

        /// <summary>

        /// 셀 카운트 속성

        /// </summary>

        public static readonly DependencyProperty CellCountProperty = DependencyProperty.Register

        (

            "CellCount",

            typeof(int),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(10, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

        #region 셀 크기 속성 - CellSizeProperty

 

        /// <summary>

        /// 셀 크기 속성

        /// </summary>

        public static readonly DependencyProperty CellSizeProperty = DependencyProperty.Register

        (

            "CellSize",

            typeof(double),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(20.0, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

        #region 그리드 브러시 속성 - GridBrushProperty

 

        /// <summary>

        /// 그리드 브러시 속성

        /// </summary>

        public static readonly DependencyProperty GridBrushProperty = DependencyProperty.Register

        (

            "GridBrush",

            typeof(Brush),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata

            (

                new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)),

                FrameworkPropertyMetadataOptions.AffectsRender

            )

        );

 

        #endregion

        #region 그리드 테두리 속성 - GridBorderProperty

 

        /// <summary>

        /// 그리드 테두리 속성

        /// </summary>

        public static readonly DependencyProperty GridBorderProperty = DependencyProperty.Register

        (

            "GridBorder",

            typeof(double),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

        #region 셀 브러시 속성 - CellBrushProperty

 

        /// <summary>

        /// 셀 브러시 속성

        /// </summary>

        public static readonly DependencyProperty CellBrushProperty = DependencyProperty.Register

        (

            "CellBrush",

            typeof(Brush),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata

            (

                new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)),

                FrameworkPropertyMetadataOptions.AffectsRender

            )

        );

 

        #endregion

        #region 셀 테두리 속성 - CellBorderProperty

 

        /// <summary>

        /// 셀 테두리 속성

        /// </summary>

        public static readonly DependencyProperty CellBorderProperty = DependencyProperty.Register

        (

            "CellBorder",

            typeof(double),

            typeof(FlottingGrid),

            new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender)

        );

 

        #endregion

 

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

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

 

        #region 그리드 오프셋 X - GridOffsetX

 

        /// <summary>

        /// 그리드 오프셋 X

        /// </summary>

        public double GridOffsetX

        {

            get

            {

                return (double)GetValue(GridOffsetXProperty);

            }

            set

            {

                SetValue(GridOffsetXProperty, value);

            }

        }

 

        #endregion

        #region 그리드 오프셋 Y - GridOffsetY

 

        /// <summary>

        /// 그리드 오프셋 Y

        /// </summary>

        public double GridOffsetY

        {

            get

            {

                return (double) GetValue(GridOffsetYProperty);

            }

            set

            {

                SetValue(GridOffsetYProperty, value);

            }

        }

 

        #endregion

        #region 확대/축소 비율 - ZoomRate

 

        /// <summary>

        /// 확대/축소 비율

        /// </summary>

        public double ZoomRate

        {

            get

            {

                return (double) GetValue(ZoomRateProperty);

            }

            set

            {

                SetValue(ZoomRateProperty, value);

            }

        }

 

        #endregion

        #region 확대/축소 중심 X - ZoomCenterX

 

        /// <summary>

        /// 확대/축소 중심 X

        /// </summary>

        public double ZoomCenterX

        {

            get

            {

                return (double)GetValue(ZoomCenterXProperty);

            }

            set

            {

                SetValue(ZoomCenterXProperty, value);

            }

        }

 

        #endregion

        #region 확대/축소 중심 Y - ZoomCenterY

 

        /// <summary>

        /// 확대/축소 중심 Y

        /// </summary>

        public double ZoomCenterY

        {

            get

            {

                return (double)GetValue(ZoomCenterYProperty);

            }

            set

            {

                SetValue(ZoomCenterYProperty, value);

            }

        }

 

        #endregion

        #region 셀 카운트 - CellCount

 

        /// <summary>

        /// 셀 카운트

        /// </summary>

        public int CellCount

        {

            get

            {

                return (int) GetValue(CellCountProperty);

            }

            set

            {

                SetValue(CellCountProperty, value);

            }

        }

 

        #endregion

        #region 셀 크기 - CellSize

 

        /// <summary>

        /// 셀 크기

        /// </summary>

        public double CellSize

        {

            get

            {

                return (double) GetValue(CellSizeProperty);

            }

            set

            {

                SetValue(CellSizeProperty, value);

            }

        }

 

        #endregion

        #region 그리드 브러시 - GridBrush

 

        /// <summary>

        /// 그리드 브러시

        /// </summary>

        public Brush GridBrush

        {

            get

            {

                return (Brush) GetValue(GridBrushProperty);

            }

            set

            {

                SetValue(GridBrushProperty, value);

            }

        }

 

        #endregion

        #region 그리드 테두리 - GridBorder

 

        /// <summary>

        /// 그리드 테두리

        /// </summary>

        public double GridBorder

        {

            get

            {

                return (double) GetValue(GridBorderProperty);

            }

            set

            {

                SetValue(GridBorderProperty, value);

            }

        }

 

        #endregion

        #region 셀 브러시 - CellBrush

 

        /// <summary>

        /// 셀 브러시

        /// </summary>

        public Brush CellBrush

        {

            get

            {

                return (Brush) GetValue(CellBrushProperty);

            }

            set

            {

                SetValue(CellBrushProperty, value);

            }

        }

 

        #endregion

        #region 셀 테두리 - CellBorder

 

        /// <summary>

        /// 셀 테두리

        /// </summary>

        public double CellBorder

        {

            get

            {

                return (double) GetValue(CellBorderProperty);

            }

            set

            {

                SetValue(CellBorderProperty, value);

            }

        }

 

        #endregion

 

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

 

        #region 최대 확대/축소 비율 - MaximumZoomRate

 

        /// <summary>

        /// 최대 확대/축소 비율

        /// </summary>

        protected virtual double MaximumZoomRate

        {

            get

            {

                return 4.0;

            }

        }

 

        #endregion

        #region 최소 확대/축소 비율 - MinimumZoomRate

 

        /// <summary>

        /// 최소 확대/축소 비율

        /// </summary>

        protected virtual double MinimumZoomRate

        {

            get

            {

                return 0.5;

            }

        }

 

        #endregion

 

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

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

 

        #region 렌더링시 처리하기 - OnRender(context)

 

        /// <summary>

        /// 렌더링시 처리하기

        /// </summary>

        /// <param name="context">드로잉 컨텍스트</param>

        protected override void OnRender(DrawingContext context)

        {

            base.OnRender(context);

 

            double zoomRateInterval = MaximumZoomRate - MinimumZoomRate;

 

            double translatedZoomRate = ZoomRate;

 

            if(ZoomRate <= MinimumZoomRate)

            {

                translatedZoomRate += zoomRateInterval;

            }

            else if(ZoomRate >= MaximumZoomRate)

            {

                translatedZoomRate -= zoomRateInterval;

            }

 

            double scaledCellSize = CellSize * translatedZoomRate;

            double scaledGridSize = scaledCellSize * CellCount;

            double templateSize   = scaledGridSize + (GridBorder / 2);

 

            double offsetX = 0.0;

            double offsetY = 0.0;

 

            DrawingBrush imageBrush = new DrawingBrush()

            {

                TileMode      = TileMode.Tile,

                Viewport      = new Rect(offsetX + GridOffsetX, offsetY + GridOffsetY, templateSize, templateSize),

                ViewportUnits = BrushMappingMode.Absolute,

                Viewbox       = new Rect(0, 0, templateSize, templateSize),

                ViewboxUnits  = BrushMappingMode.Absolute

            };

 

            DrawingGroup drawingGroup = new DrawingGroup();

 

            Brush cellBrush = CellBrush.Clone();

 

            double cellBrushOpacity = cellBrush.Opacity * (1.0 - ((MaximumZoomRate - translatedZoomRate) / zoomRateInterval));

 

            cellBrush.Opacity = cellBrushOpacity;

 

            Brush borderBrush = GridBrush.Clone();

 

            double borderBrushOpacity = borderBrush.Opacity * (this.MaximumZoomRate - translatedZoomRate) / zoomRateInterval;

 

            borderBrush.Opacity = borderBrushOpacity;

 

            Pen gridPen = new Pen(GridBrush, GridBorder);

            Pen cellPen = new Pen(cellBrush, CellBorder);

 

            using(DrawingContext groupContext = drawingGroup.Open())

            {

                groupContext.DrawLine(gridPen, new Point(scaledGridSize, 0), new Point(scaledGridSize, templateSize));

                groupContext.DrawLine(gridPen, new Point(0, scaledGridSize), new Point(templateSize, scaledGridSize));

 

                for(int i = 1; i < this.CellCount; i++)

                {

                    double offset = (double)((int)(scaledCellSize * i));

 

                    groupContext.DrawLine(cellPen, new Point(offset, 0), new Point(offset, templateSize));

                }

 

                for(int i = 1; i < this.CellCount; i++)

                {

                    double offset = (double)((int)(scaledCellSize * i));

 

                    groupContext.DrawLine(cellPen, new Point(0, offset), new Point(templateSize, offset));

                }

            }

 

            imageBrush.Drawing = drawingGroup;

 

            context.DrawRectangle(imageBrush, null, new Rect(0, 0, this.ActualWidth, this.ActualHeight));

        }

 

        #endregion

    }

}

 

 

DraggableCanvas.cs

 

 

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

using System.Windows.Media;

using System.Xaml;

 

namespace TestProject

{

    /// <summary>

    /// 드래그 가능한 캔버스

    /// </summary>

    [TemplatePart(Name = "PART_RootCanvas", Type = typeof(MovableCanvas))]

    public class DraggableCanvas : TreeView

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property

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

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

 

        #region 확대/축소 비율 속성 - ZoomRateProperty

 

        /// <summary>

        /// 확대/축소 비율 속성

        /// </summary>

        public static readonly DependencyProperty ZoomRateProperty = DependencyProperty.Register

        (

            "ZoomRate",

            typeof(double),

            typeof(DraggableCanvas),

            new PropertyMetadata(1.0)

        );

 

        #endregion

        #region 확대/축소 간격 속성 - ZoomIntervalProperty

 

        /// <summary>

        /// 확대/축소 간격 속성

        /// </summary>

        public static readonly DependencyProperty ZoomIntervalProperty = DependencyProperty.Register

        (

            "ZoomInterval",

            typeof(double),

            typeof(DraggableCanvas),

            new PropertyMetadata(0.02)

        );

 

        #endregion

        #region 최소 확대/축소 비율 속성 - MinimumZoomRateProperty

 

        /// <summary>

        /// 최소 확대/축소 비율 속성

        /// </summary>

        public static readonly DependencyProperty MinimumZoomRateProperty = DependencyProperty.Register

        (

            "MinimumZoomRate",

            typeof(double),

            typeof(DraggableCanvas),

            new PropertyMetadata(0.5)

        );

 

        #endregion

        #region 최대 확대/축소 비율 속성 - MaximumZoomRateProperty

 

        /// <summary>

        /// 최대 확대/축소 비율 속성

        /// </summary>

        public static readonly DependencyProperty MaximumZoomRateProperty = DependencyProperty.Register

        (

            "MaximumZoomRate",

            typeof(double),

            typeof(DraggableCanvas),

            new PropertyMetadata(3.0)

        );

 

        #endregion

 

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

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

 

        #region Field

 

        /// <summary>

        /// 이동 가능한 캔버스

        /// </summary>

        private MovableCanvas movableCanvas = null;

 

        #endregion

 

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

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

 

        #region 확대/축소 비율 - ZoomRate

 

        /// <summary>

        /// 확대/축소 비율

        /// </summary>

        public double ZoomRate

        {

            get

            {

                return (double)GetValue(ZoomRateProperty);

            }

            set

            {

                SetValue(ZoomRateProperty, value);

            }

        }

 

        #endregion

        #region 확대/축소 간격 - ZoomInterval

 

        /// <summary>

        /// 확대/축소 간격

        /// </summary>

        public double ZoomInterval

        {

            get

            {

                return (double)GetValue(ZoomIntervalProperty);

            }

            set

            {

                SetValue(ZoomIntervalProperty, value);

            }

        }

 

        #endregion

        #region 최소 확대/축소 비율 - MinimumZoomRate

 

        /// <summary>

        /// 최소 확대/축소 비율

        /// </summary>

        public double MinimumZoomRate

        {

            get

            {

                return (double)GetValue(MinimumZoomRateProperty);

            }

            set

            {

                SetValue(MinimumZoomRateProperty, value);

            }

        }

 

        #endregion

        #region 최대 확소/축소 비율 - MaximumZoomRate

 

        /// <summary>

        /// 최대 확소/축소 비율

        /// </summary>

        public double MaximumZoomRate

        {

            get

            {

                return (double)GetValue(MaximumZoomRateProperty);

            }

            set

            {

                SetValue(MaximumZoomRateProperty, value);

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

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

 

        #region 생성자 - DraggableCanvas()

 

        /// <summary>

        /// 생성자

        /// </summary>

        static DraggableCanvas()

        {

            DefaultStyleKeyProperty.OverrideMetadata

            (

                typeof(DraggableCanvas),

                new FrameworkPropertyMetadata(typeof(DraggableCanvas))

            );

        }

 

        #endregion

 

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

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

 

        #region 템플리트 적용시 처리하기 - OnApplyTemplate()

 

        /// <summary>

        /// 템플리트 적용시 처리하기

        /// </summary>

        public override void OnApplyTemplate()

        {

            DependencyObject dependencyObject = this.GetTemplateChild("PART_RootCanvas");

 

            if(dependencyObject is MovableCanvas == false)

            {

                throw new XamlParseException("'PART_RootCanvas' cannot be found.");

            }

 

            this.movableCanvas = dependencyObject as MovableCanvas;

 

            base.OnApplyTemplate();

        }

 

        #endregion

        #region 이동시키기 - MoveTo(item)

 

        /// <summary>

        /// 이동시키기

        /// </summary>

        /// <param name="item">항목</param>

        public void MoveTo(FrameworkElement item)

        {

            if(item == null)

            {

                return;

            }

 

            Point itemPoint = new Point();

 

            if(item is TreeViewItem && (item as TreeViewItem).HasHeader == true && (item as TreeViewItem).Header is FrameworkElement)

            {

                FrameworkElement itemHeader = (item as TreeViewItem).Header as FrameworkElement;

 

                itemPoint = item.TranslatePoint

                (

                    new Point(itemHeader.ActualWidth / 2, itemHeader.ActualHeight / 2),

                    this

                );

            }

            else

            {

                Rect boundRectangle = VisualTreeHelper.GetDescendantBounds(item);

 

                itemPoint = item.TranslatePoint

                (

                    new Point(boundRectangle.Width / 2, boundRectangle.Height / 2),

                    this

                );

            }

 

            this.movableCanvas.OffsetX = (this.movableCanvas.ActualWidth  / 2) - (itemPoint.X - this.movableCanvas.OffsetX);

            this.movableCanvas.OffsetY = (this.movableCanvas.ActualHeight / 2) - (itemPoint.Y - this.movableCanvas.OffsetY);

        }

 

        #endregion

 

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

 

        #region 마우스 WHEEL 처리하기 - OnMouseWheel(e)

 

        /// <summary>

        /// 마우스 WHEEL 처리하기

        /// </summary>

        /// <param name="e">이벤트 발생자</param>

        protected override void OnMouseWheel(MouseWheelEventArgs e)

        {

            if(e.Delta > 0)

            {

                double zoomRate = ZoomRate + ZoomInterval;

 

                ZoomRate = (zoomRate > MaximumZoomRate) ? MaximumZoomRate : zoomRate;

            }

            else if(e.Delta < 0)

            {

                double zoomRate = ZoomRate - ZoomInterval;

 

                ZoomRate = (zoomRate < MinimumZoomRate) ? MinimumZoomRate : zoomRate;

            }

 

            base.OnMouseWheel(e);

        }

 

        #endregion

    }

}

 

 

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:local="clr-namespace:TestProject"

    Width="800"

    Height="600"

    Title="확대/축소/이동/드래그 가능한 캔버스 만들기"

    FontFamily="나눔고디코딩"

    FontSize="16">

    <local:DraggableCanvas

        Margin="10"

        Background="LightGray"

        ClipToBounds="True">

        <TreeViewItem Canvas.Left="10" Canvas.Top="10">

            <TreeViewItem.Header>

                <Rectangle Width="100" Height="100" Fill="Red" />

            </TreeViewItem.Header>

            <TreeViewItem Canvas.Left="150" Canvas.Top="0">

                <TreeViewItem.Header>

                    <Ellipse Width="100" Height="100" Fill="Orange" />

                </TreeViewItem.Header>

            </TreeViewItem>

            <TreeViewItem Canvas.Left="0" Canvas.Top="150">

                <TreeViewItem.Header>

                    <Ellipse Width="100" Height="100" Fill="Yellow" />

                </TreeViewItem.Header>

            </TreeViewItem>

            <TreeViewItem Canvas.Left="150" Canvas.Top="150">

                <TreeViewItem.Header>

                    <Border>

                        <Ellipse Width="100" Height="100" Fill="Green" />

                    </Border>

                </TreeViewItem.Header>

                <TreeViewItem Canvas.Left="150" Canvas.Top="0">

                    <TreeViewItem.Header>

                        <Rectangle Width="100" Height="100" Fill="Blue" />

                    </TreeViewItem.Header>

                </TreeViewItem>

                <TreeViewItem Canvas.Left="0" Canvas.Top="150">

                    <TreeViewItem.Header>

                        <Rectangle Width="100" Height="100" Fill="Purple" />

                    </TreeViewItem.Header>

                </TreeViewItem>

            </TreeViewItem>

        </TreeViewItem>

        <TreeViewItem>

            <TreeViewItem Canvas.Left="310" Canvas.Top="310">

                <Ellipse Width="100" Height="100" Fill="Navy" />

            </TreeViewItem>

        </TreeViewItem>

    </local:DraggableCanvas>

</Window>

 

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

※ 패키지 설치 : Expression.Blend.Sdk

 

1. 비주얼 스튜디오를 실행한다.

2. 비주얼 스튜디오에서 [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 클릭한다.

3. [패키지 관리자 콘솔]에서 아래 명령을 실행한다.

 

 

Install-Package Expression.Blend.Sdk

 

 

728x90
반응형
Posted by 사용자 icodebroker
TAG , , ,

댓글을 달아 주세요