WPF画布放大鼠标位置

时间:2019-11-26 19:02:41

标签: wpf canvas scrollbar rendertransform layouttransform

我正在使用Canvas作为ItemPanel的ItemsControl。尝试根据other question的答案来实现缩放行为。

复制所需的最小代码应该是这样。

MainWindow.xaml

<Window x:Class="WpfApp7.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp7"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.Resources>
    <Style TargetType="{x:Type local:DesignerSheetView}" BasedOn="{StaticResource {x:Type ItemsControl}}">
        <Style.Resources>
        </Style.Resources>
        <Setter Property="ItemsControl.ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <Canvas  local:ZoomBehavior.IsZoomable="True" local:ZoomBehavior.ZoomFactor="0.1" local:ZoomBehavior.ModifierKey="Ctrl">
                        <Canvas.Background>
                            <VisualBrush TileMode="Tile" Viewport="-1,-1,20,20" ViewportUnits="Absolute" Viewbox="-1,-1,20,20" ViewboxUnits="Absolute">
                                <VisualBrush.Visual>
                                    <Grid Width="20" Height="20">
                                        <Ellipse Height="2" Width="2" Stroke="Black" StrokeThickness="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="-1,-1" />
                                    </Grid>
                                </VisualBrush.Visual>
                            </VisualBrush>
                        </Canvas.Background>
                    </Canvas>
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ItemsControl.ItemContainerStyle">
            <Setter.Value>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Top" Value="{Binding Path=YPos}" />
                    <Setter Property="Canvas.Left" Value="{Binding Path=XPos}" />
                    <Setter Property="VerticalAlignment" Value="Stretch" />
                    <Setter Property="HorizontalAlignment" Value="Stretch" />
                </Style>
            </Setter.Value>
        </Setter>
        <Setter Property="Focusable" Value="True" />
        <Setter Property="IsEnabled" Value="True" />

    </Style>
</Window.Resources>

<Grid>
    <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <local:DesignerSheetView Background="Beige">

        </local:DesignerSheetView>
    </ScrollViewer>
</Grid>

DesignerSheetView背后的代码:

    public class DesignerSheetView : ItemsControl
{
    static DesignerSheetView()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(DesignerSheetView), new FrameworkPropertyMetadata(typeof(DesignerSheetView)));
    }
}

修改后的ZoomBehavior

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

namespace WpfApp7
{
public static class ZoomBehavior
{
    //example from https://stackoverflow.com/questions/46424149/wpf-zoom-canvas-center-on-mouse-position

    #region ZoomFactor
    public static double GetZoomFactor(DependencyObject obj)
    {
        return (double)obj.GetValue(ZoomFactorProperty);
    }
    public static void SetZoomFactor(DependencyObject obj, double value)
    {
        obj.SetValue(ZoomFactorProperty, value);
    }
    // Using a DependencyProperty as the backing store for ZoomFactor.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ZoomFactorProperty =
        DependencyProperty.RegisterAttached("ZoomFactor", typeof(double), typeof(ZoomBehavior), new PropertyMetadata(1.05));
    #endregion

    #region ModifierKey       
    public static ModifierKeys? GetModifierKey(DependencyObject obj)
    {
        return (ModifierKeys?)obj.GetValue(ModifierKeyProperty);
    }
    public static void SetModifierKey(DependencyObject obj, ModifierKeys? value)
    {
        obj.SetValue(ModifierKeyProperty, value);
    }
    // Using a DependencyProperty as the backing store for ModifierKey.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ModifierKeyProperty =
        DependencyProperty.RegisterAttached("ModifierKey", typeof(ModifierKeys?), typeof(ZoomBehavior), new PropertyMetadata(null));
    #endregion
    public static TransformMode ModeOfTransform { get; set; } = TransformMode.Layout;
    private static Transform _transform;
    private static Canvas _view;

    #region IsZoomable        
    public static bool GetIsZoomable(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsZoomableProperty);
    }
    public static void SetIsZoomable(DependencyObject obj, bool value)
    {
        obj.SetValue(IsZoomableProperty, value);
    }
    // Using a DependencyProperty as the backing store for IsZoomable.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsZoomableProperty =
        DependencyProperty.RegisterAttached(
            "IsZoomable",
            typeof(bool),
            typeof(ZoomBehavior),
            new UIPropertyMetadata(false, OnIsZoomableChanged));
    #endregion

    private static void OnIsZoomableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        _view = d as Canvas;
        if (null == _view)
        {
            System.Diagnostics.Debug.Assert(false, "Wrong dependency object type");
            return;
        }
        if ((e.NewValue is bool) == false)
        {
            System.Diagnostics.Debug.Assert(false, "Wrong value type assigned to dependency object");
            return;
        }

