2010-08-19 23 views
3

想象一下,您有一個畫布,您想要縮放到很高的值,然後允許「平移」。翻譯一個高比例的WPF畫布因素並非離原點平滑

一個很好的例子是一個地理工具,需要允許從整個地球範圍到幾米範圍內的「縮放」級別進行平移。

我發現,如果您縮放到超過500,000,翻譯變得非常不穩定,但只有當您遠離畫布的0,0原點觀看!

我試着翻譯使用畫布的RenderTransform,我試圖通過在縮放畫布上移動另一個畫布。我在別人的在線示例應用程序中也看到了同樣的問題。

以下示例代碼提供了在兩個不同縮放位置進行平移(單擊並拖動)。如果你實現了代碼,你可以點擊一個按鈕放大到0,0,你會發現很好的,平滑的鼠標平移。然後使用其他按鈕放大到200,200平滑平移不再!

任何想法爲什麼這是或如何可以解決它?

XAML樣品:

<Window x:Class="TestPanZoom.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="Window1" Height="500" Width="500" Loaded="Window_Loaded"> 
    <Grid PreviewMouseLeftButtonDown="Grid_PreviewMouseLeftButtonDown" MouseMove="Grid_MouseMove"> 
     <Canvas Name="canvas1"></Canvas> 
     <Button Height="31" 
       Name="button1" 
       Click="button1_Click" 
       Margin="12,12,0,0" 
       VerticalAlignment="Top" 
       HorizontalAlignment="Left" Width="270"> 
      Zoom WAY in to 0,0 and get smooth panning 
     </Button> 
     <Button Height="31" 
       Name="button2" 
       Click="button2_Click" 
       Margin="12,49,0,0" 
       VerticalAlignment="Top" 
       HorizontalAlignment="Left" 
       Width="270"> 
      Zoom WAY in to 200, 200 -- NO smooth panning 
     </Button> 
    </Grid> 
</Window> 

代碼樣品:

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

namespace TestPanZoom 
{ 
    /// <summary> 
    /// Interaction logic for Window1.xaml 
    /// Demo of an issue with translate transform when scale is very high 
    /// but ONLY when you are far from the canvas's 0,0 origin. 
    /// Why? Is their a fix? 
    /// </summary> 
    public partial class Window1 : Window 
    { 
     Point m_clickPoint; 

     public Window1() 
     { 
      InitializeComponent(); 
     } 

     private void Window_Loaded(object sender, RoutedEventArgs e) 
     { 
      // Add a 2x2 black ellipse centered at 0,0 
      Ellipse el = new Ellipse(); 
      el.Fill = Brushes.Black; 
      el.Width = 2; 
      el.Height = 2; 
      el.HorizontalAlignment = HorizontalAlignment.Left; 
      el.VerticalAlignment = VerticalAlignment.Top; 
      el.Margin = new Thickness(0 - el.Width/2, 0 - el.Height/2, 0, 0); 
      canvas1.Children.Add(el); 

      // Add a 1x1 red rectangle with its top/left corner at 0,0 
      Rectangle r = new Rectangle(); 
      r.Fill = Brushes.Red; 
      r.Width = 1; 
      r.Height = 1; 
      r.HorizontalAlignment = HorizontalAlignment.Left; 
      r.VerticalAlignment = VerticalAlignment.Top; 
      r.Margin = new Thickness(0, 0, 0, 0); 
      canvas1.Children.Add(r); 


      // Add a 2x2 purple ellipse at a point 200,200 
      Point otherPoint = new Point(200, 200); 
      el = new Ellipse(); 
      el.Fill = Brushes.Purple; 
      el.Width = 2; 
      el.Height = 2; 
      el.HorizontalAlignment = HorizontalAlignment.Left; 
      el.VerticalAlignment = VerticalAlignment.Top; 
      el.Margin = new Thickness(otherPoint.X - el.Width/2, otherPoint.Y - el.Height/2, 0, 0); 
      canvas1.Children.Add(el); 

      // Add a 1x1 blue rectangle with its top/left corner at 200,200 
      r = new Rectangle(); 
      r.Fill = Brushes.Blue; 
      r.Width = 1; 
      r.Height = 1; 
      r.HorizontalAlignment = HorizontalAlignment.Left; 
      r.VerticalAlignment = VerticalAlignment.Top; 
      r.Margin = new Thickness(otherPoint.X, otherPoint.Y, 0, 0); 
      canvas1.Children.Add(r); 
     } 


