WPF 图像在画布上平移、缩放和滚动图层 [英] WPF Image Pan, Zoom and Scroll with layers on a canvas

查看:118
本文介绍了WPF 图像在画布上平移、缩放和滚动图层的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望有人可以帮助我.我正在构建一个 WPF 成像应用程序,它从相机获取实时图像,允许用户查看图像,然后突出显示该图像上的感兴趣区域 (ROI).然后将有关 ROI 的信息(宽度、高度、相对于图像上某个点的位置等)发送回相机,实际上是告诉/训练相机固件在哪里寻找条形码、文本、液位、转弯等内容图像上的螺丝等).所需的功能是能够平移和缩放图像及其 ROI,以及在图像放大到大于查看区域时滚动.ROI 的 StrokeThickness 和 FontSize 需要保持原始比例,但 ROI 内形状的宽度和高度需要随图像缩放(这对于捕获精确的像素位置以传输到相机至关重要).除了滚动和其他一些问题外,我已经解决了大部分问题.我关注的两个方面是:

I'm hoping someone can help me out here. I'm building a WPF imaging application that takes live images from a camera allowing users to view the image, and subsequently highlight regions of interest (ROI) on that image. Information about the ROIs (width, height, location relative to a point on the image, etc) is then sent back to the camera, in effect telling/training the camera firmware where to look for things like barcodes, text, liquid levels, turns on a screw, etc. on the image). A desired feature is the ability to pan and zoom the image and it's ROIs, as well as scroll when the image is zoomed larger than the viewing area. The StrokeThickness and FontSize of the ROI's need to keep there original scale, but the width and height of the shapes within an ROI need to scale with the image (this is critical to capture exact pixel locations to transmit to the camera). I've got most of this worked out with the exception of scrolling and a few other issues. My two areas of concern are:

  1. 当我引入 ScrollViewer 时,我没有任何滚动行为.据我了解,我需要引入 LayoutTransform 以获得正确的 ScrollViewer 行为.但是,当我这样做时,其他区域开始崩溃(例如,ROI 没有在图像上保持正确的位置,或者平移时鼠标指针开始远离图像上的选定点,或图像的左角弹跳到 MouseDown 上的当前鼠标位置.)

  1. When I introduce a ScrollViewer I don't get any scroll behavior. As I understand it I need to introduce a LayoutTransform to get the correct ScrollViewer behavior. However when I do that other areas start to break down (e.g. ROIs don't hold their correct position over the image, or the mouse pointer begins to creep away from the selected point on the image when panning, or the left corner of my image bounces to the current mouse position on MouseDown .)

我无法按照我需要的方式获得投资回报率的缩放比例.我有这个工作,但它并不理想.我所拥有的并没有保留确切的笔触粗细,而且我没有考虑忽略文本块上的比例.希望您能在代码示例中看到我在做什么.

I can't quite get the scaling of my ROI's the way I need them. I have this working, but it is not ideal. What I have doesn't retain the exact stroke thickness, and I haven't looked into ignoring scale on the textblocks. Hopefully you'll see what I'm doing in the code samples.

我确定我的问题与我对变换及其与 WPF 布局系统的关系缺乏了解有关.希望展示我迄今为止完成的代码的再现会有所帮助(见下文).

I'm sure my issue has something to do with my lack of understanding of Transforms and their relationship to the WPF layout system. Hopefully a rendition of the code that exhibits what I've accomplished so far will help (see below).

仅供参考,如果 Adorners 是建议,那在我的场景中可能行不通,因为我最终可能会得到比支持更多的装饰器(谣言 144 装饰器是事情开始崩溃的时候).

FYI, if Adorners are the suggestion, that may not work in my scenario because I could end up with more adorners than are supported (rumor 144 adorners is when things start breaking down).

首先,下面是一个屏幕截图,显示了一个带有 ROI(文本和形状)的图像.矩形、椭圆和文本需要在缩放和旋转上跟随图像上的区域,但它们不应该在粗细或字体大小上缩放.

