近期项目需要用WPF绘制,要求能实现缩放、平移以及图元的单独移动,图元还要求支持选取、双击等操作,参考GitHub - SEilers/WpfPanAndZoom: A panning and zooming canvas for WPF.以及相关文章,记录下实现过程。
缩放操作:使用滚轮实现缩放
平移操作:按住鼠标右键,实现画布的平移;鼠标左键按住画布上的图元,实现图元的平移
复位操作:重置画布比例和位置
1、xaml文件
<Canvas
x:Class="WpfPanAndZoom.CustomControls.PanAndZoomCanvas"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfPanAndZoom.CustomControls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
Background="#333333"
mc:Ignorable="d">
</Canvas>
2、cs文件,
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
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.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
using TrainControl.Sharp;
namespace WpfPanAndZoom.CustomControls
{
/// <summary>
/// Interaktionslogik für PanAndZoomCanvas.xaml
/// https://stackoverflow.com/questions/35165349/how-to-drag-rendertransform-with-mouse-in-wpf
/// </summary>
public partial class PanAndZoomCanvas : Canvas
{
#region Variables
private readonly MatrixTransform _transform = new MatrixTransform();
private Point _initialMousePosition;
private bool _dragging;
private UIElement _selectedElement;
private Vector _draggingDelta;
private Color _backgroundColor = Colors.Black;
private List<Line> _gridLines = new List<Line>();
#endregion
public float Zoomfactor { get; set; } = 1.1f;
public PanAndZoomCanvas()
{
InitializeComponent();
MouseDown += PanAndZoomCanvas_MouseDown;
MouseUp += PanAndZoomCanvas_MouseUp;
MouseMove += PanAndZoomCanvas_MouseMove;
MouseWheel += PanAndZoomCanvas_MouseWheel;
BackgroundColor = _backgroundColor;
}
public Color BackgroundColor
{
get { return _backgroundColor; }
set
{
_backgroundColor = value;
Background = new SolidColorBrush(_backgroundColor);
}
}
#region 平移、缩放和复位操作
/// <summary>
/// 新增复位操作
/// </summary>
public void Reset()
{
Zoomfactor = 1.1f
foreach (UIElement child in this.Children)
{
child.RenderTransform = Transform.Identity;
}
}
private void PanAndZoomCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Right)
{
_initialMousePosition = _transform.Inverse.Transform(e.GetPosition(this));
}
if (e.ChangedButton == MouseButton.Left)
{
if (this.Children.Contains((UIElement)e.Source))
{
_selectedElement = (UIElement)e.Source;
Point mousePosition = Mouse.GetPosition(this);
double x = Canvas.GetLeft(_selectedElement);
double y = Canvas.GetTop(_selectedElement);
Point elementPosition = new Point(x, y);
_draggingDelta = elementPosition - mousePosition;
}
_dragging = true;
}
}
private void PanAndZoomCanvas_MouseUp(object sender, MouseButtonEventArgs e)
{
_dragging = false;
_selectedElement = null;
}
private void PanAndZoomCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.RightButton == MouseButtonState.Pressed)
{
Point mousePosition = _transform.Inverse.Transform(e.GetPosition(this));
Vector delta = Point.Subtract(mousePosition, _initialMousePosition);
var translate = new TranslateTransform(delta.X, delta.Y);
_transform.Matrix = translate.Value * _transform.Matrix;
foreach (UIElement child in this.Children)
{
child.RenderTransform = _transform;
}
}
if (_dragging && e.LeftButton == MouseButtonState.Pressed)
{
double x = Mouse.GetPosition(this).X;
double y = Mouse.GetPosition(this).Y;
if (_selectedElement != null)
{
Canvas.SetLeft(_selectedElement, x + _draggingDelta.X);
Canvas.SetTop(_selectedElement, y + _draggingDelta.Y);
}
}
}
private void PanAndZoomCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
float scaleFactor = Zoomfactor;
if (e.Delta < 0)
{
scaleFactor = 1f / scaleFactor;
}
Point mousePostion = e.GetPosition(this);
Matrix scaleMatrix = _transform.Matrix;
scaleMatrix.ScaleAt(scaleFactor, scaleFactor, mousePostion.X, mousePostion.Y);
_transform.Matrix = scaleMatrix;
foreach (UIElement child in this.Children)
{
double x = Canvas.GetLeft(child);
double y = Canvas.GetTop(child);
double sx = x * scaleFactor;
double sy = y * scaleFactor;
Canvas.SetLeft(child, sx);
Canvas.SetTop(child, sy);
child.RenderTransform = _transform;
}
}
#endregion
}
}
图元的移动,使用Canvas的SerLeft和SetTop方法实现
3、使用
<Window
x:Class="TrainControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomControls="clr-namespace:WpfPanAndZoom.CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TrainControl"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="1024"
Height="768"
mc:Ignorable="d">
<Grid>
<CustomControls:PanAndZoomCanvas
x:Name="canvas"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
</CustomControls:PanAndZoomCanvas>
<Button
Name="btnReset"
Margin="20,87,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="btnReset_Click"
Content="复位" />
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
RegenGeometry();
}
/// <summary>
/// Canvas复位
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnReset_Click(object sender, RoutedEventArgs e)
{
canvas.Reset();
}
/// <summary>
/// 绘制图元
/// </summary>
private void RegenGeometry()
{
canvas.Children.Clear();
// DrawVisual方式绘制文本的时候,需要用到
double dpi = VisualTreeHelper.GetDpi(this).PixelsPerDip;
TrainSingalService.Instance.Regen(canvas, dpi);
}
}
下一篇再描述TrainSingalService.Instance.Regen(canvas, dpi)函数的实现