WPF图像平移,缩放和在画布上滚动图层

时间:2013-06-05 01:20:33

标签: wpf scroll zoom scrollviewer pan

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

  1. 当我介绍ScrollViewer时,我没有得到任何滚动行为。据我所知,我需要引入一个LayoutTransform来获得正确的ScrollViewer行为。然而,当我这样做时,其他区域开始崩溃(例如,ROI在图像上没有保持正确的位置,或者当平移时鼠标指针开始从图像上的选定点开始蠕动,或者我的图像的左角弹回到MouseDown上的当前鼠标位置。)

  2. 我无法按照我需要的方式调整投资回报率。我有这个工作,但它并不理想。我所拥有的并没有保留精确的笔划厚度,我没有考虑忽略文本块上的比例。希望你会在代码示例中看到我在做什么。

  3. 我确定我的问题与我对Transforms及其与WPF布局系统的关系缺乏了解有关。希望展示我迄今所取得的成就的代码将有所帮助(见下文)。

    仅供参考,如果Adorners是建议,那可能在我的场景中不起作用,因为我最终可能会得到更多的装饰者而不是支持(谣言144装饰者就是事情开始崩溃的时候)。

    首先,下面是显示带有ROI(文本和形状)的图像的屏幕截图。矩形,椭圆和文本需要按照比例和旋转方式跟随图像上的区域,但不能在厚度或字体大小上进行缩放。

    Screen shot showing sample image with ROIs

    这是显示上述图像的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项目和解决方案中工作,假设没有剪切/粘贴错误。

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

3 个答案:

答案 0 :(得分:20)

确定。这是我对你描述的内容的看法。

看起来像这样:

enter image description here

  • 由于我没有应用任何RenderTransforms,因此我获得了所需的Scrollbar / ScrollViewer功能。
  • MVVM,这是WPF的方式。 UI和数据是独立的,因此DataItems只有X,Y,Width,Height等的doubleint属性,您可以将它们用于任何目的,甚至将它们存储在数据库中。
  • 我在Thumb内添加了所有内容以处理平移。当您通过ResizerControl拖动/调整ROI大小时,您仍需要对平移进行一些操作。我猜您可以查看Mouse.DirectlyOver或其他内容。
  • 我实际上使用ListBox来处理投资回报率,这样您在任何给定时间都可以获得1个选定的投资回报率。这会切换调整大小功能。因此,如果您点击ROI,您将看到缩放器可见。
  • Scaling在ViewModel级别处理,因此无需自定义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将定义一个控制子元素位置的附加属性,并将变换应用于此位置而不是孩子的RenderTransformLayoutTransform

这可以让您了解如何实施此类小组:

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;
        }
     */
}

我希望它将来对某人有帮助!