First off, below is a screenshot showing an image with to ROI's (text and a shape). The rectangle, ellipse and text need to follow the area on the image in scale and rotation, but not they shouldn't scale in thickness or fontsize.

这是显示上图的 XAML,以及用于缩放的滑块(鼠标滚轮缩放稍后会出现)

<Window x:Class="PanZoomStackOverflow.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"
    Title="MainWindow" Height="768" Width="1024">

<DockPanel>
  <Slider x:Name="_ImageZoomSlider" DockPanel.Dock="Bottom"
          Value="2"
          HorizontalAlignment="Center" Margin="6,0,0,0" 
          Width="143" Minimum=".5" Maximum="20" SmallChange=".1" 
          LargeChange=".2" TickFrequency="2" 
          TickPlacement="BottomRight" Padding="0" Height="23"/>

  <!-- This resides in a user control in my solution -->
  <Grid x:Name="LayoutRoot">
    <ScrollViewer Name="border" HorizontalScrollBarVisibility="Auto" 
                  VerticalScrollBarVisibility="Auto">
      <Grid x:Name="_ImageDisplayGrid">
        <Image x:Name="_DisplayImage" Margin="2" Stretch="None"
               Source="Untitled.bmp"
               RenderTransformOrigin ="0.5,0.5"
               RenderOptions.BitmapScalingMode="NearestNeighbor"
               MouseLeftButtonDown="ImageScrollArea_MouseLeftButtonDown"
               MouseLeftButtonUp="ImageScrollArea_MouseLeftButtonUp"
               MouseMove="ImageScrollArea_MouseMove">                            
           <Image.LayoutTransform>
             <TransformGroup>
               <ScaleTransform />
               <TranslateTransform />
             </TransformGroup>
           </Image.LayoutTransform>
         </Image>
         <AdornerDecorator> <!-- Using this Adorner Decorator for Move, Resize and Rotation and feedback adornernments -->
           <Canvas x:Name="_ROICollectionCanvas"
                   Width="{Binding ElementName=_DisplayImage, Path=ActualWidth, Mode=OneWay}"
                   Height="{Binding ElementName=_DisplayImage, Path=ActualHeight, Mode=OneWay}"
                   Margin="{Binding ElementName=_DisplayImage, Path=Margin, Mode=OneWay}">

             <!-- This is a user control in my solution -->
             <Grid IsHitTestVisible="False" Canvas.Left="138" Canvas.Top="58" Height="25" Width="186">
               <TextBlock Text="Rectangle ROI" HorizontalAlignment="Center" VerticalAlignment="Top" 
                          Foreground="Orange" FontWeight="Bold" Margin="0,-15,0,0"/>
                 <Rectangle StrokeThickness="2" Stroke="Orange"/>
             </Grid>

             <!-- This is a user control in my solution -->
             <Grid IsHitTestVisible="False" Canvas.Left="176" Canvas.Top="154" Height="65" Width="69">
               <TextBlock Text="Ellipse ROI" HorizontalAlignment="Center" VerticalAlignment="Top" 
                          Foreground="Orange" FontWeight="Bold" Margin="0,-15,0,0"/>
               <Ellipse StrokeThickness="2" Stroke="Orange"/>
             </Grid>
           </Canvas>
         </AdornerDecorator>
       </Grid>
     </ScrollViewer>
  </Grid>
</DockPanel>

这是管理平移和缩放的 C#.

public partial class MainWindow : Window
{
private Point origin;
private Point start;
private Slider _slider;

public MainWindow()
{
    this.InitializeComponent();

    //Setup a transform group that we'll use to manage panning of the image area
    TransformGroup group = new TransformGroup();
    ScaleTransform st = new ScaleTransform();
    group.Children.Add(st);
    TranslateTransform tt = new TranslateTransform();
    group.Children.Add(tt);
    //Wire up the slider to the image for zooming
    _slider = _ImageZoomSlider;
    _slider.ValueChanged += _ImageZoomSlider_ValueChanged;
    st.ScaleX = _slider.Value;
    st.ScaleY = _slider.Value;
    //_ImageScrollArea.RenderTransformOrigin = new Point(0.5, 0.5);
    //_ImageScrollArea.LayoutTransform = group;
    _DisplayImage.RenderTransformOrigin = new Point(0.5, 0.5);
    _DisplayImage.RenderTransform = group;
    _ROICollectionCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
    _ROICollectionCanvas.RenderTransform = group;
}

//Captures the mouse to prepare for panning the scrollable image area
private void ImageScrollArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    _DisplayImage.ReleaseMouseCapture();
}

