WPF图像平移,缩放和滚animation布上的图层
我希望有人能帮助我。 我正在构build一个WPF成像应用程序,该应用程序从摄像头获取实时图像,以便用户查看图像,然后突出显示该图像上的感兴趣区域(ROI)。 关于ROI(宽度,高度,相对于图像上的点的位置等等)的信息然后被发送回相机,实际上是告诉/训练相机固件在哪里寻找诸如条形码,文本,液位,转弯在图像上的螺丝等)。 所需的function是能够平移和缩放图像及其ROI,以及在图像缩放比查看区域大时滚动。 ROI的StrokeThickness和FontSize需要保持原始比例,但是ROI中的形状的宽度和高度需要随图像一起缩放(这对捕获传输到相机的精确像素位置至关重要)。 除了滚动和其他一些问题之外,我已经完成了大部分工作。 我关心的两个方面是:
-
当我介绍一个ScrollViewer我没有得到任何滚动的行为。 据我了解,我需要引入一个LayoutTransform来获得正确的ScrollViewer行为。 但是当我这样做时,其他区域开始分解(例如ROI在图像上不能保持其正确的位置,或者在平移时鼠标指针开始从图像上的选定点开始蠕动,或者图像的左angular弹跳到MouseDown上的当前鼠标位置。)
-
我不能像我所需要的那样得到我的投资回报率。 我有这个工作,但它不理想。 我有什么不保留确切的中风厚度,我没有看到在文本块忽略规模。 希望你能看到我在代码示例中做了什么。
我相信我的问题与我对变换缺乏理解以及它们与WPF布局系统的关系有关。 希望展示我迄今为止所完成的代码的翻译将有所帮助(见下文)。
仅供参考,如果Adorners是这样的build议,这可能不适用于我的情况,因为我最终可能会得到比所支持的更多的装饰(传闻144装饰是事情开始崩溃的时候)。
首先,下面是一个屏幕截图,展示了一个ROI(文本和形状)的图像。 矩形,椭圆和文本需要按照图像上的比例和旋转的区域,但不能按比例缩放或字体大小。
这里是显示上述图像的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>
这是pipe理平移和缩放的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 - vX; tt.Y = origin.Y - vY; } //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项目和解决scheme中工作,假设没有剪切/粘贴错误。
有什么想法吗? 欢迎提出build议。
好。 这是我所说的。
它看起来像这样:
- 由于我没有应用任何
RenderTransforms
,我得到了所需的滚动条/滚动查看器function。 - MVVM,这是WPF的方式。 UI和数据是独立的,因此DataItem只具有X,Y,Width,Height等的
double
和int
属性,您可以将其用于任何目的,甚至可以将它们存储在数据库中。 - 我在一个
Thumb
里面加了整个东西来处理平移。 您仍然需要对通过ResizerControl拖动/调整ROI大小时发生的平移进行操作。 我想你可以检查Mouse.DirectlyOver
什么的。 - 我实际上使用了一个
ListBox
来处理ROI,以便在任何给定的时间可以有1个选定的ROI。 这切换调整function。 所以,如果你点击一个投资回报率,你会看到调整器。 - 缩放是在ViewModel级别处理的,因此不需要自定义
Panels
或类似的东西(虽然@Clemens的解决scheme也很好) - 我使用一个
Enum
和一些DataTriggers
来定义形状。 请参阅DataTemplate DataType={x:Type local:ROI}
部分。 -
WPF的岩石。 只需将我的代码复制并粘贴到
File -> New Project -> WPF Application
然后查看结果。<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 ViewModel:
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助手类):
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)); })); } }
Resizer控制:
<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; } }
为了在不改变笔画厚度的情况下转换形状,可以使用具有转换几何graphics的Path
对象。
以下XAML将一个图像和两个path放置在canvas上。 该图像通过RenderTransform进行缩放和翻译。 两个path几何的Transform
属性也使用相同的变换。
<Canvas> <Image Source="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"> <Image.RenderTransform> <TransformGroup x:Name="transform"> <ScaleTransform ScaleX="0.5" ScaleY="0.5"/> <TranslateTransform X="100" Y="50"/> </TransformGroup> </Image.RenderTransform> </Image> <Path Stroke="Orange" StrokeThickness="2"> <Path.Data> <RectangleGeometry Rect="50,100,100,50" Transform="{Binding ElementName=transform}"/> </Path.Data> </Path> <Path Stroke="Orange" StrokeThickness="2"> <Path.Data> <EllipseGeometry Center="250,100" RadiusX="50" RadiusY="50" Transform="{Binding ElementName=transform}"/> </Path.Data> </Path> </Canvas>
您的应用程序现在可以简单地更改transform
对象,以响应像MouseMove或MouseWheel这样的input事件。
当涉及到转换TextBlocks或其他不应缩放的元素时,事情会变得有点棘手,但只能移动到适当的位置。
您可以创build一个专门的面板,将其应用于其子元素。 这样一个Panel将定义一个控制一个子元素位置的附加属性,并将这个变换应用到这个位置,而不是RenderTransform
或者LayoutTransform
。
这可能会给你一个这样一个小组如何实施的想法:
public class TransformPanel : Panel { public static readonly DependencyProperty TransformProperty = DependencyProperty.Register( "Transform", typeof(Transform), typeof(TransformPanel), new FrameworkPropertyMetadata(Transform.Identity, FrameworkPropertyMetadataOptions.AffectsArrange)); public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached( "Position", typeof(Point?), typeof(TransformPanel), new PropertyMetadata(PositionPropertyChanged)); public Transform Transform { get { return (Transform)GetValue(TransformProperty); } set { SetValue(TransformProperty, value); } } public static Point? GetPosition(UIElement element) { return (Point?)element.GetValue(PositionProperty); } public static void SetPosition(UIElement element, Point? value) { element.SetValue(PositionProperty, value); } protected override Size MeasureOverride(Size availableSize) { var infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); foreach (UIElement element in InternalChildren) { element.Measure(infiniteSize); } return new Size(); } protected override Size ArrangeOverride(Size finalSize) { foreach (UIElement element in InternalChildren) { ArrangeElement(element, GetPosition(element)); } return finalSize; } private void ArrangeElement(UIElement element, Point? position) { var arrangeRect = new Rect(element.DesiredSize); if (position.HasValue && Transform != null) { arrangeRect.Location = Transform.Transform(position.Value); } element.Arrange(arrangeRect); } private static void PositionPropertyChanged( DependencyObject obj, DependencyPropertyChangedEventArgs e) { var element = (UIElement)obj; var panel = VisualTreeHelper.GetParent(element) as TransformPanel; if (panel != null) { panel.ArrangeElement(element, (Point?)e.NewValue); } } }
这将在XAML中使用,如下所示:
<local:TransformPanel> <local:TransformPanel.Transform> <TransformGroup> <ScaleTransform ScaleX="0.5" ScaleY="0.5" x:Name="scale"/> <TranslateTransform X="100"/> </TransformGroup> </local:TransformPanel.Transform> <Image Source="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg" RenderTransform="{Binding Transform, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TransformPanel}}"/> <Path Stroke="Orange" StrokeThickness="2"> <Path.Data> <RectangleGeometry Rect="50,100,100,50" Transform="{Binding Transform, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TransformPanel}}"/> </Path.Data> </Path> <Path Stroke="Orange" StrokeThickness="2"> <Path.Data> <EllipseGeometry Center="250,100" RadiusX="50" RadiusY="50" Transform="{Binding Transform, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:TransformPanel}}"/> </Path.Data> </Path> <TextBlock Text="Rectangle" local:TransformPanel.Position="50,150"/> <TextBlock Text="Ellipse" local:TransformPanel.Position="200,150"/> </local:TransformPanel>