[C#] 팬 & 줌 이미지

사용자가 다음을 수행 할 수 있도록 WPF에서 간단한 이미지 뷰어를 만들고 싶습니다.

  • 팬 (이미지를 마우스로 드래그하여).
  • 확대 / 축소 (슬라이더 사용)
  • 오버레이 표시 (예 : 사각형 선택).
  • 원본 이미지를 표시합니다 (필요한 경우 스크롤 막대 포함).

어떻게하는지 설명해 주실 수 있습니까?

웹에서 좋은 샘플을 찾지 못했습니다. ViewBox를 사용해야합니까? 아니면 ImageBrush? ScrollViewer가 필요합니까?



답변

이 문제를 해결하는 방법은 ClipToBounds 속성이 True로 설정된 이미지를 Border 내에 배치하는 것입니다. 그런 다음 이미지의 RenderTransformOrigin이 0.5,0.5로 설정되어 이미지가 이미지 중앙에서 확대되기 시작합니다. RenderTransform도 ScaleTransform과 TranslateTransform을 포함하는 TransformGroup으로 설정됩니다.

그런 다음 이미지에서 MouseWheel 이벤트를 처리하여 확대 / 축소를 구현했습니다.

private void image_MouseWheel(object sender, MouseWheelEventArgs e)
{
    var st = (ScaleTransform)image.RenderTransform;
    double zoom = e.Delta > 0 ? .2 : -.2;
    st.ScaleX += zoom;
    st.ScaleY += zoom;
}

패닝을 처리하기 위해 이미지에서 MouseLeftButtonDown 이벤트를 처리하고 마우스를 캡처하고 위치를 기록하는 것이 가장 중요했습니다. 또한 TranslateTransform의 현재 값도 저장합니다.

Point start;
Point origin;
private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    image.CaptureMouse();
    var tt = (TranslateTransform)((TransformGroup)image.RenderTransform)
        .Children.First(tr => tr is TranslateTransform);
    start = e.GetPosition(border);
    origin = new Point(tt.X, tt.Y);
}

그런 다음 MouseMove 이벤트를 처리하여 TranslateTransform을 업데이트했습니다.

private void image_MouseMove(object sender, MouseEventArgs e)
{
    if (image.IsMouseCaptured)
    {
        var tt = (TranslateTransform)((TransformGroup)image.RenderTransform)
            .Children.First(tr => tr is TranslateTransform);
        Vector v = start - e.GetPosition(border);
        tt.X = origin.X - v.X;
        tt.Y = origin.Y - v.Y;
    }
}

마지막으로 마우스 캡처를 해제하는 것을 잊지 마십시오.

private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    image.ReleaseMouseCapture();
}

크기 조정을위한 선택 핸들은 adorner를 사용하여 수행 할 수 있습니다 . 자세한 내용은 이 기사 를 확인하십시오.


답변

이 질문의 샘플을 사용한 후 마우스 포인터를 기준으로 적절한 확대 / 축소를 사용하여 전체 버전의 팬 및 확대 / 축소 앱을 만들었습니다. 모든 이동 및 확대 / 축소 코드가 ZoomBorder라는 별도의 클래스로 이동되었습니다.

ZoomBorder.cs

