向WPF窗体或用户控件中添加"缩放"特效

标签: WPF C#
发布时间: 2015/1/9 15:34:29

简介

本文介绍如何在WPF应用中添加“zooming”(放大缩小)功能。代码比较简单,并且可用于大多数WPF的应用。我上传的代码展示了三种控制放大缩小的方式。

为什么需要该功能?

你是否有过这样的经历:当房间内有人在展示一个应用程序,而你却很不幸地坐在房间后边,并且改应用的字体还非常小,读起来很费劲?另一个情景就是:当一些人一起围着一个显示器,讨论显示器上的内容。正对屏幕的那个人可以很清晰的看到屏幕内容,但是他身后的其他人呢?当我准备演示一个我刚开发的应用时,我忽而想到了这个问题。。。
我意识到,有很多其他的方法来解决这个问题。比如换一个更大的显示器或者使用投影仪。然而以相对平滑的缩放形式进行展示,不失为一种简单易行,且给人视觉冲击感的方式。

效果展示

我上传的代码中,缩放因子被我写死了,范围是1.0到1.5.下面动态图是由我上传的代码创建的。用来展示三种方法来控制缩放因子。当然,还有更多的方法。比如你喜欢用鼠标滚轮或者单独的放大缩小按钮按钮来控制缩放。
第一个GIF动态图展示了使用滑动轴控制缩放的效果。

860914/Slider.gif

第二个GIF动态图展示的是使用wpf动画来实现平滑缩放。如图所示,当用户单击左侧按钮时,触发的缩放是由最小倍数放大到了最大倍数。滑块变量值已经在后代代码自动与放大因子绑定。该动画的展示速度被设置为600毫秒。

860914/Animated.gif

第三个GIF动态图展示的是使用按钮,将放大倍率从1.0瞬时切换到1.5.这种方式有些扎眼儿,不过代码却相当简单。

860914/Instant.gif

请注意观察以上三副GIF图的上方文本是如何处理的。文本是由TextBlock控制的(可实现自动换行),其他控件配合TextBlock控件,进行上下移动来适应文本框高度的变化。这些操作均由WPF的图层引擎自动完成。如果你的应用能缩放窗体大小,那么你也可以借鉴上述方式进行处理,因为使窗体缩放与内容缩放对图层的影响都是一样的。下面的GIF是一个缩放范围为0.7到1.2的实例(适应codeproject网站的页面宽度640像素)。考虑到展示效果,我把动画的渐变时间设置成了两秒。查看图片的时候请注意观察datagrid上方的控件所发生的变化。当控件宽度太大的时候,就会自动变成两行排列,并且横向、纵向滚动条会在适当的时候出现。出现这样的效果,是因为我将程序设置成了适应用户调整的状态。我只是后续添加了一个缩放的功能,结果,效果还蛮不错!

860914/RealApp.gif

代码

文中代码基于带有子控件的WPF窗体。该子控件同时需要包含所有其他控件。我们可以把该子控件命名为“mainPanel”。
缩放只是关于 设置  mainPanel.LayoutTransform 到一个ScaleTransform 实例。设置对象的ScaleX 和andScaleY 属性为你需要的倍率即可。  
通常用VS创建一个新窗体时,都会带有默认的Grid控件。而我通常使用DockPanel 或者StackPanel 来代替Grid 。至于本文讨论的缩放技巧,对所有带有LayoutTransform  属性的控件应该都有效(比如由FrameworkElement 派生的类)。
在我上传的代码中,缩放mainPanel 控件的功能已经在后台编写完毕。
若将缩放比例直接由1.0变为1.5(举例来说),用户可能会有些困惑,或者效果会比较突兀。平滑缩放和瞬时改变缩放倍率二者比较起来,用户更能明白发生了什么事情,缩放的展现过程也会比较细腻。使用包含ScaleX 和ScaleY两个属性的DoubleAnimation 类就可以很容易实现上述效果。比如,下边的C#方法(上传的代码中就有)是实现将缩放倍率从1.0平滑缩放到1.5时,所有的需要被调用的后台代码。

private void btnAnimate_Click(object sender, RoutedEventArgs e)
{
    // We may have already set the LayoutTransform to a ScaleTransform.
    // If not, do so now.

    var scaler = mainPanel.LayoutTransform as ScaleTransform;

    if (scaler == null)
    {
        scaler = new ScaleTransform(1.0, 1.0);
        mainPanel.LayoutTransform = scaler;
    }

    // We'll need a DoubleAnimation object to drive
    // the ScaleX and ScaleY properties.

    DoubleAnimation animator = new DoubleAnimation()
    {
        Duration = new Duration(TimeSpan.FromMilliseconds(600)),
    };

    // Toggle the scale between 1.0 and 1.5.

    if (scaler.ScaleX == 1.0)
    {
        animator.To = 1.5;
    }
    else
    {
        animator.To = 1.0;
    }

    scaler.BeginAnimation(ScaleTransform.ScaleXProperty, animator);
    scaler.BeginAnimation(ScaleTransform.ScaleYProperty, animator);
}

兴趣点

在编写本文所需demo的过程中,我对WPF开发有了一点新的发现。若像上文中那样对一个依赖属性比如ScaleX 设置一个动画值时,当动画完成后,该属性并不能像通常情况那样给它赋值为其他值。那是因为在依赖属性设置级别列表(Dependency Property Setting Precedence List)中,动画相关的属性设定,比其它任何操作有更高的权限,甚至直接赋值都没有它的级别高。因此,使用滑块控件实现缩放时,就需要编写一些额外的代码,而"Instant Zoom"按钮触发的事件则不需添加特殊处理,因为没有用到WPF中的动画。
比如,下面是"Instant Zoom"按钮事件的代码。"if (scaler.HasAnimatedProperties)" 这条语句用来检查用户在单击这个按钮之前,是否已经点击过"Animated Zoom"按钮。在这种情况下,仅仅执行scaler.ScaleX = 1.5 (或1.0)就不会起作用了。若想实现瞬时缩放的效果,必须处理最近一次在动画播放结束后所残留的与缩放相关的属性。

private void btnInstant_Click(object sender, RoutedEventArgs e)
{
    var scaler = mainPanel.LayoutTransform as ScaleTransform;

    if (scaler == null)
    {
        // Currently no zoom, so go instantly to max zoom.
        mainPanel.LayoutTransform = new ScaleTransform(1.5, 1.5);
    }
    else
    {
        double curZoomFactor = scaler.ScaleX;

        // If the current ScaleX and ScaleY properties were set by animation,
        // we'll have to remove the animation before we can explicitly set
        // them to "local" values.

        if (scaler.HasAnimatedProperties)
        {
            // Remove the animation by assigning a null
            // AnimationTimeline to the properties.
            // Note that this causes them to revert to
            // their most recently assigned "local" values.

            scaler.BeginAnimation(ScaleTransform.ScaleXProperty, null);
            scaler.BeginAnimation(ScaleTransform.ScaleYProperty, null);
        }

        if (curZoomFactor == 1.0)
        {
            scaler.ScaleX = 1.5;
            scaler.ScaleY = 1.5;
        }
        else
        {
            scaler.ScaleX = 1.0;
            scaler.ScaleY = 1.0;
        }
    }
}
赞助商