     private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
     { 
      m_clickPoint = e.GetPosition(this); 
     } 

     // Pan with the mouse when left-mouse is down 
     private void Grid_MouseMove(object sender, MouseEventArgs e) 
     { 
      if (e.LeftButton == MouseButtonState.Pressed) 
      { 
       Point mousePosition = e.GetPosition(this); 
       double xDiff = mousePosition.X - m_clickPoint.X; 
       double yDiff = mousePosition.Y - m_clickPoint.Y; 

       TranslateTransform tt = new TranslateTransform(xDiff, yDiff); 
       TransformGroup tg = new TransformGroup(); 
       tg.Children.Add(canvas1.RenderTransform); 
       tg.Children.Add(tt); 
       canvas1.RenderTransform = tg; 

       m_clickPoint = e.GetPosition(this); 
      } 
     } 

     private void button1_Click(object sender, RoutedEventArgs e) 
     { 
      TransformGroup tg = new TransformGroup(); 
      double scale = 1000000; 
      double xCenter = 0; 
      double yCenter = 0; 
      double xOffset = (canvas1.ActualHeight/2.0 - xCenter); 
      double yOffset = (canvas1.ActualWidth/2.0 - yCenter); 
      ScaleTransform st = new ScaleTransform(scale, scale); 
      st.CenterX = xCenter; 
      st.CenterY = yCenter; 
      TranslateTransform tt = new TranslateTransform(xOffset, yOffset); 
      tg.Children.Add(st); 
      tg.Children.Add(tt); 
      canvas1.RenderTransform = tg; 
     } 

     private void button2_Click(object sender, RoutedEventArgs e) 
     { 
      TransformGroup tg = new TransformGroup(); 
      double scale = 1000000; 
      double xCenter = 200; 
      double yCenter = 200; 
      double xOffset = (canvas1.ActualHeight/2.0 - xCenter); 
      double yOffset = (canvas1.ActualWidth/2.0 - yCenter); 
      ScaleTransform st = new ScaleTransform(scale, scale); 
      st.CenterX = xCenter; 
      st.CenterY = yCenter; 
      TranslateTransform tt = new TranslateTransform(xOffset, yOffset); 
      tg.Children.Add(st); 
      tg.Children.Add(tt); 
      canvas1.RenderTransform = tg; 
     } 

    } 
} 

回答

1

這是由帆布本身引起的。對於更高級的渲染來說,它效果不佳。 Insted你需要使用Visual類。這有點困難,但你獲得低級渲染的優勢。

Solution download

下面是代碼:MainWindow.xaml

<Window x:Class="VisualTest.MainWindow" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
Title="VisualLayer" Height="350.4" Width="496.8" 
xmlns:local="clr-namespace:VisualTest" 
> 
<Grid> 
    <Grid.ColumnDefinitions> 
     <ColumnDefinition Width="Auto"></ColumnDefinition> 
     <ColumnDefinition></ColumnDefinition> 
    </Grid.ColumnDefinitions> 

    <StackPanel Orientation="Vertical"> 
     <Button Name="button1" Click="button1_Click" Margin="5" Padding="5,0"> 
      Zoom WAY in to 0,0 
     </Button> 
     <Button Name="button2" Click="button2_Click" Margin="5" Padding="5,0"> 
      Zoom WAY in to 200, 200 
     </Button> 
     <Button Name="button3" Click="button3_Click" Margin="5" Padding="5,0"> 
      Zoom back 
     </Button> 
    </StackPanel> 
    <local:DrawingCanvas Grid.Column="1" x:Name="drawingSurface" Background="White" ClipToBounds="True" 
         MouseLeftButtonDown="drawingSurface_MouseLeftButtonDown" 
      MouseLeftButtonUp="drawingSurface_MouseLeftButtonUp" 
      MouseMove="drawingSurface_MouseMove"> 
    </local:DrawingCanvas> 
