첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
본 블로그는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 블로그 콘텐츠 향상을 위해 쓰여집니다.

728x90
반응형
728x170

TestProject.zip
0.02MB

▶ 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_FloatingGrid"
                        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
    }
}

 

▶ FloatingGrid

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 , ,

댓글을 달아 주세요