a·gen·tic a·gil·i·ty

Wpf Scale Transform Behaviour

TL;DR; Explains how to use a WPF behaviour with attached properties to globally scale multiple UI controls at runtime, supporting MVVM and slider-based scale adjustment.

Published on
6 minute read
Image
https://nkdagility.com/resources/PXeY0Nggg1B
Subscribe

Although this post is called Scale Transform Behaviour you could use any transform / animation in its place. The purpose is to have a slider control in a menu be able to alter the scale of any number of controls within MVVM views.

Wpf Scale Transform Behaviour

This behaviour allows you to add any Framework Elements to a list of attached controls by adding an attached property of GlobalScaleTransformBehaviour.IsScaled to your controls.

 1Public Class GlobalScaleTransformBehaviour
 2
 3    Private Shared sm_AttachedControls As List(Of FrameworkElement)
 4    Public Shared ReadOnly IsScaledProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsScaled", GetType(Boolean), GetType(GlobalScaleTransformBehaviour), New UIPropertyMetadata(False, New PropertyChangedCallback(AddressOf GlobalScaleTransformBehaviour.IsScaledChanged)))
 5    Private Shared sm_CurrentScale As Double = 1
 6
 7    Shared Sub New()
 8        sm_AttachedControls = New List(Of FrameworkElement)
 9    End Sub