//Moves/Pans the scrollable image area  assuming mouse is captured.
private void ImageScrollArea_MouseMove(object sender, MouseEventArgs e)
{
    if (!_DisplayImage.IsMouseCaptured) return;

    var tt = (TranslateTransform)((TransformGroup)_DisplayImage.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;
}

//Cleanup for Move/Pan when mouse is released
private void ImageScrollArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    _DisplayImage.CaptureMouse();
    var tt = (TranslateTransform)((TransformGroup)_DisplayImage.RenderTransform).Children.First(tr => tr is TranslateTransform);
    start = e.GetPosition(border);
    origin = new Point(tt.X, tt.Y);
}

//Zoom according to the slider changes
private void _ImageZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    //Panel panel = _ImageScrollArea;
    Image panel = _DisplayImage;

    //Set the scale coordinates on the ScaleTransform from the slider
    ScaleTransform transform = (ScaleTransform)((TransformGroup)panel.RenderTransform).Children.First(tr => tr is ScaleTransform);
    transform.ScaleX = _slider.Value;
    transform.ScaleY = _slider.Value;


    //Set the zoom (this will affect rotate too) origin to the center of the panel
    panel.RenderTransformOrigin = new Point(0.5, 0.5);

    foreach (UIElement child in _ROICollectionCanvas.Children)
    {
        //Assume all shapes are contained in a panel
        Panel childPanel = child as Panel;

        var x = childPanel.Children;

        //Shape width and heigh should scale, but not StrokeThickness
        foreach (var shape in childPanel.Children.OfType<Shape>())
        {
            if (shape.Tag == null)
            {
                //Hack: This is be a property on a usercontrol in my solution
                shape.Tag = shape.StrokeThickness;
            }
            double orignalStrokeThickness = (double)shape.Tag;

            //Attempt to keep the underlying shape border/stroke from thickening as well
            double newThickness = shape.StrokeThickness - (orignalStrokeThickness / transform.ScaleX);

            shape.StrokeThickness -= newThickness;
        }
    }
}
}

假设没有剪切/粘贴错误,代码应该适用于 .NET 4.0 或 4.5 项目和解决方案.

The code should work in a .NET 4.0 or 4.5 project and solution, assuming no cut/paste errors.

有什么想法吗?欢迎提出建议.

Any thoughts? Suggestions are welcome.

推荐答案

好的.这是我对你描述的看法.

Ok. This is my take on what you described.

