我正在尝试制作一个选择矩形,可以更改其位置和大小,还可以旋转它 - 一切都将通过拖动鼠标来完成。
我移动成功,但旋转和调整大小时遇到问题。
在旋转之前调整大小工作正常,但在旋转之后,矩形在调整大小时会改变位置。
第一次旋转时,一切似乎都工作正常,但第二次矩形会跳跃并改变位置。
附上一张图片,以及我写的代码。
如何让
RotateThumb
和CenterBottomThumb
正常工作?
我将完整的项目上传到 GitHub,这里。
<Window x:Class="SelectionRect.MainWindow"
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:SelectionRect"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="400"
Height="400"
d:DataContext="{d:DesignInstance Type=local:RectModel}"
mc:Ignorable="d">
<Window.Resources>
<Style x:Key="ThumbStyle" TargetType="Thumb">
<Setter Property="Focusable" Value="True" />
<Setter Property="Height" Value="7" />
<Setter Property="Width" Value="7" />
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Background" Value="LightBlue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Canvas x:Name="Canvas1">
<Grid Canvas.Left="{Binding Left}" Name="RectGrid"
Canvas.Top="{Binding Top}"
Width="{Binding Width}"
Height="{Binding Height}">
<Grid.LayoutTransform>
<RotateTransform Angle="{Binding Angle}" />
</Grid.LayoutTransform>
<Border Background="#4C0000FF" BorderBrush="Blue" BorderThickness="1" />
<Thumb Name="LeftTopThumb"
Margin="-5,-5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Cursor="SizeNWSE"
DragDelta="LeftTopThumb_DragDelta"
Style="{StaticResource ThumbStyle}" />
<Thumb Name="RightTopThumb"
Margin="0,-5,-5,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Cursor="SizeNESW"
DragDelta="RightTopThumb_DragDelta"
Style="{StaticResource ThumbStyle}" />
<Thumb Name="LeftBottomThumb"
Margin="-5,0,0,-5"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Cursor="SizeNESW"
DragDelta="LeftBottomThumb_DragDelta"
Style="{StaticResource ThumbStyle}" />
<Thumb Name="RightBottomThumb"
Margin="0,0,-5,-5"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Cursor="SizeNWSE"
DragDelta="RightBottomThumb_DragDelta"
Style="{StaticResource ThumbStyle}" />
<Thumb Name="CenterBottomThumb"
Margin="0,0,0,-5"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Cursor="SizeNS"
DragDelta="CenterBottomThumb_DragDelta"
Style="{StaticResource ThumbStyle}" />
<Thumb Name="CenterTopThumb"
Margin="0,-5,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Cursor="SizeNS"
DragDelta="CenterTopThumb_DragDelta"
Style="{StaticResource ThumbStyle}" />
<Thumb Name="RotateThumb"
Margin="0,-25,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Cursor="Cross"
DragDelta="RotateThumb_DragDelta"
DragStarted="RotateThumb_DragStarted"
Style="{StaticResource ThumbStyle}" />
<Thumb Name="CenterLeftThumb"
Margin="-5,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Cursor="SizeWE"
DragDelta="CenterLeftThumb_DragDelta"
Style="{StaticResource ThumbStyle}" />
<Thumb Name="CenterRightThumb"
Margin="0,0,-5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Cursor="SizeWE"
DragDelta="CenterRightThumb_DragDelta"
Style="{StaticResource ThumbStyle}" />
<Thumb x:Name="CenterThumb"
Width="Auto"
Height="Auto"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Cursor="SizeAll"
DragDelta="CenterThumb_DragDelta"
Focusable="True">
<Thumb.Template>
<ControlTemplate>
<Border Name="CenterThumbBorder"
Margin="10"
Background="Transparent"
MouseDown="CenterThumbBorder_MouseDown" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
</Grid>
</Canvas>
</Grid>
</Window>
cs:
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using Linq;
namespace SelectionRect
{
public partial class MainWindow : Window
{
private double _initialLeft;
private double _initialTop;
public MainWindow()
{
InitializeComponent();
DataContext = new RectModel() { Height = 200, Width = 100 , Left = 20, Top = 50};
}
private void CenterBottomThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
var thumb = sender as Thumb;
var model = thumb.DataContext as RectModel;
if (model.Height + e.VerticalChange >= 1)
{
model.Height += e.VerticalChange;
}
}
private void CenterTopThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
}
private void RotateThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
var thumb = sender as Thumb;
var model = thumb.DataContext as RectModel;
var initialAngle = model.Angle;
var center = new Point(model.CenterX, model.CenterY);
center = RectGrid.TranslatePoint(center, Canvas1);
var currentPos = Mouse.GetPosition(Canvas1);
model.Angle = center.GetAngle(currentPos) + 90;
var Rect = new Rect(_initialLeft, _initialTop, model.Width, model.Height);
var cornerPoints = new[] { Rect.TopLeft, Rect.TopRight, Rect.BottomRight, Rect.BottomLeft };
var m = new Matrix();
m.RotateAt(model.Angle, center.X, center.Y);
m.Transform(cornerPoints);
model.Left = cornerPoints.Min(p => p.X);
model.Top = cornerPoints.Min(p => p.Y);
}
private void RotateThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
var thumb = sender as Thumb;
var model = thumb.DataContext as RectModel;
_initialLeft = model.Left;
_initialTop = model.Top;
}
private void CenterLeftThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
}
private void CenterRightThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
}
private void CenterThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
var thumb = sender as Thumb;
var model = thumb.DataContext as RectModel;
model.Top += e.VerticalChange;
model.Left += e.HorizontalChange;
}
private void CenterThumbBorder_MouseDown(object sender, MouseButtonEventArgs e)
{
}
private void LeftBottomThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
}
private void LeftTopThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
}
private void RightTopThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
}
private void RightBottomThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
}
}
public static class Ex
{
public static double GetAngle(this System.Windows.Point p1, System.Windows.Point p2)
{
var xDiff = p2.X - p1.X;
var yDiff = p2.Y - p1.Y;
return Math.Atan2(yDiff, xDiff) * 180.0 / Math.PI;
}
}
class RectModel : INotifyPropertyChanged
{
private double _Angle;
public double Angle
{
get { return _Angle; }
set
{
if (value == _Angle) return;
_Angle = value;
OnPropertyChanged(nameof(Angle));
}
}
private double _Height;
public double Height
{
get { return _Height; }
set
{
if (value == _Height) return;
_Height = value;
OnPropertyChanged(nameof(Height));
}
}
private double _Width;
public double Width
{
get { return _Width; }
set
{
if (value == _Width) return;
_Width = value;
OnPropertyChanged(nameof(Width));
}
}
private double _Top;
public double Top
{
get { return _Top; }
set
{
if (value == _Top) return;
_Top = value;
OnPropertyChanged(nameof(Top));
}
}
private double _Left;
public double Left
{
get { return _Left; }
set
{
if (value == _Left) return;
_Left = value;
OnPropertyChanged(nameof(Left));
}
}
public double CenterX => Width / 2;
public double CenterY => Height / 2;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
应用旋转时,变换原点和坐标空间会发生变化:您应该确保旋转的变换原点设置为矩形的中心。这将使旋转行为更加可预测。
并且在调整大小时,您需要考虑当前的旋转以正确计算新的大小和位置。
在
RotateThumb_DragDelta
中,您正确地重新计算了角度,但您还需要在旋转后调整矩形的位置以确保其保持在原位。这涉及考虑新角度重新计算 Left
和 Top
属性。Left
和 Top
。
private void RotateThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
var thumb = sender as Thumb;
var model = thumb.DataContext as RectModel;
var initialAngle = model.Angle;
var center = new Point(model.CenterX, model.CenterY);
center = RectGrid.TranslatePoint(center, Canvas1);
var currentPos = Mouse.GetPosition(Canvas1);
model.Angle = center.GetAngle(currentPos) + 90;
var rotatedRect = RotateRect(new Rect(_initialLeft, _initialTop, model.Width, model.Height), model.Angle, center);
model.Left = rotatedRect.Left;
model.Top = rotatedRect.Top;
}
private Rect RotateRect(Rect rect, double angle, Point center)
{
var cornerPoints = new[] { rect.TopLeft, rect.TopRight, rect.BottomRight, rect.BottomLeft };
var m = new Matrix();
m.RotateAt(angle, center.X, center.Y);
m.Transform(cornerPoints);
double minX = cornerPoints.Min(p => p.X);
double minY = cornerPoints.Min(p => p.Y);
double maxX = cornerPoints.Max(p => p.X);
double maxY = cornerPoints.Max(p => p.Y);
return new Rect(minX, minY, maxX - minX, maxY - minY);
}
您还需要实现其他拇指(
DragDelta
、CenterTopThumb
、CenterLeftThumb
等)的CenterRightThumb
事件的逻辑,类似于您对CenterBottomThumb
所做的操作。根据拖动的拇指和当前的旋转来计算新的大小和位置。
private void CenterTopThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
// Logic to resize from the top, considering current rotation
// Similar implementations are needed for other thumbs
}
对于调整大小方法(
CenterTopThumb_DragDelta
等),您需要实现类似的逻辑,在考虑当前旋转和位置的同时调整大小。这将防止矩形在调整大小时意外改变位置。