using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace PanAndZoom
{
  public class ZoomBorder : Border
  {
    private UIElement child = null;
    private Point origin;
    private Point start;

    private TranslateTransform GetTranslateTransform(UIElement element)
    {
      return (TranslateTransform)((TransformGroup)element.RenderTransform)
        .Children.First(tr => tr is TranslateTransform);
    }

    private ScaleTransform GetScaleTransform(UIElement element)
    {
      return (ScaleTransform)((TransformGroup)element.RenderTransform)
        .Children.First(tr => tr is ScaleTransform);
    }

    public override UIElement Child
    {
      get { return base.Child; }
      set
      {
        if (value != null && value != this.Child)
          this.Initialize(value);
        base.Child = value;
      }
    }

    public void Initialize(UIElement element)
    {
      this.child = element;
      if (child != null)
      {
        TransformGroup group = new TransformGroup();
        ScaleTransform st = new ScaleTransform();
        group.Children.Add(st);
        TranslateTransform tt = new TranslateTransform();
        group.Children.Add(tt);
        child.RenderTransform = group;
        child.RenderTransformOrigin = new Point(0.0, 0.0);
        this.MouseWheel += child_MouseWheel;
        this.MouseLeftButtonDown += child_MouseLeftButtonDown;
        this.MouseLeftButtonUp += child_MouseLeftButtonUp;
        this.MouseMove += child_MouseMove;
        this.PreviewMouseRightButtonDown += new MouseButtonEventHandler(
          child_PreviewMouseRightButtonDown);
      }
    }

    public void Reset()
    {
      if (child != null)
      {
        // reset zoom
        var st = GetScaleTransform(child);
        st.ScaleX = 1.0;
        st.ScaleY = 1.0;

        // reset pan
        var tt = GetTranslateTransform(child);
        tt.X = 0.0;
        tt.Y = 0.0;
      }
    }

    #region Child Events

        private void child_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (child != null)
            {
                var st = GetScaleTransform(child);
                var tt = GetTranslateTransform(child);

                double zoom = e.Delta > 0 ? .2 : -.2;
                if (!(e.Delta > 0) && (st.ScaleX < .4 || st.ScaleY < .4))
                    return;

                Point relative = e.GetPosition(child);
                double absoluteX;
                double absoluteY;

                absoluteX = relative.X * st.ScaleX + tt.X;
                absoluteY = relative.Y * st.ScaleY + tt.Y;

                st.ScaleX += zoom;
                st.ScaleY += zoom;

                tt.X = absoluteX - relative.X * st.ScaleX;
                tt.Y = absoluteY - relative.Y * st.ScaleY;
            }
        }

        private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (child != null)
            {
                var tt = GetTranslateTransform(child);
                start = e.GetPosition(this);
                origin = new Point(tt.X, tt.Y);
                this.Cursor = Cursors.Hand;
                child.CaptureMouse();
            }
        }

        private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (child != null)
            {
                child.ReleaseMouseCapture();
                this.Cursor = Cursors.Arrow;
            }
        }

        void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.Reset();
        }

        private void child_MouseMove(object sender, MouseEventArgs e)
        {
            if (child != null)
            {
                if (child.IsMouseCaptured)
                {
                    var tt = GetTranslateTransform(child);
                    Vector v = start - e.GetPosition(this);
                    tt.X = origin.X - v.X;
                    tt.Y = origin.Y - v.Y;
                }
            }
        }

        #endregion
    }
}

MainWindow.xaml

<Window x:Class="PanAndZoom.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:PanAndZoom"
        Title="PanAndZoom" Height="600" Width="900" WindowStartupLocation="CenterScreen">
    <Grid>
        <local:ZoomBorder x:Name="border" ClipToBounds="True" Background="Gray">
            <Image Source="image.jpg"/>
        </local:ZoomBorder>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace PanAndZoom
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}


답변

답변이 위에 게시되었지만 완료되지 않았습니다. 완성 된 버전은 다음과 같습니다.

XAML

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MapTest.Window1"
x:Name="Window"
Title="Window1"
Width="1950" Height="1546" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Controls="clr-namespace:WPFExtensions.Controls;assembly=WPFExtensions" mc:Ignorable="d" Background="#FF000000">

<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="52.92"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Border Grid.Row="1" Name="border">
        <Image Name="image" Source="map3-2.png" Opacity="1" RenderTransformOrigin="0.5,0.5"  />
    </Border>

</Grid>

뒤에 코드

using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

namespace MapTest
{
    public partial class Window1 : Window
    {
        private Point origin;
        private Point start;

        public Window1()
        {
            InitializeComponent();

            TransformGroup group = new TransformGroup();

            ScaleTransform xform = new ScaleTransform();
            group.Children.Add(xform);

            TranslateTransform tt = new TranslateTransform();
            group.Children.Add(tt);

            image.RenderTransform = group;

            image.MouseWheel += image_MouseWheel;
            image.MouseLeftButtonDown += image_MouseLeftButtonDown;
            image.MouseLeftButtonUp += image_MouseLeftButtonUp;
            image.MouseMove += image_MouseMove;
        }

        private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            image.ReleaseMouseCapture();
        }

        private void image_MouseMove(object sender, MouseEventArgs e)
        {
            if (!image.IsMouseCaptured) return;

            var tt = (TranslateTransform) ((TransformGroup) image.RenderTransform).Children.First(tr => tr is TranslateTransform);
            Vector v = start - e.GetPosition(border);
            tt.X = origin.X - v.X;
            tt.Y = origin.Y - v.Y;
        }

