0

我正在使用 Canvas 作为 ItemPanel 的 ItemsControl。尝试根据其他问题的答案实现缩放行为。

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

主窗口.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 模式,缩放会按预期发生在鼠标位置。问题是,背景没有填满容器/窗口。 在此处输入图像描述
  • 如果我使用 LayoutTransform 模式,背景会填充窗口,但缩放不会发生在鼠标位置。变换原点位于 (0,0)。 在此处输入图像描述
  • 无论我选择哪种变换模式,都不会激活 ScrollBar-s。

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

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

4

1 回答 1

0

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

// 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 内,以便用户在放大时可以滚动。为此,您需要绑定到 Horizo​​ntalOffset 和 VerticalOffset 属性的行为
  • 您还需要知道滚动查看器客户区的宽度和高度,因此您需要修改该行为以同时为它们提供属性。
  • 您需要跟踪当前鼠标相对于 Canvas 的坐标,即拦截 MouseEnter/MouseMove/MouseLeave 并维护该MouseOnCanvas属性。
于 2019-11-26T23:57:25.680 回答