[C# WPF] Microsoft.Xaml.Behaviors.Wpf ItemsControl로 생성된 컨트롤에 MouseLeftButtonDown Interaction.Behaviors 적용하기

우롱밀크티당도70·2024년 6월 27일
1

WPF

목록 보기
22/22
post-thumbnail
post-custom-banner

1. 배경

지난 글에 이어서 Polyline을 그린 후 클릭 이벤트를 적용하는 방법에 대해 찾아보았고 Microsoft.Xaml.Behaviors.Wpf를 사용하기로 했다. 더불어 이 글의 댓글에서 지적해주셨던 것 처럼 Behavior의 정석 작성법은 ViewModel에서 작성한 Command를 호출하는 것이 아니라 Behavior<>를 상속받아 OnAttach() 와 OnDetaching()를 작성하는 것이 올바른 방법이기에 이 또한 적용했다.


2. 개발환경

  • VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.8)

3. 내용

3-1. 환경 설정

NuGet 패키지 관리에서 Microsoft.Xaml.Behaviors.Wpf 설치

Behaviors 폴더 생성한 후 MouseBehavior.cs 파일 생성하기.
폴더를 따로 안만들어도 상관 없다. local:로 접근하면 되니까...

XAML에 네임스페이스를 작성한다.
나는 local이 아니라 bh로 Behavior에 접근하려고 한다.

3-2. Behavior 작성

내가 Behavior를 작성하는 이유는 Polyline에는 Button처럼 마우스 왼쪽 클릭을 했을 때 Command를 적용할 방법이 없기 때문이다.
최대한 MVVM 패턴을 준수하며 작성하고 싶었기 때문에 코드 비하인드를 사용하지 않고 싶었다.

3-2-1. MouseBehavior.cs는 Behavior를 상속 받는다.

: Behavior<T>에서 T는 Behavior를 적용할 Control이 된다. Polyline에 적용하기 때문에 : Behavior<Polyline>이 된다.

3-2-2. DependencyProperty를 작성한다.

		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 작성시 속성으로 사용할 수 있게 된다.

3-2-3. OnAttached()와 OnDetaching()의 작성

코드 비하인드에서 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;
        }

3-3. ViewModel 작성

지난 글에서의 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));
            }
        }

3-4. XAML 작성

지난 글에서의 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>

4. 결과

Polyline의 Fill을 Transparent로 설정하고 Cursor를 Hand로 설정하여 마치 Button을 클릭하는 느낌이 됐다.
Polyline이 그려진 부분을 클릭하면 위에 배치한 TextBlock에 바인딩 된 X값과 Y값을 확인할 수 있다.


5. 오류 해결하기

처음엔 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}" />

사실 실행을 했을 때는 문제가 없어 보이는데 어쨌든 콘솔에 출력된 에러라면 해결해야하는 것이니까...


6. 참조

Behavior 작성

에러 해결


profile
안뇽하세용
post-custom-banner

2개의 댓글

comment-user-thumbnail
2024년 6월 27일

포스트 잘 보았습니다 ~!

Behavior 관련 이글도 한번 확인 해 보시면 좋을것 같아요 ~
https://forum.dotnetdev.kr/t/wpf-behavior-onattached/11026

메모리 누수 문제도 걱정 해야하고,
이벤트가 중복으로 등록되는 문제도 한번 확인해보시면 좋을것 같습니다

1개의 답글