我希望有人可以帮助我。我正在构建一个WPF成像应用程序,它从相机中获取实时图像,允许用户查看图像,然后突出显示该图像上的感兴趣区域(ROI)。然后将关于ROI的信息(宽度,高度,相对于图像上的点的位置等)发送回相机,实际上告知/训练相机固件在哪里寻找条形码,文本,液位,转弯等内容在图像上的螺丝等)。所需的功能是能够平移和缩放图像及其ROI,以及在图像缩放比观看区域大时滚动。 ROI的StrokeThickness和FontSize需要保持原始比例,但ROI内形状的宽度和高度需要与图像一起缩放(这对于捕获传输到相机的精确像素位置至关重要)。除了滚动和其他一些问题之外,我已经解决了大部分问题。我关注的两个方面是:
当我介绍ScrollViewer时,我没有得到任何滚动行为。据我所知,我需要引入一个LayoutTransform来获得正确的ScrollViewer行为。然而,当我这样做时,其他区域开始崩溃(例如,ROI在图像上没有保持正确的位置,或者当平移时鼠标指针开始从图像上的选定点开始蠕动,或者我的图像的左角弹回到MouseDown上的当前鼠标位置。)
我无法按照我需要的方式调整投资回报率。我有这个工作,但它并不理想。我所拥有的并没有保留精确的笔划厚度,我没有考虑忽略文本块上的比例。希望你会在代码示例中看到我在做什么。
我确定我的问题与我对Transforms及其与WPF布局系统的关系缺乏了解有关。希望展示我迄今所取得的成就的代码将有所帮助(见下文)。
仅供参考,如果Adorners是建议,那可能在我的场景中不起作用,因为我最终可能会得到更多的装饰者而不是支持(谣言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>
这是管理平移和缩放的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项目和解决方案中工作,假设没有剪切/粘贴错误。
有什么想法?欢迎提出建议。
答案 0 :(得分:20)
确定。这是我对你描述的内容的看法。
看起来像这样:
RenderTransforms
,因此我获得了所需的Scrollbar / ScrollViewer功能。double
和int
属性,您可以将它们用于任何目的,甚至将它们存储在数据库中。Thumb
内添加了所有内容以处理平移。当您通过ResizerControl拖动/调整ROI大小时,您仍需要对平移进行一些操作。我猜您可以查看Mouse.DirectlyOver
或其他内容。ListBox
来处理投资回报率,这样您在任何给定时间都可以获得1个选定的投资回报率。这会切换调整大小功能。因此,如果您点击ROI,您将看到缩放器可见。Panels
或类似的东西(尽管@Clemens的解决方案也很好)Enum
和一些DataTriggers
来定义形状。请参阅DataTemplate DataType={x:Type local:ROI}
部分。 WPF Rocks。只需将我的代码复制并粘贴到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;
}
}
}
Main ViewModel:
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 Helper类):
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 Control:
<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;
}
}
答案 1 :(得分:1)
为了在不改变笔触粗细的情况下变换形状,您可以使用具有变换几何的Path
个对象。
以下XAML在画布上放置图像和两个路径。图像由RenderTransform缩放和翻译。相同的变换也用于两个路径的几何的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等输入事件。
在转换TextBlocks或其他不应缩放的元素时,事情变得有点棘手,但只能移动到正确的位置。
您可以创建一个专门的Panel,它可以将这种变换应用于其子元素。这样的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>
答案 2 :(得分:1)
这个答案并不能真正帮助OP解决他指定的问题,但总的来说,相机平移,放大和缩小以及四处查看(使用鼠标)非常困难,所以我只想对我如何将摄像机移动实现到视口场景(如Blender或Unity等)
这是一个名为CameraPan的类,其中包含一些您可以自定义以编辑放大和缩小距离/速度,平移速度和相机外观灵敏度的变量。在该类的底部,有一些散列的代码代表了任何场景中的基本实现。首先,您需要创建一个视口并将其分配给“边框”(这是一个UI元素,因为视口无法处理,因此它可以处理鼠标事件),还需要创建一个摄像头以及从CameraPan访问的几个其他公共变量类:
public partial class CameraPan
{
Point TemporaryMousePosition;
Point3D PreviousCameraPosition;
Quaternion QuatX;
Quaternion PreviousQuatX;
Quaternion QuatY;
Quaternion PreviousQuatY;
private readonly float PanSpeed = 4f;
private readonly float LookSensitivity = 100f;
private readonly float ZoomInOutDistance = 1f;
private readonly MainWindow mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
public Vector3D LookDirection(PerspectiveCamera camera, Point3D pointToLookAt) // Calculates vector direction between two points (LookAt() method)
{
Point3D CameraPosition = camera.Position;
Vector3D VectorDirection = new Vector3D
(pointToLookAt.X - CameraPosition.X,
pointToLookAt.Y - CameraPosition.Y,
pointToLookAt.Z - CameraPosition.Z);
return VectorDirection;
}
public void PanLookAroundViewport_MouseMove(object sender, MouseEventArgs e) // Panning the viewport using the camera
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
Point mousePos = e.GetPosition(sender as Border); // Gets the current mouse pos
Point3D newCamPos = new Point3D(
((-mousePos.X + TemporaryMousePosition.X) / mainWindow.Width * PanSpeed) + PreviousCameraPosition.X,
((mousePos.Y - TemporaryMousePosition.Y) / mainWindow.Height * PanSpeed) + PreviousCameraPosition.Y,
mainWindow.MainCamera.Position.Z); // Calculates the proportional distance to move the camera,
//can be increased by changing the variable 'PanSpeed'
if (Keyboard.IsKeyDown(Key.LeftCtrl)) // Pan viewport
{
mainWindow.MainCamera.Position = newCamPos;
}
else // Look around viewport
{
double RotY = (e.GetPosition(sender as Label).X - TemporaryMousePosition.X) / mainWindow.Width * LookSensitivity; // MousePosX is the Y axis of a rotation
double RotX = (e.GetPosition(sender as Label).Y - TemporaryMousePosition.Y) / mainWindow.Height * LookSensitivity; // MousePosY is the X axis of a rotation
QuatX = Quaternion.Multiply(new Quaternion(new Vector3D(1, 0, 0), -RotX), PreviousQuatX);
QuatY = Quaternion.Multiply(new Quaternion(new Vector3D(0, 1, 0), -RotY), PreviousQuatY);
Quaternion QuaternionRotation = Quaternion.Multiply(QuatX, QuatY); // Composite Quaternion between the x rotation and the y rotation
mainWindow.camRotateTransform.Rotation = new QuaternionRotation3D(QuaternionRotation); // MainCamera.Transform = RotateTransform3D 'camRotateTransform'
}
}
}
public void MiddleMouseButton_MouseDown(object sender, MouseEventArgs e) // Declares some constants when mouse button 3 is first held down
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
TemporaryMousePosition = e.GetPosition(sender as Label);
PreviousCameraPosition = mainWindow.MainCamera.Position;
PreviousQuatX = QuatX;
PreviousQuatY = QuatY;
}
}
public void MouseUp(object sender, MouseEventArgs e)
{
mainWindow.CameraCenter = new Point3D(
mainWindow.CameraCenter.X + mainWindow.MainCamera.Position.X - mainWindow.OriginalCamPosition.X,
mainWindow.CameraCenter.Y + mainWindow.MainCamera.Position.Y - mainWindow.OriginalCamPosition.Y,
mainWindow.CameraCenter.Z + mainWindow.MainCamera.Position.Z - mainWindow.OriginalCamPosition.Z);
// Sets the center of rotation of cam to current mouse position
} // Declares some constants when mouse button 3 is first let go
public void ZoomInOutViewport_MouseScroll(object sender, MouseWheelEventArgs e)
{
var cam = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault().MainCamera;
if (e.Delta > 0) // Wheel scrolled forwards - Zoom In
{
cam.Position = new Point3D(cam.Position.X, cam.Position.Y, cam.Position.Z - ZoomInOutDistance);
}
else // Wheel scrolled forwards - Zoom Out
{
cam.Position = new Point3D(cam.Position.X, cam.Position.Y, cam.Position.Z + ZoomInOutDistance);
}
}
// -----CODE IN 'public MainWindow()' STRUCT-----
/*
public PerspectiveCamera MainCamera = new PerspectiveCamera();
public AxisAngleRotation3D MainCamAngle;
public RotateTransform3D camRotateTransform;
public Point3D CameraCenter = new Point3D(0, 0, 0);
public Point3D OriginalCamPosition;
public MainWindow()
{
Viewport3D Viewport = new Viewport3D();
CameraPan cameraPan = new CameraPan(); // Initialises CameraPan class
MainCamera.Position = new Point3D(0, 2, 10);
MainCamera.FieldOfView = 60;
MainCamera.LookDirection = cameraPan.LookDirection(MainCamera, new Point3D(0, 0, 0));
// Some custom camera settings
OriginalCamPosition = MainCamera.Position;
// Saves the MainCamera's first position
camRotateTransform = new RotateTransform3D() // Rotation of camera
{
CenterX = CameraCenter.X,
CenterY = CameraCenter.Y,
CenterZ = CameraCenter.Z,
};
MainCamAngle = new AxisAngleRotation3D() // Rotation value of camRotateTransform
{
Axis = new Vector3D(1, 0, 0),
Angle = 0
};
camRotateTransform.Rotation = MainCamAngle;
MainCamera.Transform = camRotateTransform;
Border viewportHitBG = new Border() { Width = Width, Height = Height, Background = new SolidColorBrush(Colors.White) };
// UI Element to detect mouse click events
viewportHitBG.MouseMove += cameraPan.PanLookAroundViewport_MouseMove;
viewportHitBG.MouseDown += cameraPan.MiddleMouseButton_MouseDown;
viewportHitBG.MouseWheel += cameraPan.ZoomInOutViewport_MouseScroll;
viewportHitBG.MouseUp += cameraPan.MouseUp;
// Mouse Event handlers
// Assign the camera to the viewport
Viewport.Camera = MainCamera;
// Assign Viewport as the child of the UI Element that detects mouse events
viewportHitBG.Child = Viewport;
}
*/
}
鼠标事件处理程序根据鼠标和键事件运行指定的摄像机平移功能。该设置类似于Unity Viewport控件(鼠标中键四处查看,鼠标中键+ CTRL平移,滚轮缩放)。
如果需要的话,这里是我对镜头平移的完整实现。它包含一个场景,该场景绘制了一个红色立方体,并允许您使用相机在场景中平移:
public partial class MainWindow : Window
{
private readonly TranslateTransform3D Position;
private readonly RotateTransform3D Rotation;
private readonly AxisAngleRotation3D Transform_Rotation;
private readonly ScaleTransform3D Scale;
public PerspectiveCamera MainCamera = new PerspectiveCamera();
public AxisAngleRotation3D MainCamAngle;
public RotateTransform3D camRotateTransform;
public Point3D CameraCenter = new Point3D(0, 0, 0);
public Point3D OriginalCamPosition;
public MainWindow()
{
InitializeComponent();
Height = SystemParameters.PrimaryScreenHeight;
Width = SystemParameters.PrimaryScreenWidth;
WindowState = WindowState.Maximized;
#region Initialising 3D Scene Objects
// Declare scene objects.
Viewport3D Viewport = new Viewport3D();
Model3DGroup ModelGroup = new Model3DGroup();
GeometryModel3D Cube = new GeometryModel3D();
ModelVisual3D CubeModel = new ModelVisual3D();
#endregion
#region UI Grid Objects
Grid grid = new Grid();
Slider AngleSlider = new Slider()
{
Height = 50,
VerticalAlignment = VerticalAlignment.Top,
};
AngleSlider.ValueChanged += AngleSlider_MouseMove;
grid.Children.Add(AngleSlider);
#endregion
#region Camera Stuff
CameraPan cameraPan = new CameraPan();
MainCamera.Position = new Point3D(0, 2, 10);
MainCamera.FieldOfView = 60;
MainCamera.LookDirection = cameraPan.LookDirection(MainCamera, new Point3D(0, 0, 0));
OriginalCamPosition = MainCamera.Position;
camRotateTransform = new RotateTransform3D()
{
CenterX = CameraCenter.X,
CenterY = CameraCenter.Y,
CenterZ = CameraCenter.Z,
};
MainCamAngle = new AxisAngleRotation3D()
{
Axis = new Vector3D(1, 0, 0),
Angle = 0
};
camRotateTransform.Rotation = MainCamAngle;
MainCamera.Transform = camRotateTransform;
Border viewportHitBG = new Border() { Width = Width, Height = Height, Background = new SolidColorBrush(Colors.White) };
viewportHitBG.MouseMove += cameraPan.PanLookAroundViewport_MouseMove;
viewportHitBG.MouseDown += cameraPan.MiddleMouseButton_MouseDown;
viewportHitBG.MouseWheel += cameraPan.ZoomInOutViewport_MouseScroll;
viewportHitBG.MouseUp += cameraPan.MouseUp;
// Asign the camera to the viewport
Viewport.Camera = MainCamera;
#endregion
#region Directional Lighting
// Define the lights cast in the scene. Without light, the 3D object cannot
// be seen. Note: to illuminate an object from additional directions, create
// additional lights.
AmbientLight ambientLight = new AmbientLight
{
Color = Colors.WhiteSmoke,
};
ModelGroup.Children.Add(ambientLight);
#endregion
#region Mesh Of Object
Vector3DCollection Normals = new Vector3DCollection
{
new Vector3D(0, 0, 1),
new Vector3D(0, 0, 1),
new Vector3D(0, 0, 1),
new Vector3D(0, 0, 1),
new Vector3D(0, 0, 1),
new Vector3D(0, 0, 1)
};
PointCollection TextureCoordinates = new PointCollection
{
new Point(0, 0),
new Point(1, 0),
new Point(1, 1),
new Point(0, 1),
};
Point3DCollection Positions = new Point3DCollection
{
new Point3D(-0.5, -0.5, 0.5), // BL FRONT 0
new Point3D(0.5, -0.5, 0.5), // BR FRONT 1
new Point3D(0.5, 0.5, 0.5), // TR FRONT 2
new Point3D(-0.5, 0.5, 0.5), // TL FRONT 3
new Point3D(-0.5, -0.5, -0.5), // BL BACK 4
new Point3D(0.5, -0.5, -0.5), // BR BACK 5
new Point3D(0.5, 0.5, -0.5), // TR BACK 6
new Point3D(-0.5, 0.5, -0.5) // TL BACK 7
};
MeshGeometry3D Faces = new MeshGeometry3D()
{
Normals = Normals,
Positions = Positions,
TextureCoordinates = TextureCoordinates,
TriangleIndices = new Int32Collection
{
0, 1, 2, 2, 3, 0,
6, 5, 4, 4, 7, 6,
4, 0, 3, 3, 7, 4,
2, 1, 5, 5, 6, 2,
7, 3, 2, 2, 6, 7,
1, 0, 4, 4, 5, 1
},
};
// Apply the mesh to the geometry model.
Cube.Geometry = Faces;
#endregion
#region Material Of Object
// The material specifies the material applied to the 3D object.
// Define material and apply to the mesh geometries.
Material myMaterial = new DiffuseMaterial(new SolidColorBrush(Color.FromScRgb(255, 255, 0, 0)));
Cube.Material = myMaterial;
#endregion
#region Transform Of Object
// Apply a transform to the object. In this sample, a rotation transform is applied, rendering the 3D object rotated.
Transform_Rotation = new AxisAngleRotation3D()
{
Angle = 0,
Axis = new Vector3D(0, 0, 0)
};
Position = new TranslateTransform3D
{
OffsetX = 0,
OffsetY = 0,
OffsetZ = 0
};
Scale = new ScaleTransform3D
{
ScaleX = 1,
ScaleY = 1,
ScaleZ = 1
};
Rotation = new RotateTransform3D
{
Rotation = Transform_Rotation
};
Transform3DGroup transformGroup = new Transform3DGroup();
transformGroup.Children.Add(Rotation);
transformGroup.Children.Add(Scale);
transformGroup.Children.Add(Position);
Cube.Transform = transformGroup;
#endregion
#region Adding Children To Groups And Parents
// Add the geometry model to the model group.
ModelGroup.Children.Add(Cube);
CubeModel.Content = ModelGroup;
Viewport.Children.Add(CubeModel);
viewportHitBG.Child = Viewport;
grid.Children.Add(viewportHitBG);
#endregion
Content = grid;
}
private void AngleSlider_MouseMove(object sender, RoutedEventArgs e)
{
Slider slider = (Slider)sender;
Transform_Rotation.Angle = slider.Value * 36;
Transform_Rotation.Axis = new Vector3D(0, 1, 0);
Scale.ScaleX = slider.Value / 5; Scale.ScaleY = slider.Value / 5; Scale.ScaleZ = slider.Value / 5;
Position.OffsetX = slider.Value / 5; Position.OffsetY = slider.Value / 5; Position.OffsetZ = slider.Value / 5;
}
}
public partial class CameraPan
{
Point TemporaryMousePosition;
Point3D PreviousCameraPosition;
Quaternion QuatX;
Quaternion PreviousQuatX;
Quaternion QuatY;
Quaternion PreviousQuatY;
private readonly float PanSpeed = 4f;
private readonly float LookSensitivity = 100f;
private readonly float ZoomInOutDistance = 1f;
private readonly MainWindow mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
public Vector3D LookDirection(PerspectiveCamera camera, Point3D pointToLookAt) // Calculates vector direction between two points (LookAt() method)
{
Point3D CameraPosition = camera.Position;
Vector3D VectorDirection = new Vector3D
(pointToLookAt.X - CameraPosition.X,
pointToLookAt.Y - CameraPosition.Y,
pointToLookAt.Z - CameraPosition.Z);
return VectorDirection;
}
public void PanLookAroundViewport_MouseMove(object sender, MouseEventArgs e) // Panning the viewport using the camera
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
Point mousePos = e.GetPosition(sender as Border); // Gets the current mouse pos
Point3D newCamPos = new Point3D(
((-mousePos.X + TemporaryMousePosition.X) / mainWindow.Width * PanSpeed) + PreviousCameraPosition.X,
((mousePos.Y - TemporaryMousePosition.Y) / mainWindow.Height * PanSpeed) + PreviousCameraPosition.Y,
mainWindow.MainCamera.Position.Z); // Calculates the proportional distance to move the camera,
//can be increased by changing the variable 'PanSpeed'
if (Keyboard.IsKeyDown(Key.LeftCtrl)) // Pan viewport
{
mainWindow.MainCamera.Position = newCamPos;
}
else // Look around viewport
{
double RotY = (e.GetPosition(sender as Label).X - TemporaryMousePosition.X) / mainWindow.Width * LookSensitivity; // MousePosX is the Y axis of a rotation
double RotX = (e.GetPosition(sender as Label).Y - TemporaryMousePosition.Y) / mainWindow.Height * LookSensitivity; // MousePosY is the X axis of a rotation
QuatX = Quaternion.Multiply(new Quaternion(new Vector3D(1, 0, 0), -RotX), PreviousQuatX);
QuatY = Quaternion.Multiply(new Quaternion(new Vector3D(0, 1, 0), -RotY), PreviousQuatY);
Quaternion QuaternionRotation = Quaternion.Multiply(QuatX, QuatY); // Composite Quaternion between the x rotation and the y rotation
mainWindow.camRotateTransform.Rotation = new QuaternionRotation3D(QuaternionRotation); // MainCamera.Transform = RotateTransform3D 'camRotateTransform'
}
}
}
public void MiddleMouseButton_MouseDown(object sender, MouseEventArgs e) // Declares some constants when mouse button 3 is first held down
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
TemporaryMousePosition = e.GetPosition(sender as Label);
PreviousCameraPosition = mainWindow.MainCamera.Position;
PreviousQuatX = QuatX;
PreviousQuatY = QuatY;
}
}
public void MouseUp(object sender, MouseEventArgs e)
{
mainWindow.CameraCenter = new Point3D(
mainWindow.CameraCenter.X + mainWindow.MainCamera.Position.X - mainWindow.OriginalCamPosition.X,
mainWindow.CameraCenter.Y + mainWindow.MainCamera.Position.Y - mainWindow.OriginalCamPosition.Y,
mainWindow.CameraCenter.Z + mainWindow.MainCamera.Position.Z - mainWindow.OriginalCamPosition.Z);
// Sets the center of rotation of cam to current mouse position
} // Declares some constants when mouse button 3 is first let go
public void ZoomInOutViewport_MouseScroll(object sender, MouseWheelEventArgs e)
{
var cam = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault().MainCamera;
if (e.Delta > 0) // Wheel scrolled forwards - Zoom In
{
cam.Position = new Point3D(cam.Position.X, cam.Position.Y, cam.Position.Z - ZoomInOutDistance);
}
else // Wheel scrolled forwards - Zoom Out
{
cam.Position = new Point3D(cam.Position.X, cam.Position.Y, cam.Position.Z + ZoomInOutDistance);
}
}
// -----CODE IN 'public MainWindow()' STRUCT-----
/*
public PerspectiveCamera MainCamera = new PerspectiveCamera();
public AxisAngleRotation3D MainCamAngle;
public RotateTransform3D camRotateTransform;
public Point3D CameraCenter = new Point3D(0, 0, 0);
public Point3D OriginalCamPosition;
public MainWindow()
{
Viewport3D Viewport = new Viewport3D();
CameraPan cameraPan = new CameraPan(); // Initialises CameraPan class
MainCamera.Position = new Point3D(0, 2, 10);
MainCamera.FieldOfView = 60;
MainCamera.LookDirection = cameraPan.LookDirection(MainCamera, new Point3D(0, 0, 0));
// Some custom camera settings
OriginalCamPosition = MainCamera.Position;
// Saves the MainCamera's first position
camRotateTransform = new RotateTransform3D() // Rotation of camera
{
CenterX = CameraCenter.X,
CenterY = CameraCenter.Y,
CenterZ = CameraCenter.Z,
};
MainCamAngle = new AxisAngleRotation3D() // Rotation value of camRotateTransform
{
Axis = new Vector3D(1, 0, 0),
Angle = 0
};
camRotateTransform.Rotation = MainCamAngle;
MainCamera.Transform = camRotateTransform;
Border viewportHitBG = new Border() { Width = Width, Height = Height, Background = new SolidColorBrush(Colors.White) };
// UI Element to detect mouse click events
viewportHitBG.MouseMove += cameraPan.PanLookAroundViewport_MouseMove;
viewportHitBG.MouseDown += cameraPan.MiddleMouseButton_MouseDown;
viewportHitBG.MouseWheel += cameraPan.ZoomInOutViewport_MouseScroll;
viewportHitBG.MouseUp += cameraPan.MouseUp;
// Mouse Event handlers
// Assign the camera to the viewport
Viewport.Camera = MainCamera;
// Assign Viewport as the child of the UI Element that detects mouse events
viewportHitBG.Child = Viewport;
}
*/
}
我希望它将来对某人有帮助!