Панорамирование и зумирование холста с UIElement'ами

Описание задачи

Есть холст на котором в процессе работы можно добавлять/создавать и располагать контролы, например Label. Нужно сделать так, чтобы все содержимое можно было панорамировать и зумировать мышью (так как все просто не поместится). Наподобие того, как это реализовано в графическом редакторе Picasa.

Реализация сейчас

Долго гуглил и читал про возможности WPF, нашел реализацию контейнера с околонужным функционалом и немного изменил под свои нужды, хотя реализация очень мне не нравится. Вот код:

MyCanvas.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace FTree.UI
{
    public class MyCanvas : Canvas
    {
        private List<UIElement> elements = new List<UIElement>();
        private ScaleTransform st = new ScaleTransform();
        private TransformGroup group = new TransformGroup();
        private TranslateTransform tt = new TranslateTransform();
        private Point origin;
        private Point start;

        private TranslateTransform GetTranslateTransform(UIElement element)
        {
            return (TranslateTransform)((TransformGroup)element.RenderTransform)
              .Children.First(tr => tr is TranslateTransform);
        }

        private ScaleTransform GetScaleTransform(UIElement element)
        {
            return (ScaleTransform)((TransformGroup)element.RenderTransform)
              .Children.First(tr => tr is ScaleTransform);
        }
        

        public MyCanvas()
        {
            group.Children.Add(st);
            group.Children.Add(tt);

            this.MouseWheel += child_MouseWheel;
            this.MouseLeftButtonDown += child_MouseLeftButtonDown;
            this.MouseLeftButtonUp += child_MouseLeftButtonUp;
            this.MouseMove += child_MouseMove;
            this.PreviewMouseRightButtonDown += new MouseButtonEventHandler(
              child_PreviewMouseRightButtonDown);
        }

        public void Add(UIElement element)
        {
            element.RenderTransform = group;

            elements.Add(element);
            this.Children.Add(element);
        }

        public void Reset()
        {
            foreach(var i in elements)
            {
                // reset zoom
                var st = GetScaleTransform(i);
                st.ScaleX = 1.0;
                st.ScaleY = 1.0;

                // reset pan
                var tt = GetTranslateTransform(i);
                tt.X = 0.0;
                tt.Y = 0.0;
            }
        }

        #region Child Events

        private void child_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            foreach (var i in elements)
            {
                var st = GetScaleTransform(i);
                var tt = GetTranslateTransform(i);

                double zoom = e.Delta > 0 ? .2 : -.2;
                if (!(e.Delta > 0) && (st.ScaleX < .3 || st.ScaleY < .3))
                    return;

                Point relative = e.GetPosition(i);
                double absoluteX;
                double absoluteY;

                absoluteX = relative.X * st.ScaleX + tt.X;
                absoluteY = relative.Y * st.ScaleY + tt.Y;

                st.ScaleX += zoom;
                st.ScaleY += zoom;

                tt.X = absoluteX - relative.X * st.ScaleX;
                tt.Y = absoluteY - relative.Y * st.ScaleY;
            }
        }

        private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if(elements.Count != 0)
            {
                var i = elements[0];
                var tt = GetTranslateTransform(i);
                start = e.GetPosition(this);
                origin = new Point(tt.X, tt.Y);
                this.Cursor = Cursors.Hand;
                i.CaptureMouse();
            }
        }

        private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (elements.Count != 0)
            {
                var i = elements[0];
                i.ReleaseMouseCapture();
                this.Cursor = Cursors.Arrow;
            }
        }

        void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.Reset();
        }

        private void child_MouseMove(object sender, MouseEventArgs e)
        {
            if(Mouse.LeftButton == MouseButtonState.Pressed)
            {
                foreach (var i in elements)
                {
                    var tt = GetTranslateTransform(i);
                    Vector v = start - e.GetPosition(this);
                    tt.X = origin.X - v.X;
                    tt.Y = origin.Y - v.Y;
                }
            }
            
        }

        #endregion
    }
}

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 FTree.UI
{

    /// <summary>
    /// Логика взаимодействия для MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Rectangle rec1 = new Rectangle();
            rec1.Height = 100;
            rec1.Width = 100;
            rec1.Fill = Brushes.Orange;


            myCanvas.Add(rec1);
            MyCanvas.SetLeft(rec1, 200);
            MyCanvas.SetTop(rec1, 200);

            Rectangle rec2 = new Rectangle();
            rec2.Height = 100;
            rec2.Width = 100;
            rec2.Fill = Brushes.Orange;

            myCanvas.Add(rec2);

            MyCanvas.SetLeft(rec2, 50);
            MyCanvas.SetTop(rec2, 50);
        }
    }
}

введите сюда описание изображения введите сюда описание изображения введите сюда описание изображения

Проблема

Проблема в реализации зумировании (объекты слишком растекаются, наезжают друг на друга), а также в том, что применение подобных махинаций я решил сделать через обращение к списку элементов (думаю есть другой лучший способ). Еще раз напомню, что я ориентируюсь я то, как реализовано панорамирование и зумирование в Picasa. Видел реализации зума картинки - похожее хотелось бы сделать со множеством элементов на холсте. В каком направлении двигаться?


Ответы (0 шт):