禁用特定元素的 Viewbox 缩放
如何使 Viewbox 中的项不被其缩放。
引言
在本技巧中,我们将学习如何使 viewbox
包含的特定项目不被其自动缩放。如果我们需要在 Viewbox
内部具有特定大小的元素,这将非常有用。例如,可调整大小的图像中的某种类型的标记。
背景
在 这篇文章 中,我们可以看到一种简单的方法来实现这一点,即应用 viewbox
应用于其子项的 Transform
的反转。这可以直接从 XAML 完成,但我们需要为每个 XAML 元素添加此代码。
<Button.LayoutTransform>
<MultiBinding Converter="{bc:ExemptFromViewboxTransformConverter}">
<Binding Source="{x:Reference Name=viewboxName}" />
<!-- The ActualWidth/ActualHeight bindings are only used to ensure
the transform updates when the window is resized. -->
<Binding Source="{x:Reference Name=viewboxName}" Path="ActualWidth" />
<Binding Source="{x:Reference Name=viewboxName}" Path="ActualHeight" />
</MultiBinding>
</Button.LayoutTransform>
我们需要创建一个实现以下代码的转换器
((ContainerVisual)VisualTreeHelper.GetChild((DependencyObject)viewbox, 0)).Transform.Inverse
这并不难,但也不容易,并且生成的 XAML 可能会非常丑陋。
Using the Code
为了实现这一点,我们创建了一个新类,该类将持有 Viewbox
的新附加属性,从而完成此操作。
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ViewBoxPrototype
{
public class ViewBoxExtra
{
/// <summary>
/// Setter for the DisableScaling attached property
/// </summary>
/// <param name="element"></param>
/// <param name="value"></param>
public static void SetDisableScaling(DependencyObject element, bool value)
{
element.SetValue(DisableScalingProperty, value);
}
/// <summary>
/// Getter for the DisableScaling attached property
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static bool GetDisableScaling(DependencyObject element)
{
return (bool)element.GetValue(DisableScalingProperty);
}
/// <summary>
/// This property is the one that we will set in the XAML elements
/// in order to indicate that we don't want that item to resize
/// </summary>
public static readonly DependencyProperty DisableScalingProperty =
DependencyProperty.RegisterAttached("DisableScaling",
typeof(bool), typeof(Viewbox), new UIPropertyMetadata(AddScalingHandler));
/// <summary>
/// Setter for the NonScaledObjects attached property
/// </summary>
/// <param name="element"></param>
/// <param name="value"></param>
public static void SetNonScaledObjectsProperty
(DependencyObject element, List<DependencyObject> value)
{
element.SetValue(NonScaledObjectsProperty, value);
}
/// <summary>
/// Getter for the NonScaledObjects attached property
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static List<DependencyObject>
GetNonScaledObjectsProperty(DependencyObject element)
{
return (List<DependencyObject>)element.GetValue(NonScaledObjectsProperty);
}
/// <summary>
/// This property is the one that will hold the references
/// to the objects that will not be resized for that viewbox
/// </summary>
public static readonly DependencyProperty NonScaledObjectsProperty =
DependencyProperty.RegisterAttached("NonScaledObjects",
typeof(List<DependencyObject>), typeof(Viewbox), null);
/// <summary>
/// Handler used to prepare the NonScaleObjects list and
/// to link the size changed event of the viewbox to our handler
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void AddScalingHandler
(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
try
{
var viewBox = ObtainParentViewbox(d);
var nonScaleObjects =
(List<DependencyObject>)viewBox.GetValue(NonScaledObjectsProperty);
if ((bool)e.NewValue && nonScaleObjects == null)
{
nonScaleObjects = new List<DependencyObject>();
viewBox.SetValue(NonScaledObjectsProperty, nonScaleObjects);
viewBox.SizeChanged += ViewBox_SizeChanged;
}
if ((bool)e.NewValue)
{
nonScaleObjects.Add(d);
}
else
{
nonScaleObjects.Remove(d);
}
}
catch (NullReferenceException exc)
{
throw new Exception
("The element must be contained inside an ViewBoxExtra", exc);
}
}
/// <summary>
/// This will be called when a viewBox has any item with the property DisableScaling=true
/// The main process to invert the object is done here.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void ViewBox_SizeChanged(object sender, SizeChangedEventArgs e)
{
var viewBox = sender as Viewbox;
var transform =
((ContainerVisual)VisualTreeHelper.GetChild(sender as DependencyObject, 0)).Transform;
if (transform != null && viewBox != null)
{
foreach (var nonScaleObject in
(List<DependencyObject>)viewBox.GetValue(NonScaledObjectsProperty))
{
var element = (FrameworkElement)nonScaleObject;
element.LayoutTransform = (Transform)transform.Inverse;
}
}
}
/// <summary>
/// Method in order to obtain the viewbox that contains the item
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
private static Viewbox ObtainParentViewbox(DependencyObject d)
{
var parent = VisualTreeHelper.GetParent(d);
return parent is Viewbox ? parent as Viewbox : ObtainParentViewbox(parent);
}
}
}
我们只需要将此类添加到项目或您在项目中创建的任何类中的内容即可。
为了从 XAML 中使用它,我们只需要添加 ViewBoxExtra.DisableScaling="true"
。
<Viewbox Grid.Row="1">
<Canvas Width="1024" Height="334">
<Rectangle Width="50" Height="50"
Canvas.Left="10" Canvas.Top="10"
Stroke="Red" local:ViewBoxExtra.DisableScaling="true"/>
<Rectangle Width="50" Height="50"
Canvas.Left="100" Canvas.Top="10"
Stroke="Green"/>
<Rectangle Width="50" Height="50"
Canvas.Left="200" Canvas.Top="10"
Stroke="Yellow" local:ViewBoxExtra.DisableScaling="true"/>
<Rectangle Width="50" Height="50"
Canvas.Left="300" Canvas.Top="10"
Stroke="Blue" local:ViewBoxExtra.DisableScaling="true"/>
</Canvas>
</Viewbox>
就是这样了!
XAML 文件简洁易懂,并且我们得到了预期的效果。
执行流程
- 我们将
DisableScaling
属性设置为true
,用于 XAML 元素。 - 为每个元素调用
AddScalingHandler
。 - 当对
viewbox
进行调整大小时,将调用ViewBox_SizeChanged
。 - 对于
NonScaledObjects
内部的每个元素,我们将反向矩阵应用于变换,以便以其正确的大小绘制该元素。
关注点
我希望这样做是因为我想要获得最终结果,但又不想弄脏 XAML 文件并使其变得丑陋。简洁的 XAML 是我们所追求的,但很难实现。
这是我的第一篇文章,如果还有什么需要改进的地方,请发表评论,我将很乐意提供答案。
历史
- 2016/06/30:第一个版本