Draggable Canvas ver 0.9




목차


앞서 시리즈로 계속 포스팅 하던 것을 정리해 보았습니다.

요약하자면, Canvas 형태로서, TreeView 처럼 Element 들이 Hierarchy 를 가지면서 노출됩니다. 마우스 왼쪽 버튼을 이용해서 이동이 가능하며, 마우스 가운데 버튼을 누른채로 Canvas 를 이동시킬 수 있습니다. 더불어 마우스의 휠을 이용하여 Zoom In/Out 을 할 수 있습니다.









개요


이전 포스팅에서 구현했던 것들을 mix 해서 만들었습니다.

ver 0.9 라고 명시한 것은, 아직 알수 없는 버그들이 존재할 수 있기 때문도 있지만, Zoom 의 Origin 이 고정되어 있기 때문입니다. 화면 한 가운데를 Origin 으로 하여, ZoomIn/Out 하고 싶었는데 아직은 미구현 상태이네요. 다음에 기회가 된다면 이 기능 또한 넣어 보도록 하겠습니다.


소스코드입니다.

namespace 라든지 일부 경로는 본인에게 맞게 수정하여야 할지 모릅니다.



    [Main]


<Window x:Class="TestMain.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sw="http://wpf.sungwook.kim/common"
        Title="MainWindow" Height="350" Width="525">
    <sw:DraggableCanvas Background="#AAAAAA">
        <TreeViewItem Canvas.Left="10" Canvas.Top="10">
            <TreeViewItem.Header>
                <Border BorderBrush="White" BorderThickness="3">
                    <Grid Width="150" Height="150" Background="Red"/>
                </Border>
            </TreeViewItem.Header>
            <TreeViewItem  Canvas.Left="170">
                <TreeViewItem.Header>
                    <Grid Width="150" Height="150" Background="Orange"/>
                </TreeViewItem.Header>
            </TreeViewItem>
            <TreeViewItem Canvas.Left="350">
                <TreeViewItem.Header>
                    <Grid Width="100" Height="100" Background="Yellow"/>
                </TreeViewItem.Header>
            </TreeViewItem>
            <TreeViewItem Canvas.Left="10" Canvas.Top="200">
                <TreeViewItem.Header>
                    <Border BorderBrush="White" BorderThickness="3" >
                        <Grid Width="150" Height="150" Background="Green" />
                    </Border>
                </TreeViewItem.Header>
                <TreeViewItem  Canvas.Left="200" >
                    <TreeViewItem.Header>
                        <Grid Width="150" Height="150" Background="Blue"/>
                    </TreeViewItem.Header>
                </TreeViewItem>
                <TreeViewItem Canvas.Left="0" Canvas.Top="100">
                    <TreeViewItem.Header>
                        <Grid Width="100" Height="100" Background="Purple"/>
                    </TreeViewItem.Header>
                </TreeViewItem>
            </TreeViewItem>
        </TreeViewItem>
        <TreeViewItem>
            <TreeViewItem Canvas.Left="380" Canvas.Top="210">
                <Grid Background="Navy" Width="100" Height="100"/>
            </TreeViewItem>
        </TreeViewItem>
    </sw:DraggableCanvas>


</Window>



    [DragOnCanvasBehavior.cs]


using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;


namespace SW.Common.Behaviors
{
    public class DragOnCanvasBehavior : Behavior<FrameworkElement>
    {
        public FrameworkElement BaseElement
        {
            get { return (FrameworkElement)GetValue(BaseElementProperty); }
            set { SetValue(BaseElementProperty, value); }
        }

        // Using a DependencyProperty as the backing store for BaseUIElement.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BaseElementProperty =
            DependencyProperty.Register("BaseElement", typeof(FrameworkElement), typeof(DragOnCanvasBehavior), new PropertyMetadata(null));