看起来像这样:

  • 由于我没有应用任何 RenderTransforms,所以我获得了所需的 Scrollbar/ScrollViewer 功能.
  • MVVM,这是 WPF 中的方法.UI 和数据是独立的,因此数据项只有 X、Y、宽度、高度等的 doubleint 属性,您可以将它们用于任何目的,甚至可以将它们存储在一个数据库.
  • 我在 Thumb 中添加了所有内容来处理平移.您仍然需要对通过 ResizerControl 拖动/调整 ROI 时发生的平移做一些事情.我想你可以检查 Mouse.DirectlyOver 或其他东西.
  • 我实际上使用了 ListBox 来处理 ROI,这样您就可以在任何给定时间选择 1 个 ROI.这会切换调整大小功能.这样,如果您点击 ROI,您就会看到调整器.
  • 缩放是在 ViewModel 级别处理的,因此不需要自定义 Panels 或类似的东西(尽管 @Clemens 的解决方案也很好)
  • 我正在使用 Enum 和一些 DataTriggers 来定义形状.请参阅 DataTemplate DataType={x:Type local:ROI} 部分.
  • WPF 摇滚.只需将我的代码复制并粘贴到 File ->新项目 ->WPF 应用程序 并亲自查看结果.

  • Since I'm not applying any RenderTransforms, I get the desired Scrollbar / ScrollViewer functionality.
  • MVVM, which is THE way to go in WPF. UI and data are independent thus the DataItems only have double and int properties for X,Y, Width,Height, etc that you can use for whatever purposes or even store them in a Database.
  • I added the whole stuff inside a Thumb to handle the panning. You will still need to do something about the Panning that occurs when you are dragging / resizing a ROI via the ResizerControl. I guess you can check for Mouse.DirectlyOver or something.
  • I actually used a ListBox to handle the ROIs so that you may have 1 selected ROI at any given time. This toggles the Resizing Functionality. So that if you click on a ROI, you will get the resizer visible.
  • The Scaling is handled at the ViewModel level, thus eliminating the need for custom Panels or stuff like that (though @Clemens' solution is nice as well)
  • I'm using an Enum and some DataTriggers to define the Shapes. See the DataTemplate DataType={x:Type local:ROI} part.
  • WPF Rocks. Just Copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.

<Window x:Class="MiscSamples.PanZoomStackOverflow_MVVM"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MiscSamples"
        Title="PanZoomStackOverflow_MVVM" Height="300" Width="300">
   <Window.Resources>
    <DataTemplate DataType="{x:Type local:ROI}">
        <Grid Background="#01FFFFFF">
            <Path x:Name="Path" StrokeThickness="2" Stroke="Black"
                  Stretch="Fill"/>
            <local:ResizerControl Visibility="Collapsed" Background="#30FFFFFF"
                                  X="{Binding X}" Y="{Binding Y}"
                                  ItemWidth="{Binding Width}"
                                  ItemHeight="{Binding Height}"
                                  x:Name="Resizer"/>
        </Grid>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Value="True">
                <Setter TargetName="Resizer" Property="Visibility" Value="Visible"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding Shape}" Value="{x:Static local:Shapes.Square}">
                <Setter TargetName="Path" Property="Data">
                    <Setter.Value>
                        <RectangleGeometry Rect="0,0,10,10"/>
                    </Setter.Value>
                </Setter>
            </DataTrigger>

            <DataTrigger Binding="{Binding Shape}" Value="{x:Static local:Shapes.Round}">
                <Setter TargetName="Path" Property="Data">
                    <Setter.Value>
                        <EllipseGeometry RadiusX="10" RadiusY="10"/>
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>

    <Style TargetType="ListBox" x:Key="ROIListBoxStyle">
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <Canvas/>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <ItemsPresenter/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="ListBoxItem" x:Key="ROIItemStyle">
        <Setter Property="Canvas.Left" Value="{Binding ActualX}"/>
        <Setter Property="Canvas.Top" Value="{Binding ActualY}"/>
        <Setter Property="Height" Value="{Binding ActualHeight}"/>
        <Setter Property="Width" Value="{Binding ActualWidth}"/>

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <ContentPresenter ContentSource="Content"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</Window.Resources>

<DockPanel>
    <Slider VerticalAlignment="Center" 
            Maximum="2" Minimum="0" Value="{Binding ScaleFactor}" SmallChange=".1"
            DockPanel.Dock="Bottom"/>

    <ScrollViewer VerticalScrollBarVisibility="Visible"
                  HorizontalScrollBarVisibility="Visible" x:Name="scr"
                  ScrollChanged="ScrollChanged">
        <Thumb DragDelta="Thumb_DragDelta">
            <Thumb.Template>
                <ControlTemplate>
                    <Grid>
                        <Image Source="/Images/Homer.jpg" Stretch="None" x:Name="Img"
                                VerticalAlignment="Top" HorizontalAlignment="Left">
                            <Image.LayoutTransform>
                                <TransformGroup>
                                    <ScaleTransform ScaleX="{Binding ScaleFactor}" ScaleY="{Binding ScaleFactor}"/>
                                </TransformGroup>
                            </Image.LayoutTransform>
                        </Image>

                        <ListBox ItemsSource="{Binding ROIs}"
                                 Width="{Binding ActualWidth, ElementName=Img}"
                                 Height="{Binding ActualHeight,ElementName=Img}"
                                 VerticalAlignment="Top" HorizontalAlignment="Left"
                                 Style="{StaticResource ROIListBoxStyle}"
                                 ItemContainerStyle="{StaticResource ROIItemStyle}"/>
                    </Grid>
                </ControlTemplate>
            </Thumb.Template>
        </Thumb>
    </ScrollViewer>