10
11    Public Shared Function GetIsScaled(ByVal element As DependencyObject) As Boolean
12        If element Is Nothing Then
13            Throw New ArgumentNullException("element")
14        End If
15
16        Return element.GetValue(IsScaledProperty)
17    End Function
18
19    Public Shared Sub SetIsScaled(ByVal element As DependencyObject, ByVal value As Boolean)
20        If element Is Nothing Then
21            Throw New ArgumentNullException("element")
22        End If
23        element.SetValue(IsScaledProperty, value)
24    End Sub
25
26    Private Shared Sub IsScaledChanged(ByVal obj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
27        Dim itemToResize As FrameworkElement = TryCast(obj, FrameworkElement)
28        If (Not itemToResize Is Nothing) Then
29            If Object.Equals(e.NewValue, True) Then
30                sm_AttachedControls.Add(itemToResize)
31                itemToResize.LayoutTransform = New ScaleTransform(sm_CurrentScale, sm_CurrentScale)
32            Else
33                sm_AttachedControls.Remove(itemToResize)
34                itemToResize.LayoutTransform = New ScaleTransform(1, 1)
35            End If
36        End If
37    End Sub
38
39End Class

As you can see, there is an attached dependency Boolean property defined with a PropertyChangedCallback. When the PropertyChangedCallback method is called we test to see if it is a True or False value and either add the control to a static list and set the current Transform, or remove the control from the list and reset the transform to 1.

This works grate and you can manipulate the list of controls at runtime by changing the dependency property.

 1<igWindows:TabItemEx
 2    xmlns:igDP="http://infragistics.com/DataPresenter"
 3    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 4    xmlns:local="clr-namespace:Hinshlabs.WpfHeatItsmDashboard"
 5    xmlns:igWindows="http://infragistics.com/Windows"
 6    xmlns:igDock="http://infragistics.com/DockManager"
 7    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 8    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 9    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
10    xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
11    mc:Ignorable="d"
12    xmlns:igEditors="http://infragistics.com/Editors"
13    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
14    xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
15    x:Class="CallsView" x:Name="CallsView" MinWidth="30" MinHeight="50">
16    <igWindows:TabItemEx.Resources>
17        <local:NinjectDataProvider
18        x:Key="ViewModel"
19        d:IsDataSource="True" ObjectType="{x:Type local:CallsViewModel}"
20        />
21       <local:DateTimeSecondsToBooleanConverter x:Key="DateTimeSecondsToBooleanConverter" />
22    </igWindows:TabItemEx.Resources>
23    <igWindows:TabItemEx.Triggers>
24        <EventTrigger RoutedEvent="FrameworkElement.Loaded"/>
25    </igWindows:TabItemEx.Triggers>
26    <igWindows:TabItemEx.Header>
27        <igEditors:XamTextEditor Text="{Binding Source={StaticResource ViewModel},Path=Header, diag:PresentationTraceSources.TraceLevel=High}" />
28    </igWindows:TabItemEx.Header>
29
30    <DockPanel local:GlobalScaleTransformBehaviour.IsScaled="True" DataContext="{Binding Source={StaticResource ViewModel}}">
31        <Border DockPanel.Dock="Top" Background="LightGray" MinHeight="20">
32        <Border.Style>
33            <Style>
34                <Style.Triggers>
35                  <DataTrigger Binding="{Binding Source={StaticResource ViewModel},Path=IsLoading, diag:PresentationTraceSources.TraceLevel=High}" Value="False">
36                           <Setter Property="Border.Visibility" Value="Collapsed" />
37                  </DataTrigger>
38                    </Style.Triggers>
39            </Style>
40        </Border.Style>
41            <Label Content="Loading data..." />
42        </Border>
43        <Border DockPanel.Dock="Top" Background="LightGray" MinHeight="20">
44            <Border.Style>
45                <Style>
46                    <Style.Triggers>
47                        <DataTrigger Binding="{Binding Source={StaticResource ViewModel},Path=IsSyncing, diag:PresentationTraceSources.TraceLevel=High}" Value="False">
48                            <Setter Property="Border.Visibility" Value="Collapsed" />
49                        </DataTrigger>
50                    </Style.Triggers>
51                </Style>
52            </Border.Style>
53            <Label Content="Syncing data..." />
54        </Border>
55        <igDP:XamDataGrid DataSource="{Binding Calls}" Theme="Office2k7Blue">
56            <igDP:XamDataGrid.Resources>
57                <Style x:Key="{x:Type igDP:DataRecordCellArea}" TargetType="{x:Type igDP:DataRecordCellArea}">
58                <Style.Triggers>
59                    <DataTrigger Binding="{Binding DataItem.TypeOfCall, Converter={StaticResource DateTimeSecondsToBooleanConverter}, ConverterParameter=1}" Value="True">
60                            <Setter Property="Background">
61                                <Setter.Value>
62                                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
63                                        <LinearGradientBrush.GradientStops>
64                                            <GradientStopCollection>
65                                                <GradientStop Offset="0" Color="Red"/>
66                                                <GradientStop Offset="1" Color="Green"/>
67                                            </GradientStopCollection>
68                                        </LinearGradientBrush.GradientStops>
69                                    </LinearGradientBrush>
70                                </Setter.Value>
71                            </Setter>
72                        </DataTrigger>
73                </Style.Triggers>
74            </Style>
75                </igDP:XamDataGrid.Resources>
76            <igDP:XamDataGrid.FieldSettings>
77                <igDP:FieldSettings AllowRecordFiltering="true" FilterEvaluationTrigger="OnCellValueChange"  AllowSummaries="True" FilterOperatorDropDownItems="All" />
78            </igDP:XamDataGrid.FieldSettings>
79            <igDP:XamDataGrid.FieldLayoutSettings>
80                <igDP:FieldLayoutSettings AutoGenerateFields="true" FilterUIType="LabelIcons" />
81            </igDP:XamDataGrid.FieldLayoutSettings>
82        </igDP:XamDataGrid>
83        </DockPanel>
84</igWindows:TabItemEx>

There is quite a lot of Wpf here, so I have highlighted the DockPanel to which the dependency has been applied. All we now need to do is provide a way to manipulate this value. We need to add a ScaleValue attached dependency property to our Behaviour that we can bind to our single or set of control controls.

 1Public Class GlobalScaleTransformBehaviour
 2
 3    Private Shared sm_AttachedControls As List(Of FrameworkElement)
 4    Public Shared ReadOnly IsScaledProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsScaled", GetType(Boolean), GetType(GlobalScaleTransformBehaviour), New UIPropertyMetadata(False, New PropertyChangedCallback(AddressOf GlobalScaleTransformBehaviour.IsScaledChanged)))
 5    Public Shared ReadOnly ScaleValueProperty As DependencyProperty = DependencyProperty.RegisterAttached("ScaleValue", GetType(Double), GetType(GlobalScaleTransformBehaviour), New UIPropertyMetadata(CType(1, Double), New PropertyChangedCallback(AddressOf GlobalScaleTransformBehaviour.ScaleValueChanged)))
 6    Private Shared sm_CurrentScale As Double = 1
 7
 8    Shared Sub New()
 9        sm_AttachedControls = New List(Of FrameworkElement)
10    End Sub
11
12    Public Shared Function GetIsScaled(ByVal element As DependencyObject) As Boolean
13        If element Is Nothing Then
14            Throw New ArgumentNullException("element")
15        End If
16
17        Return element.GetValue(IsScaledProperty)
18    End Function
19
20    Public Shared Sub SetIsScaled(ByVal element As DependencyObject, ByVal value As Boolean)
21        If element Is Nothing Then
22            Throw New ArgumentNullException("element")
23        End If
24        element.SetValue(IsScaledProperty, value)
25    End Sub
26
27    Private Shared Sub IsScaledChanged(ByVal obj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
28        Dim itemToResize As FrameworkElement = TryCast(obj, FrameworkElement)
29        If (Not itemToResize Is Nothing) Then
30            If Object.Equals(e.NewValue, True) Then
31                sm_AttachedControls.Add(itemToResize)
32                itemToResize.LayoutTransform = New ScaleTransform(sm_CurrentScale, sm_CurrentScale)
33            Else
34                sm_AttachedControls.Remove(itemToResize)
35                itemToResize.LayoutTransform = New ScaleTransform(1, 1)
36            End If
37        End If
38    End Sub
39
40    Public Shared Function GetScaleValue(ByVal element As DependencyObject) As Double
41        If element Is Nothing Then
42            Throw New ArgumentNullException("element")
43        End If
44
45        Return element.GetValue(ScaleValueProperty)
46    End Function
47
48    Public Shared Sub SetScaleValue(ByVal element As DependencyObject, ByVal value As Double)
49        If element Is Nothing Then
50            Throw New ArgumentNullException("element")
51        End If
52        element.SetValue(ScaleValueProperty, value)
53    End Sub
54
55    Private Shared Sub ScaleValueChanged(ByVal obj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
56        If Not Application.Current.Dispatcher.CheckAccess Then
57            Exit Sub
58        End If
59        sm_CurrentScale = e.NewValue
60        SyncLock sm_AttachedControls
61            For Each itemToResize In sm_AttachedControls.ToList
62                ' Apply Tensform
63                itemToResize.LayoutTransform = New ScaleTransform(sm_CurrentScale, sm_CurrentScale)
64            Next
65        End SyncLock
66    End Sub
67
68End Class

This value is stored so we can set new controls, and then applied to all of the currently attached controls. I have chosen to bind to a slider, but any way of passing in the required values is just fine.

 1<igRibbon:XamRibbonWindow x:Class="MainWindowView"
 2    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4    xmlns:igRibbon="http://infragistics.com/Ribbon"
 5    xmlns:igEditors="http://infragistics.com/Editors"
 6    xmlns:igWindows="http://infragistics.com/Windows"
 7    xmlns:igDock="http://infragistics.com/DockManager"
 8    xmlns:local="clr-namespace:Hinshlabs.WpfHeatItsmDashboard"
 9    Title="Heat Itsm Dashboard" MinHeight="600" MinWidth="800" Icon="/Hinshlabs.WpfHeatItsmDashboard;component/HeatItsm.ico">
10    <igRibbon:XamRibbonWindow.Resources>
11        <local:NinjectDataProvider
12        x:Key="ViewModel"
13        ObjectType="{x:Type local:MainWindowViewModel}"
14        />
15    </igRibbon:XamRibbonWindow.Resources>
16    <igRibbon:RibbonWindowContentHost DataContext="{StaticResource ViewModel}">
17        <igRibbon:RibbonWindowContentHost.Ribbon>
18            <igRibbon:XamRibbon local:XamRibbonBehaviour.IsEntryPoint="True" DockPanel.Dock="Top" AutoHideEnabled="True" Theme="Office2k7Blue" >
19            <igRibbon:XamRibbon.ApplicationMenu>
20                    <igRibbon:ApplicationMenu RecentItemsHeader="{Binding Resources.RecentItemsHeader}" Image="/Hinshlabs.WpfHeatItsmDashboard;component/Images/heat.gif">
21                        <igRibbon:ButtonTool Caption="Update" />
22                    <igRibbon:ApplicationMenu.FooterToolbar>
23                        <igRibbon:ApplicationMenuFooterToolbar>
24                            <igRibbon:ButtonTool Command="{Binding ExitCommand}" Caption="{Binding Resources.ExitButtonCaption}"/>
25                        </igRibbon:ApplicationMenuFooterToolbar>
26                    </igRibbon:ApplicationMenu.FooterToolbar>
27                        </igRibbon:ApplicationMenu>
28                </igRibbon:XamRibbon.ApplicationMenu>
29                <igRibbon:XamRibbon.Tabs>
30                    <igRibbon:RibbonTabItem Header="{Binding Resources.Ribbon_HomeTab_Header}">
31                        <igRibbon:RibbonGroup Caption="{Binding Resources.Ribbon_HomeTab_ViewsGroup_Caption}">
32                            <igRibbon:ToolHorizontalWrapPanel>
33                                <igRibbon:ButtonTool Caption="{Binding Resources.Ribbon_HomeTab_ViewsGroup_CallsViewButtonCaption}" Command="{Binding AddCallsViewCommand}" />
34                            </igRibbon:ToolHorizontalWrapPanel>
35                        </igRibbon:RibbonGroup>
36                        <igRibbon:RibbonGroup Caption="{Binding Resources.Ribbon_HomeTab_OptionsGroup_Caption}">
37                                    <igRibbon:ToolHorizontalWrapPanel>
38                                    <igRibbon:ButtonGroup>
39                                        <igRibbon:ToggleButtonTool IsChecked="{Binding FickEnabled, Mode=TwoWay}" Content="{Binding Resources.Ribbon_HomeTab_OptionsGroup_Flick_ToggleButton_Caption}"/>
40                                </igRibbon:ButtonGroup>
41                                    </igRibbon:ToolHorizontalWrapPanel>
42                            <igRibbon:ToolHorizontalWrapPanel>
43                                <igRibbon:ButtonGroup>
44                                    <Label Content="Scale" />
45                                    <Slider Minimum="0.5" Maximum="3" Width="200" local:GlobalScaleTransformBehaviour.ScaleValue="1" LargeChange=".5" SmallChange=".1"  Value="{Binding Path=(local:GlobalScaleTransformBehaviour.ScaleValue),RelativeSource={RelativeSource Self}, Mode=TwoWay}">
46                                    </Slider>
47                                </igRibbon:ButtonGroup>
48                            </igRibbon:ToolHorizontalWrapPanel>
49                        </igRibbon:RibbonGroup>
50                    </igRibbon:RibbonTabItem>
51                </igRibbon:XamRibbon.Tabs>
52            </igRibbon:XamRibbon>
53    </igRibbon:RibbonWindowContentHost.Ribbon>
54        <AdornerDecorator>
55        <DockPanel>
56            <local:UpdateView DockPanel.Dock="Top" />
57            <igWindows:XamTabControl TabItemCloseButtonVisibility="Visible" TabStripPlacement="Top" ItemsSource="{Binding CallsViews}" SelectedItem="{Binding SelectedCallsView}" local:TabControlTimedBehaviour.IsTimedCycle="{Binding FickEnabled}" Theme="Office2k7Blue">
58            </igWindows:XamTabControl>
59        </DockPanel>
60        </AdornerDecorator>
61    </igRibbon:RibbonWindowContentHost>
62</igRibbon:XamRibbonWindow>

Wpf Scale Transform Behaviour

As you can see I am heavily utilizing the Infragistics controls, but that would not affect this procedure. The result is the ability to smoothly scale your controls based on a global scale setting.

Wpf Scale Transform Behaviour

krsu46zvpt

Technorati Tags: .NET   WPF   CodeProject   MVVM

Smart Classifications

Each classification [Concepts, Categories, & Tags] was assigned using AI-powered semantic analysis and scored across relevance, depth, and alignment. Final decisions? Still human. Always traceable. Hover to see how it applies.

Subscribe

Connect with Martin Hinshelwood

If you've made it this far, it's worth connecting with our principal consultant and coach, Martin Hinshelwood, for a 30-minute 'ask me anything' call.

Our Happy Clients​

We partner with businesses across diverse industries, including finance, insurance, healthcare, pharmaceuticals, technology, engineering, transportation, hospitality, entertainment, legal, government, and military sectors.​

Higher Education Statistics Agency Logo

Higher Education Statistics Agency

Illumina Logo

Illumina

Healthgrades Logo

Healthgrades

New Signature Logo

New Signature

Epic Games Logo

Epic Games

Schlumberger Logo

Schlumberger

Slicedbread Logo

Slicedbread

Big Data for Humans Logo

Big Data for Humans

Capita Secure Information Solutions Ltd Logo

Capita Secure Information Solutions Ltd

Sage Logo

Sage

MacDonald Humfrey (Automation) Ltd. Logo

MacDonald Humfrey (Automation) Ltd.

Boeing Logo

Boeing

Slaughter and May Logo

Slaughter and May

Boxit Document Solutions Logo

Boxit Document Solutions

Cognizant Microsoft Business Group (MBG) Logo

Cognizant Microsoft Business Group (MBG)

Akaditi Logo

Akaditi

Trayport Logo

Trayport

Bistech Logo

Bistech

Ghana Police Service Logo

Ghana Police Service

Washington Department of Enterprise Services Logo

Washington Department of Enterprise Services

Nottingham County Council Logo

Nottingham County Council

New Hampshire Supreme Court Logo

New Hampshire Supreme Court

Royal Air Force Logo

Royal Air Force

Washington Department of Transport Logo

Washington Department of Transport

Jack Links Logo

Jack Links

Deliotte Logo

Deliotte

Slaughter and May Logo

Slaughter and May

Lockheed Martin Logo

Lockheed Martin

ALS Life Sciences Logo

ALS Life Sciences

SuperControl Logo

SuperControl