        public FrameworkElement Target
        {
            get { return (FrameworkElement)GetValue(TargetProperty); }
            set { SetValue(TargetProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Target.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TargetProperty =
            DependencyProperty.Register("Target", typeof(FrameworkElement), typeof(DragOnCanvasBehavior), new PropertyMetadata(null));




        protected override void OnAttached()
        {
            this.AssociatedObject.MouseLeftButtonDown += OnMouseLeftButtonDown;

            base.OnAttached();
        }



        protected override void OnDetaching()
        {
            this.AssociatedObject.MouseLeftButtonDown -= OnMouseLeftButtonDown;

            base.OnDetaching();
        }


        private Point _initMousePosition;
        private Point _initItemPosition;
        private bool _onDrag = false;

        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (this._onDrag == false)
            {
                FrameworkElement captureBase = (this.BaseElement ?? this.AssociatedObject);
                captureBase.MouseMove += OnMouseMove;
                captureBase.MouseLeftButtonUp += OnMouseLeftButtonUp;

                FrameworkElement target = (this.Target ?? this.AssociatedObject);
                this._onDrag = true;
                this._initMousePosition = e.GetPosition(captureBase);
                double x = Canvas.GetLeft(target);
                double y = Canvas.GetTop(target);
                x = double.IsNaN(x) ? 0 : x;
                y = double.IsNaN(y) ? 0 : y;

                this._initItemPosition = new Point(x, y);
                captureBase.CaptureMouse();
            }
        }


        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed && this._onDrag == true)
            {
                FrameworkElement captureBase = (this.BaseElement ?? this.AssociatedObject);
                FrameworkElement target = (this.Target ?? this.AssociatedObject);

                Vector delta = e.GetPosition(captureBase) - this._initMousePosition;
                Canvas.SetLeft(target, this._initItemPosition.X + delta.X);
                Canvas.SetTop(target, this._initItemPosition.Y + delta.Y);
            }
        }



        private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            this._onDrag = false;

            FrameworkElement captureBase = (this.BaseElement ?? this.AssociatedObject);
            captureBase.ReleaseMouseCapture();

            captureBase.MouseMove -= OnMouseMove;
            captureBase.MouseLeftButtonUp -= OnMouseLeftButtonUp;
        }
    }
}



    [DraggableCanvas.cs]


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Xaml;