</DockPanel>

背后的代码:

public partial class PanZoomStackOverflow_MVVM : Window
    {
        public PanZoomViewModel ViewModel { get; set; }

        public PanZoomStackOverflow_MVVM()
        {
            InitializeComponent();
            DataContext = ViewModel = new PanZoomViewModel();

            ViewModel.ROIs.Add(new ROI() {ScaleFactor = ViewModel.ScaleFactor, X = 150, Y = 150, Height = 200, Width = 200, Shape = Shapes.Square});

            ViewModel.ROIs.Add(new ROI() { ScaleFactor = ViewModel.ScaleFactor, X = 50, Y = 230, Height = 102, Width = 300, Shape = Shapes.Round });
        }

        private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            //TODO: Detect whether a ROI is being resized / dragged and prevent Panning if so.
            IsPanning = true;
            ViewModel.OffsetX = (ViewModel.OffsetX + (((e.HorizontalChange/10) * -1) * ViewModel.ScaleFactor));
            ViewModel.OffsetY = (ViewModel.OffsetY + (((e.VerticalChange/10) * -1) * ViewModel.ScaleFactor));

            scr.ScrollToVerticalOffset(ViewModel.OffsetY);
            scr.ScrollToHorizontalOffset(ViewModel.OffsetX);

            IsPanning = false;
        }

        private bool IsPanning { get; set; }

        private void ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if (!IsPanning)
            {
                ViewModel.OffsetX = e.HorizontalOffset;
                ViewModel.OffsetY = e.VerticalOffset;
            }
        }
    }

主视图模型:

public class PanZoomViewModel:PropertyChangedBase
{
    private double _offsetX;
    public double OffsetX
    {
        get { return _offsetX; }
        set
        {
            _offsetX = value;
            OnPropertyChanged("OffsetX");
        }
    }

    private double _offsetY;
    public double OffsetY
    {
        get { return _offsetY; }
        set
        {
            _offsetY = value;
            OnPropertyChanged("OffsetY");
        }
    }

    private double _scaleFactor = 1;
    public double ScaleFactor
    {
        get { return _scaleFactor; }
        set
        {
            _scaleFactor = value;
            OnPropertyChanged("ScaleFactor");
            ROIs.ToList().ForEach(x => x.ScaleFactor = value);
        }
    }

    private ObservableCollection<ROI> _rois;
    public ObservableCollection<ROI> ROIs
    {
        get { return _rois ?? (_rois = new ObservableCollection<ROI>()); }
    }
}

ROI 视图模型:

public class ROI:PropertyChangedBase
{
    private Shapes _shape;
    public Shapes Shape
    {
        get { return _shape; }
        set
        {
            _shape = value;
            OnPropertyChanged("Shape");
        }
    }

    private double _scaleFactor;
    public double ScaleFactor
    {
        get { return _scaleFactor; }
        set
        {
            _scaleFactor = value;
            OnPropertyChanged("ScaleFactor");
            OnPropertyChanged("ActualX");
            OnPropertyChanged("ActualY");
            OnPropertyChanged("ActualHeight");
            OnPropertyChanged("ActualWidth");
        }
    }

    private double _x;
    public double X
    {
        get { return _x; }
        set
        {
            _x = value;
            OnPropertyChanged("X");
            OnPropertyChanged("ActualX");
        }
    }

    private double _y;
    public double Y
    {
        get { return _y; }
        set
        {
            _y = value;
            OnPropertyChanged("Y");
            OnPropertyChanged("ActualY");
        }
    }

    private double _height;
    public double Height
    {
        get { return _height; }
        set
        {
            _height = value;
            OnPropertyChanged("Height");
            OnPropertyChanged("ActualHeight");
        }
    }

