Introduction
In this tip, we are going to see how we can make a specific item contained in a viewbox
not being scaled automatically by it. This is useful if we want to have an element with a specific size that is inside a Viewbox
. For example, some type of marker in a resizable image.
Background
In this article, we can see an easy way to accomplish this to apply the invert of the Transform
that the viewbox
is applying to his child item. This can be done from XAML directly but we need to add this code for each XAML element.
<Button.LayoutTransform>
<MultiBinding Converter="{bc:ExemptFromViewboxTransformConverter}">
<Binding Source="{x:Reference Name=viewboxName}" />
-->
<Binding Source="{x:Reference Name=viewboxName}" Path="ActualWidth" />
<Binding Source="{x:Reference Name=viewboxName}" Path="ActualHeight" />
</MultiBinding>
</Button.LayoutTransform>
And we need to create a Converter that implements the following code:
((ContainerVisual)VisualTreeHelper.GetChild((DependencyObject)viewbox, 0)).Transform.Inverse
This is not hard to do but it's not easy either and the resulting XAML can be very ugly.
Using the Code
In order to accomplish this, we have created a new class that will hold the new attached properties for the Viewbox
that will do the trick.
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ViewBoxPrototype
{
public class ViewBoxExtra
{
public static void SetDisableScaling(DependencyObject element, bool value)
{
element.SetValue(DisableScalingProperty, value);
}
public static bool GetDisableScaling(DependencyObject element)
{
return (bool)element.GetValue(DisableScalingProperty);
}
public static readonly DependencyProperty DisableScalingProperty =
DependencyProperty.RegisterAttached("DisableScaling",
typeof(bool), typeof(Viewbox), new UIPropertyMetadata(AddScalingHandler));
public static void SetNonScaledObjectsProperty
(DependencyObject element, List<DependencyObject> value)
{
element.SetValue(NonScaledObjectsProperty, value);
}
public static List<DependencyObject>
GetNonScaledObjectsProperty(DependencyObject element)
{
return (List<DependencyObject>)element.GetValue(NonScaledObjectsProperty);
}
public static readonly DependencyProperty NonScaledObjectsProperty =
DependencyProperty.RegisterAttached("NonScaledObjects",
typeof(List<DependencyObject>), typeof(Viewbox), null);
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);
}
}
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;
}
}
}
private static Viewbox ObtainParentViewbox(DependencyObject d)
{
var parent = VisualTreeHelper.GetParent(d);
return parent is Viewbox ? parent as Viewbox : ObtainParentViewbox(parent);
}
}
}
We just need to add this class to the project or the content in any class that you create in your project.
In order to use it from the XAML, we just need to add 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>
This is it!
The XAML file is clean and understandable and we have the desired effect.
Flow of Execution
- We set the
DisableScaling
property to true
for an XAML element.
- The
AddScalingHandler
is called for each element.
- When a resize is done to the
viewbox
, the ViewBox_SizeChanged
is invoked.
- For each element inside the
NonScaledObjects
, we apply the inverse matrix to the transform in order to draw the element with his correct size.
Points of Interest
I wanted to do this because I wanted to get the final result but without soiling the XAML file and making it ugly. A clean XAML is something that we aspire but it is very hard to obtain.
This was my first article, so if there is something to improve on, please comment on it and I will gladly provide an answer.
History
- 06/30/2016: First version