마우스 포인터 옆에 preview이미지 만드는 방법

안녕하세요.

<수정> WPF입니다. 현재 Caliburn.Micro 기반으로 개발중입니다.

Canvas에 도형이나 Custom으로 정의된 Shape이 있는데, 이걸 Canvas상에 클릭으로 삽입을 하려고 합니다.

해당 도형을 선택하는 버튼을 누르면, 마우스 포인터의 옆에 Canva상에 찍혀질 이미지를 따라다니게 만들고 싶은데 어떻게 접근하면 좋을까요?

감사합니다.

좋아요 2

먼저, 원하시는 답변이 될지 모르겠지만

winform에서는 Cursor 클래스를 사용하여 마우스 포인터의 모양을 변경할 수 있습니다
필요에 따라 cursor 모양을 바꾸는 방법입니다.

구글에 c# custom cursor 로 검색하시면 도움이 될 것으로 보입니다.

감사합니다.

좋아요 1

윈폼이라던가 WPF 등 사용하시는 UI 프레임워크 정보를 알려주시면 답변에 도움이 될 것 같습니다.

좋아요 1

WPF라면 Adorner로 구현하면 좋습니다.

혹시 확인하고 구현해보는데 잘 안되시면 다시 말씀 주세요.

좋아요 1

감사합니다.~

시도해보고 말씀드릴께요~

WPF에서 DragDrop을 이용해 ItemsControl의 Item 순서를 바꿀 때 비주얼 피드백을 주고 싶어서 관련 코드를 찾아본 적이 있는데 말씀해 주신 상황에 적용할 수 있게 부분적인 코드만 추출해서 올려드립니다. 참고하시면 좋을 것 같습니다.

  1. DragDrop 가능한 Shape의 타입을 정의합니다.
    아무 컨트롤이나 다 드래그 드랍을 할 수 있게 되면 중구난방이 되므로 특정한 인터페이스를 정의하고, DragDrop하고자 하는 Shape에 해당 인터페이스를 구현해 해당 인터페이스를 구현한 Shape만 DragDrop 대상이 되도록 합니다.

  2. Adorner를 구현합니다.

public class DragDropAdorner : Adorner
{
    // Shape를 화면에 시각화할 Brush
	private Brush _visualBrush;

    // Shape의 화면 좌표
	private Point _currentLocation;

    // 최초 클릭 좌표
	private Point _startLocation;

    public DragDropAdorner(UIElement adornedElement, Point startLocation) : base(adornedElement)
	{
		_startLocation = startLocation;
		_visualBrush = new VisualBrush(AdornedElement)
		{
			Opacity = 0.75
		};
		IsHitTestVisible = false;
	}

	public void Update(Point location)
	{
		_currentLocation = location;

        (Parent as AdornerLayer)?.Update(AdornedElement);
	}

	protected override void OnRender(DrawingContext dc)
	{
		var p = _currentLocation;
		p.Offset(-_startLocation.X, -_startLocation.Y);

		dc.DrawRectangle(_visualBrush, null, new Rect(p, RenderSize));
	}
}

Update 메서드가 호출될 때마다 location을 이용해 현재 좌표(_currentLocation)을 갱신하고, AdornerLayer의 Update 메서드를 호출해 변경한 좌표를 반영하게 됩니다.

  1. Behavior를 구현합니다.
public class DragDropBehavior : Behavior<Canvas>

저는 하나의 ItemsControl에서 아이템 간 순서를 변경하는 게 주 목적이어서 Parent 타입을 AssociatedObject로 잡았는데 이건 구현하기 나름일 듯합니다.


그 다음으로는 드래그 드랍 대상 타입을 담을 DP를 만들어 줍니다. 여기에는 1에서 만든 인터페이스 타입이 들어가게 됩니다.

public Type InnerType
{
	get => (Type)GetValue(InnerTypeProperty);
	set => SetValue(InnerTypeProperty, value);
}

public static readonly DependencyProperty InnerTypeProperty =
		DependencyProperty.RegisterAttached(nameof(InnerType),
		typeof(Type), typeof(DragDropBehavior), new PropertyMetadata(default(Type)));

Parent(여기서는 Canvas)를 AssociatedObject로 잡았으므로 클릭한 좌표에 위치한 Shape를 가져오기 위한 메서드를 아래와 같은 방법으로 구현해야 합니다.

