WPF画布缩放和子位置 [英] WPF Canvas zoom and children position

查看:60
本文介绍了WPF画布缩放和子位置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在画布上实现缩放行为.画布和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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