        private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            image.CaptureMouse();
            var tt = (TranslateTransform) ((TransformGroup) image.RenderTransform).Children.First(tr => tr is TranslateTransform);
            start = e.GetPosition(border);
            origin = new Point(tt.X, tt.Y);
        }

        private void image_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            TransformGroup transformGroup = (TransformGroup) image.RenderTransform;
            ScaleTransform transform = (ScaleTransform) transformGroup.Children[0];

            double zoom = e.Delta > 0 ? .2 : -.2;
            transform.ScaleX += zoom;
            transform.ScaleY += zoom;
        }
    }
}

내 웹 사이트 에서이 코드를 사용하는 전체 wpf 프로젝트의 예가 있습니다 : Jot the sticky note app .


답변

이 줌 컨트롤을 사용해보십시오 : http://wpfextensions.codeplex.com

컨트롤의 사용법은 매우 간단합니다. wpfextensions 어셈블리를 참조하는 것보다

<wpfext:ZoomControl>
    <Image Source="..."/>
</wpfext:ZoomControl>

현재 스크롤바는 지원되지 않습니다. (다음 릴리스에서는 1 ~ 2 주 후에 제공 될 예정입니다).


답변

  • 팬 : 이미지를 캔버스 안에 넣습니다. Mouse Up, Down 및 Move 이벤트를 구현하여 Canvas.Top, Canvas.Left 속성을 이동합니다. 아래로 내려 가면 isDraggingFlag를 true로, 위로 올리면 플래그를 false로 설정합니다. 이동하면 플래그가 설정되어 있는지, 캔버스 내 이미지에서 Canvas.Top 및 Canvas.Left 속성이 오프셋되어 있는지 확인합니다.
  • 줌 : 슬라이더를 캔버스의 스케일 변환에 바인딩
  • 오버레이 표시 : 이미지가 포함 된 캔버스 위에 배경이없는 추가 캔버스를 추가합니다.
  • 원본 이미지 표시 : ViewBox 내부의 이미지 제어

답변

@Anothen 및 @ Number8-Vector 클래스는 Silverlight에서 사용할 수 없으므로 제대로 작동하려면 MouseMove 이벤트가 마지막으로 호출 된 마지막 위치를 기록한 다음 두 지점을 비교하여 차이점을 찾아야합니다. ; 그런 다음 변환을 조정하십시오.

XAML :

    <Border Name="viewboxBackground" Background="Black">
            <Viewbox Name="viewboxMain">
                <!--contents go here-->
            </Viewbox>
    </Border>  

코드 숨김 :

    public Point _mouseClickPos;
    public bool bMoving;


    public MainPage()
    {
        InitializeComponent();
        viewboxMain.RenderTransform = new CompositeTransform();
    }

    void MouseMoveHandler(object sender, MouseEventArgs e)
    {

        if (bMoving)
        {
            //get current transform
            CompositeTransform transform = viewboxMain.RenderTransform as CompositeTransform;

            Point currentPos = e.GetPosition(viewboxBackground);
            transform.TranslateX += (currentPos.X - _mouseClickPos.X) ;
            transform.TranslateY += (currentPos.Y - _mouseClickPos.Y) ;

            viewboxMain.RenderTransform = transform;

            _mouseClickPos = currentPos;
        }
    }

    void MouseClickHandler(object sender, MouseButtonEventArgs e)
    {
        _mouseClickPos = e.GetPosition(viewboxBackground);
        bMoving = true;
    }

    void MouseReleaseHandler(object sender, MouseButtonEventArgs e)
    {
        bMoving = false;
    }

또한 이동 및 확대 / 축소를 구현하기 위해 TransformGroup 또는 컬렉션이 필요하지 않습니다. 대신 CompositeTransform 은 번거 로움없이 트릭을 수행합니다.

나는 이것이 리소스 사용 측면에서 실제로 비효율적이라고 확신하지만 적어도 작동합니다 🙂


답변

마우스 위치를 기준으로 확대 / 축소하려면 필요한 것은 다음과 같습니다.

var position = e.GetPosition(image1);
image1.RenderTransformOrigin = new Point(position.X / image1.ActualWidth, position.Y / image1.ActualHeight);