2010-08-19 11 views
3

Imaginez que vous ayez une toile que vous souhaitez mettre à l'échelle à une valeur très élevée, puis autorisez le "panoramique".Traduire une toile WPF à des facteurs d'échelle élevés n'est pas lisse loin de l'origine

Un bon exemple est un outil géographique qui doit permettre un panoramique avec des niveaux de "zoom" à partir de l'étendue de la terre jusqu'à quelques mètres.

J'ai trouvé que si vous êtes mis à l'échelle de plus de, disons 500 000, la traduction devient très erratique, mais seulement si vous regardez loin de l'origine 0,0 de la toile!

J'ai essayé de traduire à l'aide de RenderTransform du canvas ET je l'ai essayé en déplaçant littéralement anothercanvas sur le canevas redimensionné. J'ai également vu le même problème dans l'exemple d'application en ligne de quelqu'un d'autre.

L'exemple de code suivant permet de faire un panoramique (cliquer et glisser) à deux emplacements de zoom différents. Si vous implémentez le code, vous pouvez appuyer sur un bouton pour zoomer vers 0,0 où vous trouverez un panoramique agréable et fluide. Ensuite, utilisez l'autre bouton pour zoomer en 200, 200 et le panoramique n'est plus!

Une idée pourquoi c'est ou comment on pourrait le réparer?

XAML pour l'échantillon:

<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> 
code

pour l'échantillon:

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

    } 
} 

Répondre

1

Ceci est dû en toile elle-même. Il n'est pas très performant pour un rendu plus avancé. Insted vous devez utiliser une classe Visual. C'est un peu plus difficile mais vous obtenez l'avantage d'un rendu de bas niveau.

Solution download

Voici le code: 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; 
    } 

} 
} 

Le système de glissement doit être réécrit. L'idée est simple mais la mise en œuvre est un peu compliquée.

+0

Merci pour la réponse! J'ai peur qu'il y ait deux problèmes avec cette solution suggérée. Tout d'abord, je n'essaie pas de faire glisser un élément visuel spécifique, j'essaie de faire un panoramique sur l'ensemble de la toile (déplacez tous les visuels sur la toile). Dans un exemple concret, un zoom avant pourrait révéler des détails fins avec beaucoup d'objets visuels. J'ai besoin de faire un panoramique autour de ces objets comme si je décalais la toile. Deuxièmement, je crains que votre effet de traînée à 200 200 n'est toujours pas lisse! Check it out - vous pouvez attraper le carré, mais alors vous devez faire glisser votre souris un peu et il saute en place, puis faites glisser un peu plus et il saute à nouveau, etc – FTLPhysicsGuy

+0

il fonctionne sur mon ordinateur portable et ce n'est pas le démon de la vitesse :) Sur la deuxième pensée 1mln zoom + paning toile entière avec beaucoup d'éléments (egzample terre entière) est impossible. ScaleTransform n'est pas supérieur à 1000 IMO. Qui plus est, il n'est pas nécessaire de transformer et de rendre 0.0 carré lorsque vous faites glisser 200.200.Vous devez rendre ce que vous voyez à l'écran + 40%, 50% pour un panoramique fluide et un certain nombre de données doivent être chargées dynamiquement pendant le zoom avant/arrière. En d'autres termes, rends ce que tu vois et un peu plus. C'est beaucoup de travail pour faire un tel conteneur. –