WPF画布缩放和子位置 [英] WPF Canvas zoom and children position
问题描述
我正在画布上实现缩放行为.画布和ScrollViewer可以正确反应,但是画布中的子项从原始位置开始漂移,发生了怪异的运动.结果,在几次放大/缩小操作之后,孩子们的位置完全不同,甚至在某些情况下甚至在画布之外!
I'm implementing a zoom behavior on my canvas. The canvas and the ScrollViewer react correctly but the children inside the canvas move weirdly, drifting from their original position. As a result, after a few of zoom in/out operation the children are in completely different position or in some cases even outside the canvas!
https://ibb.co/TwRW40Z https://ibb.co/16JKR5C
<ScrollViewer x:Name="ScrollViewerCanvas" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" CanContentScroll="True" >
<MyCanvas Panel.ZIndex="0" x:Name="nodeGraph" Width="1200" Height="790" HorizontalAlignment="Center" />
</ScrollViewer>
protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
{
if (Keyboard.Modifiers != ModifierKeys.Control)
return;
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 Children)
{
Canvas.SetLeft(child, Canvas.GetLeft(child) * scaleFactor);
Canvas.SetTop(child, Canvas.GetTop(child) * scaleFactor);
child.RenderTransform = _transform;
}
this.LayoutTransform = _transform;
}
推荐答案
我认为您遇到了麻烦,因为您正在画布上移动对象.我怀疑这是否可取.您应该调整 ScrollViewer
的位置,以将对象保持在适当的位置.
I think you are getting into trouble because you are moving the objects across the canvas. I doubt if this is desirable. You rather should adjust the ScrollViewer
position to keep the objects in position.
以下代码将缩放封装为附加行为.
The following code encapsulates the zooming into an attached behavior.
要使其正常运行,您应该始终设置 ZoomBehavior.IsEnabled ="True"
,并将父 ScrollViewer
绑定到 ZoomBehavior.ScrollViewer 代码>附加属性.
ZoomBehavior.ZoomFactor
是可选的,默认为 0.1
:
To make it work properly you should always set ZoomBehavior.IsEnabled="True"
and also bind the parent ScrollViewer
to the ZoomBehavior.ScrollViewer
attached property. The ZoomBehavior.ZoomFactor
is optional and defaults to 0.1
:
用法
<ScrollViewer x:Name="MainScrollViewer"
CanContentScroll="False"
Width="500" Height="500"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<Canvas Width="300" Height="300"
local:ZoomBehavior.IsEnabled="True"
local:ZoomBehavior.ZoomFactor="0.1"
local:ZoomBehavior.ScrollViewer="{Binding ElementName=MainScrollViewer}"
Background="DarkGray">
<Ellipse Fill="DarkOrange"
Height="100" Width="100"
Canvas.Top="100" Canvas.Left="100" />
</Canvas>
</ScrollViewer>
ZoomBehavior.cs
public class ZoomBehavior : DependencyObject
{
#region IsEnabled attached property
// Required
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
"IsEnabled", typeof(bool), typeof(ZoomBehavior), new PropertyMetadata(default(bool), ZoomBehavior.OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject attachingElement, bool value) => attachingElement.SetValue(ZoomBehavior.IsEnabledProperty, value);
public static bool GetIsEnabled(DependencyObject attachingElement) => (bool) attachingElement.GetValue(ZoomBehavior.IsEnabledProperty);
#endregion
#region ZoomFactor attached property
// Optional
public static readonly DependencyProperty ZoomFactorProperty = DependencyProperty.RegisterAttached(
"ZoomFactor", typeof(double), typeof(ZoomBehavior), new PropertyMetadata(0.1));
public static void SetZoomFactor(DependencyObject attachingElement, double value) => attachingElement.SetValue(ZoomBehavior.ZoomFactorProperty, value);
public static double GetZoomFactor(DependencyObject attachingElement) => (double) attachingElement.GetValue(ZoomBehavior.ZoomFactorProperty);
#endregion
#region ScrollViewer attached property
// Optional
public static readonly DependencyProperty ScrollViewerProperty = DependencyProperty.RegisterAttached(
"ScrollViewer", typeof(ScrollViewer), typeof(ZoomBehavior), new PropertyMetadata(default(ScrollViewer)));
public static void SetScrollViewer(DependencyObject attachingElement, ScrollViewer value) => attachingElement.SetValue(ZoomBehavior.ScrollViewerProperty, value);
public static ScrollViewer GetScrollViewer(DependencyObject attachingElement) => (ScrollViewer) attachingElement.GetValue(ZoomBehavior.ScrollViewerProperty);
#endregion
private static void OnIsEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is FrameworkElement frameworkElement))
{
throw new ArgumentException("Attaching element must be of type FrameworkElement");
}
bool isEnabled = (bool) e.NewValue;
if (isEnabled)
{
frameworkElement.PreviewMouseWheel += ZoomBehavior.Zoom_OnMouseWheel;
if (ZoomBehavior.TryGetScaleTransform(frameworkElement, out _))
{
return;
}
if (frameworkElement.LayoutTransform is TransformGroup transformGroup)
{
transformGroup.Children.Add(new ScaleTransform());
}
else
{
frameworkElement.LayoutTransform = new ScaleTransform();
}
}
else
{
frameworkElement.PreviewMouseWheel -= ZoomBehavior.Zoom_OnMouseWheel;
}
}
private static void Zoom_OnMouseWheel(object sender, MouseWheelEventArgs e)
{
var zoomTargetElement = sender as FrameworkElement;
Point mouseCanvasPosition = e.GetPosition(zoomTargetElement);
double scaleFactor = e.Delta > 0
? ZoomBehavior.GetZoomFactor(zoomTargetElement)
: -1 * ZoomBehavior.GetZoomFactor(zoomTargetElement);
ZoomBehavior.ApplyZoomToAttachedElement(mouseCanvasPosition, scaleFactor, zoomTargetElement);
ZoomBehavior.AdjustScrollViewer(mouseCanvasPosition, scaleFactor, zoomTargetElement);
}
private static void ApplyZoomToAttachedElement(Point mouseCanvasPosition, double scaleFactor, FrameworkElement zoomTargetElement)
{
if (!ZoomBehavior.TryGetScaleTransform(zoomTargetElement, out ScaleTransform scaleTransform))
{
throw new InvalidOperationException("No ScaleTransform found");
}
scaleTransform.CenterX = mouseCanvasPosition.X;
scaleTransform.CenterY = mouseCanvasPosition.Y;
scaleTransform.ScaleX = Math.Max(0.1, scaleTransform.ScaleX + scaleFactor);
scaleTransform.ScaleY = Math.Max(0.1, scaleTransform.ScaleY + scaleFactor);
}
private static void AdjustScrollViewer(Point mouseCanvasPosition, double scaleFactor, FrameworkElement zoomTargetElement)
{
ScrollViewer scrollViewer = ZoomBehavior.GetScrollViewer(zoomTargetElement);
if (scrollViewer == null)
{
return;
}
scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + mouseCanvasPosition.X * scaleFactor);
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + mouseCanvasPosition.Y * scaleFactor);
}
private static bool TryGetScaleTransform(FrameworkElement frameworkElement, out ScaleTransform scaleTransform)
{
// C# 8.0 Switch Expression
scaleTransform = frameworkElement.LayoutTransform switch
{
TransformGroup transformGroup => transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault(),
ScaleTransform transform => transform,
_ => null
};
return scaleTransform != null;
}
}
这篇关于WPF画布缩放和子位置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!