        if (true == (bool)e.NewValue)
        {
            _view.MouseWheel += Canvas_MouseWheel;
            if (ModeOfTransform == TransformMode.Render)
            {
                _transform = _view.RenderTransform = new MatrixTransform();
            }
            else
            {
                _transform = _view.LayoutTransform = new MatrixTransform();
            }
        }
        else
        {
            _view.MouseWheel -= Canvas_MouseWheel;
        }
    }





    public static double GetZoomScale(DependencyObject obj)
    {
        return (double)obj.GetValue(ZoomScaleProperty);
    }

    public static void SetZoomScale(DependencyObject obj, double value)
    {
        obj.SetValue(ZoomScaleProperty, value);
    }

    // Using a DependencyProperty as the backing store for ZoomScale.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ZoomScaleProperty =
        DependencyProperty.RegisterAttached("ZoomScale", typeof(double), typeof(ZoomBehavior), new PropertyMetadata(1.0));



    private static void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        ModifierKeys? modifierkey = GetModifierKey(sender as DependencyObject);

        if (!modifierkey.HasValue)
        {
            return;
        }

        if((Keyboard.Modifiers & (modifierkey.Value)) == ModifierKeys.None)
        {
            return;
        }

        if (!(_transform is MatrixTransform transform))
        {
            return;
        }

        var pos1 = e.GetPosition(_view);

        double zoomfactor = GetZoomFactor(sender as DependencyObject);
        double scale = GetZoomScale(sender as DependencyObject);
        //scale = (e.Delta < 0) ? (scale * zoomfactor) : (scale / zoomfactor);
        scale = (e.Delta < 0) ? (scale + zoomfactor) : (scale - zoomfactor);
        scale = (scale < 0.1) ? 0.1 : scale;
        SetZoomScale(sender as DependencyObject, scale);

        var mat = transform.Matrix;            
        mat.ScaleAt(scale, scale, pos1.X, pos1.Y);
        //transform.Matrix = mat;
        if (TransformMode.Layout == ModeOfTransform)
        {
            _view.LayoutTransform = new MatrixTransform(mat);
        }
        else
        {
            _view.RenderTransform = new MatrixTransform(mat);
        }

        e.Handled = true;
    }

    public enum TransformMode
    {
        Layout,
        Render,
    }
}

}

我认为ZoomBehavior应该还可以,我并没有做太多改动。问题出在xaml中。我观察到很多事情,为此我寻求解决方案:

  • 如果使用RenderTransform模式,则缩放将按预期在鼠标位置进行。问题是,背景没有填充容器/窗口。 enter image description here
  • 如果我使用LayoutTransform模式,背景将填满窗口,但是鼠标位置不会发生缩放。变换原点位于(0,0)。 enter image description here
  • 无论我选择哪种转换模式,都不会激活ScrollBar-s。

关于SO的大多数问题都始于问询者,他们试图通过布局转换解决缩放问题。几乎所有答案都使用RenderTransform而不是LayoutTrasform(例如thisthisthis)。没有一个答案可以解释为什么RenderTransform比LayoutTransform更适合该任务。这是因为使用LayoutTransform还需要更改Canvas的位置吗?

为了使RenderTransform工作(背景填充整个容器和ScrollBars出现),我应该改变什么?

1 个答案:

答案 0 :(得分:0)

基本上,您必须将LayoutTransform应用于Canvas(或父网格),通过缩放点的倒数对其进行平移(考虑当前缩放因子),然后将其再次转换回其原始位置(考虑新的缩放比例)。所以这个:

// zoom level. typically changes by +/- 1 (or some other constant)
// at a time and updates this.Zoom which is the actual zoom
// multiplication factor.
private double _ZoomLevel = 1;
private double ZoomLevel
{
    get { return this._ZoomLevel; }
    set
    {
        var zoomPointX = this.ViewportWidth / 2;
        var zoomPointY = this.ViewportHeight / 2;
        if (this.MouseOnCanvas)
        {
            zoomPointX = this.LastMousePos.X * this.Zoom - this.HorizontalOffset;
            zoomPointY = this.LastMousePos.Y * this.Zoom - this.VerticalOffset;
        }
        var imageX = (this.HorizontalOffset + zoomPointX) / this.Zoom;
        var imageY = (this.VerticalOffset + zoomPointY) / this.Zoom;
        this._ZoomLevel = value;
        this.Zoom = 0.25 * Math.Pow(Math.Sqrt(2), value);
        this.HorizontalOffset = imageX * this.Zoom - zoomPointX;
        this.VerticalOffset = imageY * this.Zoom - zoomPointY;
    }
}

关于此代码的几点注意事项:

  • 它假定Canvas位于ScrollViewer内部,以便用户在放大时可以滚动。为此,您需要a behavior that binds to the HorizontalOffset and VerticalOffset properties
  • 您还需要知道scrollviewer客户区的宽度和高度,因此您需要修改该行为以为其提供属性。
  • 您需要跟踪相对于Canvas的当前鼠标坐标,这意味着拦截MouseEnter / MouseMove / MouseLeave并保持MouseOnCanvas属性。