    private double _width;
    public double Width
    {
        get { return _width; }
        set
        {
            _width = value;
            OnPropertyChanged("Width");
            OnPropertyChanged("ActualWidth");
        }
    }

    public double ActualX { get { return X*ScaleFactor; }}
    public double ActualY { get { return Y*ScaleFactor; }}
    public double ActualWidth { get { return Width*ScaleFactor; }}
    public double ActualHeight { get { return Height * ScaleFactor; } }
}

形状枚举:

public enum Shapes
{
    Round = 1,
    Square = 2,
    AnyOther
}

PropertyChangedBase(MVVM Helper 类):

PropertyChangedBase (MVVM Helper class):

    public class PropertyChangedBase:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            Application.Current.Dispatcher.BeginInvoke((Action) (() =>
                                                                     {
                                                                         PropertyChangedEventHandler handler = PropertyChanged;
                                                                         if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
                                                                     }));
        }
    }

缩放控件:

<UserControl x:Class="MiscSamples.ResizerControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Thumb DragDelta="Center_DragDelta" Height="10" Width="10"
               VerticalAlignment="Center" HorizontalAlignment="Center"/>

        <Thumb DragDelta="UpperLeft_DragDelta" Height="10" Width="10"
               VerticalAlignment="Top" HorizontalAlignment="Left"/>

        <Thumb DragDelta="UpperRight_DragDelta" Height="10" Width="10"
               VerticalAlignment="Top" HorizontalAlignment="Right"/>

        <Thumb DragDelta="LowerLeft_DragDelta" Height="10" Width="10"
               VerticalAlignment="Bottom" HorizontalAlignment="Left"/>

        <Thumb DragDelta="LowerRight_DragDelta" Height="10" Width="10"
               VerticalAlignment="Bottom" HorizontalAlignment="Right"/>

    </Grid>
</UserControl>

背后的代码:

 public partial class ResizerControl : UserControl
    {
        public static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double), typeof(ResizerControl), new FrameworkPropertyMetadata(0d,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double), typeof(ResizerControl), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(ResizerControl), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(ResizerControl), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

        public double X
        {
            get { return (double) GetValue(XProperty); }
            set { SetValue(XProperty, value); }
        }

        public double Y
        {
            get { return (double)GetValue(YProperty); }
            set { SetValue(YProperty, value); }
        }

        public double ItemHeight
        {
            get { return (double) GetValue(ItemHeightProperty); }
            set { SetValue(ItemHeightProperty, value); }
        }

        public double ItemWidth
        {
            get { return (double) GetValue(ItemWidthProperty); }
            set { SetValue(ItemWidthProperty, value); }
        }

        public ResizerControl()
        {
            InitializeComponent();
        }

        private void UpperLeft_DragDelta(object sender, DragDeltaEventArgs e)
        {
            X = X + e.HorizontalChange;
            Y = Y + e.VerticalChange;

            ItemHeight = ItemHeight + e.VerticalChange * -1;
            ItemWidth = ItemWidth + e.HorizontalChange * -1;
        }

        private void UpperRight_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Y = Y + e.VerticalChange;

            ItemHeight = ItemHeight + e.VerticalChange * -1;
            ItemWidth = ItemWidth + e.HorizontalChange;
        }

        private void LowerLeft_DragDelta(object sender, DragDeltaEventArgs e)
        {
            X = X + e.HorizontalChange;

            ItemHeight = ItemHeight + e.VerticalChange;
            ItemWidth = ItemWidth + e.HorizontalChange * -1;
        }

        private void LowerRight_DragDelta(object sender, DragDeltaEventArgs e)
        {
            ItemHeight = ItemHeight + e.VerticalChange;
            ItemWidth = ItemWidth + e.HorizontalChange;
        }

        private void Center_DragDelta(object sender, DragDeltaEventArgs e)
        {
            X = X + e.HorizontalChange;
            Y = Y + e.VerticalChange;
        }
    }

这篇关于WPF 图像在画布上平移、缩放和滚动图层的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