namespace SW.Common.Controls
{
    [TemplatePart(Name = "PART_RootCanvas", Type = typeof(MovableCanvas))]
    public class DraggableCanvas : TreeView
    {
        public double ZoomRate
        {
            get { return (double)GetValue(ZoomRateProperty); }
            set { SetValue(ZoomRateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomRate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomRateProperty =
            DependencyProperty.Register("ZoomRate", typeof(double), typeof(DraggableCanvas), new PropertyMetadata(1.0));




        public double ZoomInterval
        {
            get { return (double)GetValue(ZoomIntervalProperty); }
            set { SetValue(ZoomIntervalProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomInterval.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomIntervalProperty =
            DependencyProperty.Register("ZoomInterval", typeof(double), typeof(DraggableCanvas), new PropertyMetadata(0.02));




        public double MaxZoomRate
        {
            get { return (double)GetValue(MaxZoomRateProperty); }
            set { SetValue(MaxZoomRateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MaxZoomRate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MaxZoomRateProperty =
            DependencyProperty.Register("MaxZoomRate", typeof(double), typeof(DraggableCanvas), new PropertyMetadata(3.0));



        public double MinZoomRate
        {
            get { return (double)GetValue(MinZoomRateProperty); }
            set { SetValue(MinZoomRateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinZoomRate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinZoomRateProperty =
            DependencyProperty.Register("MinZoomRate", typeof(double), typeof(DraggableCanvas), new PropertyMetadata(0.5));




        private MovableCanvas _rootCanvas = null;

        static DraggableCanvas()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DraggableCanvas), new FrameworkPropertyMetadata(typeof(DraggableCanvas)));
        }


        public override void OnApplyTemplate()
        {
            DependencyObject canvas = this.GetTemplateChild("PART_RootCanvas");
            if (canvas is MovableCanvas == false) { throw new XamlParseException("'PART_RootCanvas' cannot be found."); }

            this._rootCanvas = canvas as MovableCanvas;

            base.OnApplyTemplate();
        }


        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            if (e.Delta > 0)
            {
                double zoomRate = this.ZoomRate + this.ZoomInterval;
                this.ZoomRate = (zoomRate > this.MaxZoomRate ? this.MaxZoomRate : zoomRate);
            }
            else if (e.Delta < 0)
            {
                double zoomRate = this.ZoomRate - this.ZoomInterval;
                this.ZoomRate = (zoomRate < this.MinZoomRate ? this.MinZoomRate : zoomRate);
            }
            base.OnMouseWheel(e);
        }



        public void MoveTo(FrameworkElement item)
        {
            if (item == null) {  return; }

            Point itemCenter = new Point();
            if(item is TreeViewItem && (item as TreeViewItem).HasHeader == true && (item as TreeViewItem).Header is FrameworkElement)
            {
                FrameworkElement targetElement = (item as TreeViewItem).Header as FrameworkElement;
                itemCenter = item.TranslatePoint(new Point(targetElement.ActualWidth / 2, targetElement.ActualHeight / 2), this);
            } else {
                Rect bounds = VisualTreeHelper.GetDescendantBounds(item);
                itemCenter = item.TranslatePoint(new Point(bounds.Width / 2, bounds.Height / 2), this);
            }

            this._rootCanvas.OffsetX = (this._rootCanvas.ActualWidth / 2) - (itemCenter.X - this._rootCanvas.OffsetX);
            this._rootCanvas.OffsetY = (this._rootCanvas.ActualHeight / 2) - (itemCenter.Y - this._rootCanvas.OffsetY);
        }
    }
}



    [DraggableCanvas.xaml]


<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:SW.Common.Controls"
                    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
                    xmlns:sw="http://wpf.sungwook.kim/common">
    <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 Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}"
                                                              Canvas.Left="{Binding (Canvas.Left)}"
                                                              Canvas.Right="{Binding (Canvas.Right)}"
                                                              Canvas.Top="{Binding (Canvas.Top)}"
                                                              Canvas.Bottom="{Binding (Canvas.Bottom)}">
                                                <i:Interaction.Behaviors>
                                                    <sw:DragOnCanvasBehavior BaseElement="{Binding RelativeSource={RelativeSource AncestorType=local:MovableCanvas}}"
                                                                                    Target="{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 Background="Transparent" x:Name="PART_RootCanvas" 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>




    [FlottingGrid.cs]


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;


namespace SW.Common.Controls
{
    public class FlottingGrid : Grid
    {
        public double ZoomRate
        {
            get { return (double) GetValue(ZoomRateProperty); }
            set { SetValue(ZoomRateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomRate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomRateProperty =
            DependencyProperty.Register("ZoomRate", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));


        public int CellCount
        {
            get { return (int) GetValue(CellCountProperty); }
            set { SetValue(CellCountProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CellCount.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CellCountProperty =
            DependencyProperty.Register("CellCount", typeof(int), typeof(FlottingGrid), new FrameworkPropertyMetadata(10, FrameworkPropertyMetadataOptions.AffectsRender));




        public double CellSize
        {
            get { return (double) GetValue(CellSizeProperty); }
            set { SetValue(CellSizeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CellSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CellSizeProperty =
            DependencyProperty.Register("CellSize", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(20.0, FrameworkPropertyMetadataOptions.AffectsRender));



        public Brush GridBrush
        {
            get { return (Brush) GetValue(GridBrushProperty); }
            set { SetValue(GridBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GridBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GridBrushProperty =
            DependencyProperty.Register("GridBrush", typeof(Brush), typeof(FlottingGrid), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)), FrameworkPropertyMetadataOptions.AffectsRender));




        public Brush CellBrush
        {
            get { return (Brush) GetValue(CellBrushProperty); }
            set { SetValue(CellBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CellBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CellBrushProperty =
            DependencyProperty.Register("CellBrush", typeof(Brush), typeof(FlottingGrid), new FrameworkPropertyMetadata(new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)), FrameworkPropertyMetadataOptions.AffectsRender));



        public double GridBorder
        {
            get { return (double) GetValue(GridBorderProperty); }
            set { SetValue(GridBorderProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GridBorder.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GridBorderProperty =
            DependencyProperty.Register("GridBorder", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));




        public double CellBorder
        {
            get { return (double) GetValue(CellBorderProperty); }
            set { SetValue(CellBorderProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CellBorder.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CellBorderProperty =
            DependencyProperty.Register("CellBorder", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));


        public double GridOffsetX
        {
            get { return (double) GetValue(GridOffsetXProperty); }
            set { SetValue(GridOffsetXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GridOffsetX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GridOffsetXProperty =
            DependencyProperty.Register("GridOffsetX", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));




        public double GridOffsetY
        {
            get { return (double) GetValue(GridOffsetYProperty); }
            set { SetValue(GridOffsetYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for GridOffsetY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty GridOffsetYProperty =
            DependencyProperty.Register("GridOffsetY", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));





        public double ZoomCenterX
        {
            get { return (double) GetValue(ZoomCenterXProperty); }
            set { SetValue(ZoomCenterXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomCenterX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomCenterXProperty =
            DependencyProperty.Register("ZoomCenterX", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(0.5, FrameworkPropertyMetadataOptions.AffectsRender));





        public double ZoomCenterY
        {
            get { return (double) GetValue(ZoomCenterYProperty); }
            set { SetValue(ZoomCenterYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ZoomCenterY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ZoomCenterYProperty =
            DependencyProperty.Register("ZoomCenterY", typeof(double), typeof(FlottingGrid), new FrameworkPropertyMetadata(0.5, FrameworkPropertyMetadataOptions.AffectsRender));


        protected virtual double MaxZoomRate { get { return 4.0; } }
        protected virtual double MinZoomRate { get { return 0.5; } }



        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            double intervalOfMaxZoomRate = this.MaxZoomRate - this.MinZoomRate;

            double translatedZoomRate = this.ZoomRate;
            if (this.ZoomRate <= this.MinZoomRate) { translatedZoomRate += intervalOfMaxZoomRate; }
            else if (this.ZoomRate >= this.MaxZoomRate) { translatedZoomRate -= intervalOfMaxZoomRate; }

            double scaledCellSize = this.CellSize * translatedZoomRate;
            double scaledGridSize = scaledCellSize * this.CellCount;
            double templateSize = scaledGridSize + (this.GridBorder / 2);

            //double offsetX = (this.ZoomCenterX * this.ActualWidth);
            //double offsetY = (this.ZoomCenterY * this.ActualHeight);
            double offsetX = 0.0;
            double offsetY = 0.0;

            DrawingBrush imageBrush = new DrawingBrush() {TileMode = TileMode.Tile, Viewbox = new Rect(0, 0, templateSize, templateSize), Viewport = new Rect(offsetX + this.GridOffsetX, offsetY + this.GridOffsetY, templateSize, templateSize), ViewportUnits = BrushMappingMode.Absolute, ViewboxUnits = BrushMappingMode.Absolute};
            DrawingGroup drawingGroup = new DrawingGroup();

            Brush cellBrush = this.CellBrush.Clone();
            double cellBrushOpacity = cellBrush.Opacity * (1.0 - ((this.MaxZoomRate - translatedZoomRate) / intervalOfMaxZoomRate));
            cellBrush.Opacity = cellBrushOpacity;

            Brush borderBrush = this.GridBrush.Clone();
            double borderBrushOpacity = borderBrush.Opacity * (this.MaxZoomRate - translatedZoomRate) / intervalOfMaxZoomRate;
            borderBrush.Opacity = borderBrushOpacity;


            Pen gridPen = new Pen(this.GridBrush, this.GridBorder);
            Pen cellPen = new Pen(cellBrush, this.CellBorder);

            using(DrawingContext context = drawingGroup.Open())
            {
                context.DrawLine(gridPen, new Point(scaledGridSize, 0), new Point(scaledGridSize, templateSize));
                context.DrawLine(gridPen, new Point(0, scaledGridSize), new Point(templateSize, scaledGridSize));

                for(int c = 1; c < this.CellCount; c++)
                {
                    var offset = (double) ((int) (scaledCellSize * c));
                    context.DrawLine(cellPen, new Point(offset, 0), new Point(offset, templateSize));
                }

                for(int r = 1; r < this.CellCount; r++)
                {
                    var offset = (double) ((int) (scaledCellSize * r));
                    context.DrawLine(cellPen, new Point(0, offset), new Point(templateSize, offset));
                }
            }
            imageBrush.Drawing = drawingGroup;
            dc.DrawRectangle(imageBrush, null, new Rect(0, 0, this.ActualWidth, this.ActualHeight));
        }
    }
}




    [MovableCanvas]


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using MS.Internal;

namespace SW.Common.Controls
{
    public class MovableCanvas : Canvas
    {
        public double OffsetX
        {
            get { return (double)GetValue(OffsetXProperty); }
            set { SetValue(OffsetXProperty, value); }
        }

        // Using a DependencyProperty as the backing store for OffsetX.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OffsetXProperty =
            DependencyProperty.Register("OffsetX", typeof(double), typeof(MovableCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));




        public double OffsetY
        {
            get { return (double)GetValue(OffsetYProperty); }
            set { SetValue(OffsetYProperty, value); }
        }

        // Using a DependencyProperty as the backing store for OffsetY.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OffsetYProperty =
            DependencyProperty.Register("OffsetY", typeof(double), typeof(MovableCanvas), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));





        public double MovingRatio
        {
            get { return (double)GetValue(MovingRatioProperty); }
            set { SetValue(MovingRatioProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MovingRatio.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MovingRatioProperty =
            DependencyProperty.Register("MovingRatio", typeof(double), typeof(MovableCanvas), new PropertyMetadata(1.0));





        public Cursor CursorOnMove
        {
            get { return (Cursor)GetValue(CursorOnMoveProperty); }
            set { SetValue(CursorOnMoveProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Cursor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CursorOnMoveProperty =
            DependencyProperty.Register("CursorOnMove", typeof(Cursor), typeof(MovableCanvas), new PropertyMetadata(Cursors.ScrollAll));

        



        private Point _initMousePosition;
        private Point _initItemPosition;
        private bool _onDrag = false;
        private Cursor _prevCursor;


        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            if (this._onDrag == false && e.MiddleButton == MouseButtonState.Pressed)
            {
                this._onDrag = true;
                this._prevCursor = this.Cursor;
                this.Cursor = this.CursorOnMove;
                this._initMousePosition = e.GetPosition(this);

                this._initItemPosition = new Point(this.OffsetX, this.OffsetY);
                this.CaptureMouse();
            }
            base.OnMouseDown(e);
        }


        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (e.MiddleButton == MouseButtonState.Pressed && this._onDrag == true)
            {
                Vector delta = e.GetPosition(this) - this._initMousePosition;
                this.OffsetX = this._initItemPosition.X + delta.X * this.MovingRatio;
                this.OffsetY = this._initItemPosition.Y + delta.Y * this.MovingRatio;
            }

            base.OnMouseMove(e);
        }


        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            this._onDrag = false;
            this.Cursor = this._prevCursor;
            this.ReleaseMouseCapture();

            base.OnMouseUp(e);
        }



        protected override Size ArrangeOverride(Size arrangeSize)
        {
            foreach (UIElement element in this.InternalChildren)
            {
                if (element != null)
                {
                    double x = 0.0;
                    double y = 0.0;
                    double left = Canvas.GetLeft(element);
                    if (!double.IsNaN(left))
                    {
                        x = left * this.MovingRatio;
                    }
                    else
                    {
                        double right = Canvas.GetRight(element) * this.MovingRatio;
                        if (!double.IsNaN(right))
                            x = arrangeSize.Width - element.DesiredSize.Width - right;
                    }
                    double top = Canvas.GetTop(element);
                    if (!double.IsNaN(top))
                    {
                        y = top * this.MovingRatio;
                    }
                    else
                    {
                        double bottom = Canvas.GetBottom(element) * this.MovingRatio;
                        if (!double.IsNaN(bottom))
                            y = arrangeSize.Height - element.DesiredSize.Height - bottom;
                    }
                    element.Arrange(new Rect(new Point((x + this.OffsetX) / this.MovingRatio, (y + this.OffsetY) / this.MovingRatio), element.DesiredSize));
                }
            }
            return arrangeSize;
        }
    }
}





'Microsoft > WPF' 카테고리의 다른 글

TextBox 의 내용이 짤릴 경우에만, ToolTip 보이기  (2) 2014.12.26
360 파노라마 뷰어  (4) 2014.12.19
Draggable Canvas ver 0.9  (0) 2014.11.17
편집용 Canvas 만들기  (0) 2014.11.16
Expanding Canvas 만들기 2  (0) 2014.11.16
격자 배경 그리기  (0) 2014.11.14