private Control? FindAncestor(DependencyObject obj)
{
	while (obj is not null)
	{
		if (InnerType.Equals(obj.GetType()) || InnerType.IsAssignableFrom(obj.GetType()))
		{
			return (Control)obj;
		}
		obj = VisualTreeHelper.GetParent(obj);
	}

	return null;
}

위 메서드를 호출하면 대상 요소의 visualtree를 타고 올라가면서 InnerType과 타입이 같거나, InnerType을 구현했다면 Control 타입으로 캐스팅해 반환합니다. 저는 Control 타입으로 캐스팅했는데 상위 타입인 UIElement로 하셔야 한다면 캐스팅 타입을 수정하시면 됩니다.


다음으로 좌표를 수정할 메서드를 만들어 줍니다. Winform 어셈블리에 있는 함수를 이용하는 방법과 dllimport를 이용하는 방법이 있는데 저는 Winform을 이용했습니다. 이 경우 프로젝트에서 Winform을 사용함으로 체크하셔야 합니다.

private static Point GetMousePositionWindowsForms()
{
	var point = System.Windows.Forms.Control.MousePosition;
	return new Point(point.X, point.Y);
}
좋아요 3

이제 Adorner를 생성하고 AdornerLayer에 추가하는 함수와, Adorner를 AdornerLayer에서 제거하는 함수를 만들어 줍니다.

private void SetDropEffect(Control control)
{
	_adorner = new DragDropAdorner(control, GetMousePositionWindowsForms());
	AdornerLayer.GetAdornerLayer(control)?.Add(_adorner);
	Mouse.SetCursor(Cursors.Hand);
}

private void UnsetDropEffect(Control control)
{
	AdornerLayer.GetAdornerLayer(control)?.Remove(_adorner);
	Mouse.SetCursor(null);
	_adorner = null;
}

다음으로는 AssociatedObject에 DragDrop과 관련한 이벤트를 추가합니다.

protected override void OnAttached()
{
	base.OnAttached();
	AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObject_PreviewMouseLeftButtonDown;
	AssociatedObject.Drop += AssociatedObject_Drop;
	AssociatedObject.GiveFeedback += AssociatedObject_GiveFeedback;
}

protected override void OnDetaching()
{
	base.OnDetaching();
	AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObject_PreviewMouseLeftButtonDown;
	AssociatedObject.Drop -= AssociatedObject_Drop;
	AssociatedObject.GiveFeedback -= AssociatedObject_GiveFeedback;
}

각 이벤트 핸들러를 구현합니다.

private void AssociatedObject_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
	var control = FindAncestor((DependencyObject)AssociatedObject.InputHitTest(e.GetPosition(AssociatedObject)));

	if (control is null)
		return;

	SetDropEffect(control);

	DataObject data = new DataObject(nameof(AssociatedObject), control, true);
	DragDrop.DoDragDrop(control, data, DragDropEffects.Move);

	UnsetDropEffect(control);
}
private void AssociatedObject_GiveFeedback(object sender, GiveFeedbackEventArgs e)
{
	e.UseDefaultCursors = e.Effects != DragDropEffects.Move;
	if (_adorner is not null)
	{
		_adorner.Update(GetMousePositionWindowsForms());
	}
	e.Handled = true;
}

SetDropEffect 메서드를 이용해 Adorner를 레이어에 추가한 뒤 DragDrop 이벤트를 발생시키고, DragDrop이 끝나면 Adorner를 제거하는 과정을 수행합니다.

DragDrop 중 발생하는 GiveFeedback 이벤트 핸들러에서는 현재 마우스 좌표를 가져와 Adorner의 위치를 업데이트 시켜줍니다.

  1. xaml에 behavior를 추가합니다.(behavior를 사용하기 위해 Nuget에서 Microsoft.Xaml.Behaviors.Wpf 패키지를 설치해야 합니다.)
<b:Interaction.Behaviors>
	<behavior:DragDropBehavior InnerType="{x:Type shapes:IMyShape}"/>
</b:Interaction.Behaviors>

이제 Canvas에서 IMyShape를 구현한 Shape를 추가하면 해당 shape 모양의 adorner가 생성됩니다.

source code:

좋아요 4

오~!!!
너무 감사합니다…ㅠㅠ
적용해보겠습니다.