以高比例因子平移 WPF 画布并不平滑地远离原点

发布于 2024-09-15 07:13:32 字数 6531 浏览 6 评论 0原文

想象一下,您有一个画布,您希望将其缩放到非常高的值,然后允许“平移”。

一个很好的例子是地理工具,它需要允许以“缩放”级别从整个地球范围到几米范围进行平移。

我发现,如果你的比例放大到超过 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;
        }

    }
}

Imagine you have a canvas that you want to scale to a very high value and then allow "panning."

A good example is a geographic tool that needs to allow panning with "zoom" levels from the extent of the whole earth down to the extent of a few meters.

I've found that if you are scaled in to more than, say 500,000, translating becomes very erratic, but ONLY if you are viewing far from the canvas's 0,0 origin!

I've tried to translate using the RenderTransform of the canvas AND I've tried it by literally moving anothercanvas on top of the scaled canvas. I've also seen the same issue in someone else's sample app online.

The following example code provides for panning (click and drag) at two different zoom locations. If you implement the code, you can hit one button to zoom in to 0,0 where you will find nice, smooth mouse panning. Then use the other button to zoom in to 200, 200 and smooth panning is no more!

Any idea why this is or how one could fix it?

XAML for sample:

<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 for sample:

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

    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

仅此而已 2024-09-22 07:13:32

这是Canvas本身造成的。对于更高级的渲染来说,它的性能不佳。您需要使用 Visual 类。这有点困难,但您可以获得低级渲染的优势。

解决方案下载

以下是代码: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;
    }

 }
}

拖拽系统需要重写。想法很简单,但实现起来有点复杂。

This is caused by Canvas itself. It is not well perfomed for more advanced rendering. Insted you need to use a Visual class. It's a bit harder yet you gain the advantage of low level rendering.

Solution download

Here is the 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;
    }

 }
}

Dragging system need to be rewrite. The idea is simple yet implementation is a bit complicated.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文