지난 글에 이어서 Polyline을 그린 후 클릭 이벤트를 적용하는 방법에 대해 찾아보았고 Microsoft.Xaml.Behaviors.Wpf를 사용하기로 했다. 더불어 이 글의 댓글에서 지적해주셨던 것 처럼 Behavior의 정석 작성법은 ViewModel에서 작성한 Command를 호출하는 것이 아니라 Behavior<>를 상속받아 OnAttach() 와 OnDetaching()를 작성하는 것이 올바른 방법이기에 이 또한 적용했다.
NuGet 패키지 관리에서 Microsoft.Xaml.Behaviors.Wpf 설치
Behaviors 폴더 생성한 후 MouseBehavior.cs 파일 생성하기.
폴더를 따로 안만들어도 상관 없다. local:로 접근하면 되니까...
XAML에 네임스페이스를 작성한다.
나는 local이 아니라 bh로 Behavior에 접근하려고 한다.
내가 Behavior를 작성하는 이유는 Polyline에는 Button처럼 마우스 왼쪽 클릭을 했을 때 Command를 적용할 방법이 없기 때문이다.
최대한 MVVM 패턴을 준수하며 작성하고 싶었기 때문에 코드 비하인드를 사용하지 않고 싶었다.
: Behavior<T>
에서 T는 Behavior를 적용할 Control이 된다. Polyline에 적용하기 때문에 : Behavior<Polyline>
이 된다.
public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
"MouseX", typeof(double), typeof(MouseBehavior), new PropertyMetadata(default(double)));
public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
"MouseY", typeof(double), typeof(MouseBehavior), new PropertyMetadata(default(double)));
MouseX, MouseY 라는 DependencyProperty를 추가하면 XAML의 Behavior 작성시 속성으로 사용할 수 있게 된다.
코드 비하인드에서 Mouse 동작과 관련한 이벤트핸들러를 추가할 때에는 +=연산자를, 삭제할 때에는 -=를 사용한 적이 있는데 그것과 같다.
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += AssociatedObjectOnMouseLeftButtonDown;
}
protected override void OnDetaching()
{
AssociatedObject.MouseLeftButtonDown -= AssociatedObjectOnMouseLeftButtonDown;
}
이벤트핸들러의 동작을 작성한다. MouseLeftButtonDown 동작이 실행되면 현재 마우스 Position을 가져온다.
private void AssociatedObjectOnMouseLeftButtonDown(object sender, MouseEventArgs mouseEventArgs)
{
var mousePoint = mouseEventArgs.GetPosition(AssociatedObject);
MouseX = mousePoint.X;
MouseY = mousePoint.Y;
}
지난 글에서의 ViewModel에 바인딩 할 PanelX, PanelY를 추가한다.
private double _panelX;
public double PanelX
{
get { return _panelX; }
set
{
_panelX = value;
OnPropertyChanged(nameof(PanelX));
}
}
private double _panelY;
public double PanelY
{
get { return _panelY; }
set
{
_panelY = value;
OnPropertyChanged(nameof(PanelY));
}
}
지난 글에서의 Xaml에 Behavior를 추가한다.
<Polyline Points="{Binding}" Stroke="Red" Fill="Transparent" StrokeThickness="2" Cursor="Hand">
<i:Interaction.Behaviors>
<bh:MouseBehavior MouseX="{Binding DataContext.PanelX, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=OneWayToSource}"
MouseY="{Binding DataContext.PanelY, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</Polyline>
Mouse의 Position값을 알기 위해 작성한 PanelX, PanelY의 값을 확인하려면 상단에 TextBlock을 추가한다.
<Grid Grid.Row="0" VerticalAlignment="Bottom">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock Text="{Binding PanelX, StringFormat='X={0} '}"/>
<TextBlock Text="{Binding PanelY, StringFormat='Y={0}'}"/>
</StackPanel>
</Grid>
Polyline의 Fill을 Transparent로 설정하고 Cursor를 Hand로 설정하여 마치 Button을 클릭하는 느낌이 됐다.
Polyline이 그려진 부분을 클릭하면 위에 배치한 TextBlock에 바인딩 된 X값과 Y값을 확인할 수 있다.
처음엔 Behavior작성 시 PanelX, PanelY를 바인딩 할 때 아래와 같이 작성했었는데
<bh:MouseBehavior MouseX="{Binding Path=DataContext.PanelX, Mode=OneWayToSource, RelativeSource={RelativeSource AncestorType={x:Type Polyline}}}"
MouseY="{Binding Path=DataContext.PanelY, Mode=OneWayToSource, RelativeSource={RelativeSource AncestorType={x:Type Polyline}}}" />
다음과 같은 에러가 발생했다.
System.Windows.Data Error: 40 : BindingExpression path error: 'PanelX' property not found on 'object' ''PointCollection' (HashCode=30046694)'. BindingExpression:Path=DataContext.PanelX; DataItem='Polyline' (Name=''); target element is 'MouseBehavior' (HashCode=11318800); target property is 'MouseX' (type 'Double')
Console을 보면 Polyline이 그려진 개 수만큼 PanelX, PanelY가 짝으로 에러가 발생하는 것을 알 수 있다.
찾아본 결과 stack overflow에서 해결 방법을 찾을 수 있었다.
DataContext의 바인딩 순서가 문제였던 듯 하여 본문의 내용과 같은 순서로 작성했더니 에러가 해결 되었다.
<bh:MouseBehavior MouseX="{Binding DataContext.PanelX, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=OneWayToSource}"
MouseY="{Binding DataContext.PanelY, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=OneWayToSource}" />
사실 실행을 했을 때는 문제가 없어 보이는데 어쨌든 콘솔에 출력된 에러라면 해결해야하는 것이니까...
Behavior 작성
에러 해결
포스트 잘 보았습니다 ~!
Behavior 관련 이글도 한번 확인 해 보시면 좋을것 같아요 ~
https://forum.dotnetdev.kr/t/wpf-behavior-onattached/11026
메모리 누수 문제도 걱정 해야하고,
이벤트가 중복으로 등록되는 문제도 한번 확인해보시면 좋을것 같습니다