</Grid> 

MainWindow.xaml.cs

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

namespace VisualTest 
{ 
/// <summary> 
/// Interaction logic for MainWindow.xaml 
/// </summary> 
public partial class MainWindow : Window 
{ 
    // Variables for dragging shapes. 
    private bool isDragging = false; 
    private Vector clickOffset; 
    private DrawingVisual selectedVisual; 
    // Drawing constants. 
    private Brush drawingBrush = Brushes.Black; 
    private Brush selectedDrawingBrush = Brushes.LightGoldenrodYellow; 
    private Pen drawingPen = new Pen(Brushes.SteelBlue, 3); 
    private Size squareSize = new Size(10, 10); 

    public MainWindow() 
    { 
     InitializeComponent(); 
     DrawingVisual v = new DrawingVisual(); 
     DrawSquare(v, new Point(0, 0)); 
     drawingSurface.AddVisual(v); 
     v = new DrawingVisual(); 
     DrawSquare(v, new Point(200, 200)); 
     drawingSurface.AddVisual(v); 
    } 

    private void button1_Click(object sender, RoutedEventArgs e) 
    { 
     TransformGroup tg = new TransformGroup(); 
     double scale = 1000000; 
     double xCenter = 0; 
     double yCenter = 0; 
     double xOffset = (drawingSurface.ActualHeight/2.0 - xCenter); 
     double yOffset = (drawingSurface.ActualWidth/2.0 - yCenter); 
     ScaleTransform st = new ScaleTransform(scale, scale); 
     st.CenterX = xCenter; 
     st.CenterY = yCenter; 
     TranslateTransform tt = new TranslateTransform(xOffset, yOffset); 
     tg.Children.Add(st); 
     tg.Children.Add(tt); 
     drawingSurface.RenderTransform = st; 
    } 

    private void button2_Click(object sender, RoutedEventArgs e) 
    { 
     TransformGroup tg = new TransformGroup(); 
     double scale = 1000000; 
     double xCenter = 200; 
     double yCenter = 200; 
     double xOffset = (drawingSurface.ActualHeight/2.0 - xCenter); 
     double yOffset = (drawingSurface.ActualWidth/2.0 - yCenter); 
     ScaleTransform st = new ScaleTransform(scale, scale); 
     st.CenterX = xCenter; 
     st.CenterY = yCenter; 
     TranslateTransform tt = new TranslateTransform(xOffset, yOffset); 
     tg.Children.Add(st); 
     tg.Children.Add(tt); 
     drawingSurface.RenderTransform = st; 
    } 

    private void button3_Click(object sender, RoutedEventArgs e) 
    { 
     ScaleTransform st = new ScaleTransform(1, 1); 
     drawingSurface.RenderTransform = st; 
    } 

    private void drawingSurface_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 
    { 
     Point pointClicked = e.GetPosition(drawingSurface); 
     DrawingVisual visual = drawingSurface.GetVisual(pointClicked); 
     if (visual != null) 
     { 
      // Calculate the top-left corner of the square. 
      // This is done by looking at the current bounds and 
      // removing half the border (pen thickness). 
      // An alternate solution would be to store the top-left 
      // point of every visual in a collection in the 
      // DrawingCanvas, and provide this point when hit testing. 
      Point topLeftCorner = new Point(
       visual.ContentBounds.TopLeft.X , 
       visual.ContentBounds.TopLeft.Y); 
      DrawSquare(visual, topLeftCorner); 

      clickOffset = topLeftCorner - pointClicked; 
      isDragging = true; 

      if (selectedVisual != null && selectedVisual != visual) 
      { 
       // The selection has changed. Clear the previous selection. 
       ClearSelection(); 
      } 
      selectedVisual = visual; 
     }   
    } 

    // Rendering the square. 
    private void DrawSquare(DrawingVisual visual, Point topLeftCorner) 
    { 
     using (DrawingContext dc = visual.RenderOpen()) 
     { 
      Brush brush = drawingBrush; 
      dc.DrawRectangle(brush, null, 
       new Rect(topLeftCorner, squareSize)); 
     } 
    } 

