검색결과 리스트
글
Dragable Canvas 만들기
목차
지난 포스팅(http://crystalcube.co.kr/152)에서 만든 CanvasTreeView 를 가지고 기능을 추가해 보도록 하겠습니다.
먼저 앞선 포스팅을 읽어보시는게 좋을 것 같습니다. :)
추가해 볼 기능은, 마우스로 Drag 하는 기능입니다. 단순히 Canvas 에 그리는 것이 아니라, 마우스로 움직일 수 있도록 하려는 것입니다. 실제로 포토샵, 파워포인트 같은 툴들처럼 마우스로 이리저리 움직이는 그런 흔한 것들 말입니다. :)
개요
이 캔버스의 이름은 DragableCanvas 라고 하겠습니다.
어떻게 하면 좋을까요?
1차 시도.
단순히 DragableCanvas 의 OnMouseLeftButtonDown 등의 이벤트를 override 해서 구현한다.
이 경우 해당 핸들러까지 이벤트가 넘어오지 않습니다 -_-; OnPreviewMouseLeftButtonDown 과 같은 Preview 메소드 들을 사용하면 가능하긴 합니다. 하지만 Canvas 위에 올라가는 각종 UIElement 들과 충돌이 생기고 관리가 지저분해지기 시작합니다. 재양의 시작이라고 해 두죠.
2차 시도.
GragableCanvas 에 Child 가 추가될때마다, 각 Child 에 Mouse 관련 이벤트 핸들러들을 달아서, Drag 가능토록 한다.
말만 들어도 끔직합니다. 매번 핸들러를 추가/삭제 하기도 힘들 뿐더러, 코드도 지저분해지고...메모리 릭도 걱정되고..
위에 언급한 방법들을 이래저래 해 보았는데, 엄청 까다롭습니다.
고민끝에 찾아낸 방법은.
Behavior 를 사용하는 것입니다. Template 안에서 Behavior 를 붙여주면, 핸들러나 메모리 릭에 대한 부담이 적어지죠.
그래서 그렇게 구현해 보았습니다. 그랬러니...
이번엔 이벤트가 문제입니다.
AssociatedObject 에서 MouseMove 를 처리하다보니, 다른 Child 에 가려지는 순간 이벤트가 더이상 넘어오지 않아, Drag 가 멈추어 버립니다. 그리고 마우스를 빠르게 움직여서 해당 AssociatedObject 밖으로 마우스가 순간적으로 빠져나가면... 마찬가지로 Drag 가 끝나버립니다.
이런 이유로, Behavior 에다가 움직일 Target(움직일 UIElement) 과 BaseUIElement(마우스 캡쳐 베이스) 디펜던시 프로퍼티를 추가해 주었습니다. 잘 동작하네요 :)
코드는 다음과 같습니다.
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 DragableCanvas : TreeView
{
static DragableCanvas()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DragableCanvas), new FrameworkPropertyMetadata(typeof(DragableCanvas)));
}
}
}
<Style TargetType="{x:Type local:DragableCanvas}">
<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>
<behaviors:DragOnCanvasBehavior BaseUIElement="{Binding RelativeSource={RelativeSource AncestorType=local:DragableCanvas}}"
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>
<ScrollViewer Background="{TemplateBinding Background}" x:Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
PanningRatio="{TemplateBinding ScrollViewer.PanningRatio}"
PanningMode="{TemplateBinding ScrollViewer.PanningMode}"
Focusable="False">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
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.Interactivity;
namespace SW.Common.Behaviors
{
public class DragOnCanvasBehavior : Behavior<UIElement>
{
public UIElement BaseUIElement
{
get { return (UIElement)GetValue(BaseUIElementProperty); }
set { SetValue(BaseUIElementProperty, value); }
}
// Using a DependencyProperty as the backing store for BaseUIElement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BaseUIElementProperty =
DependencyProperty.Register("BaseUIElement", typeof(UIElement), typeof(DragOnCanvasBehavior), new PropertyMetadata(null));
public UIElement Target
{
get { return (UIElement)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(UIElement), 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)
{
UIElement captureBase = (this.BaseUIElement ?? this.AssociatedObject);
captureBase.MouseMove += OnMouseMove;
captureBase.MouseLeftButtonUp += OnMouseLeftButtonUp;
UIElement 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)
{
UIElement captureBase = (this.BaseUIElement ?? this.AssociatedObject);
UIElement 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;
UIElement captureBase = (this.BaseUIElement ?? this.AssociatedObject);
captureBase.ReleaseMouseCapture();
captureBase.MouseMove -= OnMouseMove;
captureBase.MouseLeftButtonUp -= OnMouseLeftButtonUp;
}
}
}
참, Behavior 를 사용하기 위해서는, System.Windows.Interactivity 를 참조추가 해 주어야 한다는걸 잊지 않으시길 바랍니다. :)
'Microsoft > WPF' 카테고리의 다른 글
격자 배경 그리기 (0) | 2014.11.14 |
---|---|
Expanding Canvas 만들기 (2) | 2014.11.14 |
TreeView Template 을 통한 Canvas 만들기 (1) | 2014.11.12 |
MEF(Managed Extensibility Framework) (2) | 2014.05.19 |
화면에 노출되는 프로퍼티는? (1) | 2014.05.13 |
RECENT COMMENT