tech·nic·al·ly agile

Wpf Scale Transform Behaviour

Discover how to implement WPF Scale Transform Behavior in MVVM applications, allowing dynamic control scaling with ease. Enhance your UI today!

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

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 

Software Development Technical Mastery Windows
Comments

Related blog posts

No related videos found.

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.​

Alignment Healthcare Logo

NIT A/S

Boeing Logo
Cognizant Microsoft Business Group (MBG) Logo
YearUp.org Logo
Milliman Logo
Flowmaster (a Mentor Graphics Company) Logo
Genus Breeding Ltd Logo
Brandes Investment Partners L.P. Logo
DFDS Logo
Slaughter and May Logo
Kongsberg Maritime Logo
Slicedbread Logo
Big Data for Humans Logo
Trayport Logo
Epic Games Logo
Healthgrades Logo
Teleplan Logo
Washington Department of Transport Logo
Ghana Police Service Logo
Department of Work and Pensions (UK) Logo
Washington Department of Enterprise Services Logo
Nottingham County Council Logo
Royal Air Force Logo
DFDS Logo
Illumina Logo
Cognizant Microsoft Business Group (MBG) Logo
Emerson Process Management Logo
Bistech Logo
Jack Links Logo