    private void drawingSurface_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 
    { 
     isDragging = false; 
    } 

    private void ClearSelection() 
    { 
     Point topLeftCorner = new Point(
        selectedVisual.ContentBounds.TopLeft.X , 
        selectedVisual.ContentBounds.TopLeft.Y); 
     DrawSquare(selectedVisual, topLeftCorner); 
     selectedVisual = null; 
    } 

    private void drawingSurface_MouseMove(object sender, MouseEventArgs e) 
    { 
     if (isDragging) 
     { 
      Point pointDragged = e.GetPosition(drawingSurface) + clickOffset; 
      DrawSquare(selectedVisual, pointDragged); 
     } 
    } 
} 
} 

DrawingCanvas.cs

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Windows.Media; 
using System.Windows.Controls; 
using System.Windows; 

namespace VisualTest 
{ 
public class DrawingCanvas : Panel 
{ 
    private List<Visual> visuals = new List<Visual>(); 

    protected override Visual GetVisualChild(int index) 
    { 
     return visuals[index]; 
    } 
    protected override int VisualChildrenCount 
    { 
     get 
     { 
      return visuals.Count; 
     } 
    } 

    public void AddVisual(Visual visual) 
    { 
     visuals.Add(visual); 

     base.AddVisualChild(visual); 
     base.AddLogicalChild(visual); 
    } 

    public void DeleteVisual(Visual visual) 
    { 
     visuals.Remove(visual); 

     base.RemoveVisualChild(visual); 
     base.RemoveLogicalChild(visual);    
    } 

    public DrawingVisual GetVisual(Point point) 
    { 
     HitTestResult hitResult = VisualTreeHelper.HitTest(this, point); 
     return hitResult.VisualHit as DrawingVisual;    
    } 

    private List<DrawingVisual> hits = new List<DrawingVisual>(); 
    public List<DrawingVisual> GetVisuals(Geometry region) 
    { 
     hits.Clear(); 
     GeometryHitTestParameters parameters = new GeometryHitTestParameters(region); 
     HitTestResultCallback callback = new HitTestResultCallback(this.HitTestCallback); 
     VisualTreeHelper.HitTest(this, null, callback, parameters); 
     return hits; 
    } 

    private HitTestResultBehavior HitTestCallback(HitTestResult result) 
    { 
     GeometryHitTestResult geometryResult = (GeometryHitTestResult)result; 
     DrawingVisual visual = result.VisualHit as DrawingVisual; 
     if (visual != null && 
      geometryResult.IntersectionDetail == IntersectionDetail.FullyInside) 
     { 
      hits.Add(visual); 
     } 
     return HitTestResultBehavior.Continue; 
    } 

} 
} 

拖動系統需要重寫。這個想法很簡單,但實現有點複雜。

+0

感謝您的回覆! 恐怕這個建議的解決方案有兩個問題。首先,我不想拖動特定的視覺項目,我試圖平移整個畫布(移動畫布上的所有視覺效果)。在一個現實世界的例子中,放大可以顯示很多可視對象的細節。我需要圍繞這些物體平移,就好像轉移畫布一樣。 其次,我恐怕你的拖動效果在200,200還不夠流暢!檢查一下 - 你可以抓住方塊,但你必須拖動鼠標一點點,然後跳到一個地方,然後再拖一下,再跳一遍,等等。 – FTLPhysicsGuy 2010-08-20 14:16:16

+0

它在我的筆記本電腦上運行平穩,並不是速度的惡魔:)關於第二個想法1mln zoom + paning與許多元素(整個地球egzample)的整個畫布是不可能的。 ScaleTransform不會超過1000個IMO。當你拖動200.200時,沒有必要轉換和渲染0.0平方。你需要渲染你在屏幕上看到的+ 40%,50%的光滑平移,並且在放大/縮小時應該動態加載獨立級別的解鎖。換句話說,渲染你看到的和更多一點。製造這種容器是很多工作。 – 2010-08-20 15:13:42