Search code examples
wpfrotationtranslationscalingmulti-touch

WPF Manipulation in Windows 8.1


Can anyone provide a few links to examples of how to do Manipulation in Windows 8.1, please?

I see many examples online of how to do it for Windows 7, but it doesn't work for me on my Windows 8.1 machine (with touchscreen). I'm using VS Studio Community v15. Here's the example that I've been trying:

https://www.tutorialspoint.com/wpf/wpf_multi_touch.htm

Using either the mouse or fingers doesn't make anything happen at all - no movement, no scaling, no rotation, etc.

P.S., I'm doing my project in VB, not C#.

For example,

<Window x:Class = "WpfManipulation1.MainWindow" 
   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
   xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
   xmlns:local = "clr-namespace:WpfManipulation1" 
   mc:Ignorable = "d" Title = "MainWindow" Height = "350" Width = "604">

    <Window.Resources>
        <MatrixTransform x:Key = "InitialMatrixTransform">
            <MatrixTransform.Matrix>
                <Matrix OffsetX = "200" OffsetY = "200"/>
            </MatrixTransform.Matrix>
        </MatrixTransform>
    </Window.Resources>

    <Canvas>
        <Rectangle Name = "manRect" Width = "321" Height = "241"  
         RenderTransform = "{StaticResource InitialMatrixTransform}" 
         IsManipulationEnabled = "true" Canvas.Left = "-70" Canvas.Top = "-170">
            <Rectangle.Fill>
                <ImageBrush ImageSource = "ManipImage.JPG"/>
            </Rectangle.Fill>
        </Rectangle>
        <Image Source="ManipImage.jpg" ManipulationMode="All" ManipulationDelta="UIElement_OnManipulationDelta" />
    </Canvas>

</Window>

Partial success with... XAML:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfManipulation2"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Image Source="ManipImage.jpg" IsManipulationEnabled="True" ManipulationDelta="OnManipulationDelta" />
    </Grid>
</Window>

VB Code-Behind:

Imports System.Windows.UIElement

Class MainWindow

    Private Sub OnManipulationDelta(ByVal sender As Object, ByVal e As ManipulationDeltaEventArgs)
        Dim transformGroup = GetTransformGroup(sender)
        Dim translateTransform = transformGroup.Children.OfType(Of TranslateTransform).First
        Dim rotateTransform = transformGroup.Children.OfType(Of RotateTransform).First
        Dim scaleTransform = transformGroup.Children.OfType(Of ScaleTransform).First
        translateTransform.X = (translateTransform.X + e.DeltaManipulation.Translation.X)
        translateTransform.Y = (translateTransform.Y + e.DeltaManipulation.Translation.Y)
        rotateTransform.Angle = (rotateTransform.Angle + e.DeltaManipulation.Rotation)
        scaleTransform.ScaleY = (scaleTransform.ScaleY * e.DeltaManipulation.Scale.Y)
        scaleTransform.ScaleX = (scaleTransform.ScaleX * e.DeltaManipulation.Scale.X)
    End Sub

    Private Function GetTransformGroup(ByVal sender As Object) As TransformGroup
        Dim element = CType(sender, UIElement)
        element.RenderTransformOrigin = New Point(0.5, 0.5)
        Dim transformGroup = CType(element.RenderTransform, TransformGroup)
        If (transformGroup Is Nothing) Then
            transformGroup = New TransformGroup
            transformGroup.Children.Add(New TranslateTransform)
            transformGroup.Children.Add(New ScaleTransform)
            transformGroup.Children.Add(New RotateTransform)
            element.RenderTransform = transformGroup
        End If

        Return transformGroup
    End Function


End Class

Error on line "Dim transformGroup = CType(element.RenderTransform, TransformGroup)": System.InvalidCastException: 'Unable to cast object of type 'System.Windows.Media.MatrixTransform' to type 'System.Windows.Media.TransformGroup'.'

Here's the result of implementing the advice provided.

XAML:

<Window x:Class="WpfTutorialSamples.Common_interface_controls.ToolbarSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="ToolbarSample" 
        Top="50" Left="50" Height="500" Width="700">
    <Window.CommandBindings>
        <CommandBinding Command="New" CanExecute="CommonCommandBinding_CanExecute" />
        <CommandBinding Command="Open" CanExecute="CommonCommandBinding_CanExecute" />
        <CommandBinding Command="Save" CanExecute="CommonCommandBinding_CanExecute" />
    </Window.CommandBindings>
    <Window.Resources>
        <MatrixTransform x:Key = "InitialMatrixTransform">
            <MatrixTransform.Matrix>
                <Matrix OffsetX = "0" OffsetY = "0"/>
            </MatrixTransform.Matrix>
        </MatrixTransform>
    </Window.Resources>
    <Grid x:Name="grid_MainWindow" Margin="0,0,0,0">
        <DockPanel>
            <ToolBarTray x:Name="myToolBarTray" DockPanel.Dock="Top" Panel.ZIndex="1">
                <!--
                <ToolBar>
                    <Button Command="New" Content="New" />
                    <Button Command="Open" Content="Open" />
                    <Button Command="Save" Content="Save" />
                </ToolBar>
                <ToolBar>
                    <Button Command="Cut" Content="Cut" />
                    <Button Command="Copy" Content="Copy" />
                    <Button Command="Paste" Content="Paste" />
                </ToolBar>
                -->
                <ToolBar x:Name="myToolBar" Height="30">
                    <Button Name="button_HideWindows" Content="Un/Hide" />
                    <Button Name="button_ClearWindows" Content="Clear" />
                    <Button Name="button_ResetMatrix" Content="Reset" />
                </ToolBar>
            </ToolBarTray>
            <!-- <TextBox AcceptsReturn="True" /> -->
            <!-- <Canvas x:Name="canvas_Main"/> -->
            <Canvas x:Name="canvas_Main" Background="LightPink" >
                <!--
                <Rectangle Name = "manRect" Width = "300" Height = "300"
                           HorizontalAlignment="Left"
                           VerticalAlignment="Top"
                           Margin="0,0,0,0"                           
                IsManipulationEnabled="True" ManipulationDelta="Window_ManipulationDelta"
                RenderTransform = "{StaticResource InitialMatrixTransform}" 
                >
                    <Rectangle.Fill>
                        <SolidColorBrush Color="LightBlue" />
                    </Rectangle.Fill>
                </Rectangle>
                -->
                <Canvas Name = "manRect" Width = "300" Height = "300"
                           HorizontalAlignment="Left"
                           VerticalAlignment="Top"
                           Margin="0,0,0,0"                           
                IsManipulationEnabled="True" ManipulationDelta="Window_ManipulationDelta"
                RenderTransform = "{StaticResource InitialMatrixTransform}" 
                        Background="LightBlue"
                >
                </Canvas>
                <Label x:Name="label_ZoomIn" Content="ZIn" Canvas.Left="330" Canvas.Top="370" Background="LightSkyBlue"/>
                <Label x:Name="label_ZoomOut" Content="ZOut" Canvas.Left="330" Canvas.Top="400" Background="LightSkyBlue"/>
            </Canvas>
        </DockPanel>

        <StackPanel x:Name="stackpanel_NW" Background="LightYellow" HorizontalAlignment="Left" Margin="0,30,0,0" VerticalAlignment="Top"  Height="100" Width="300">
            <Grid x:Name="grid_NW">
                <TextBox x:Name="textbox_NW" Background="LightYellow" BorderThickness="0" AcceptsReturn="True" Height="90" Width="290" Margin="5,5,5,5" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />
            </Grid>
        </StackPanel>
        <StackPanel x:Name="stackpanel_NE" Background="LightYellow" HorizontalAlignment="Right" Margin="0,30,0,0" VerticalAlignment="Top"  Height="300" Width="300">
            <Grid x:Name="grid_NE">
                <TextBox x:Name="textbox_NE" Background="LightYellow" BorderThickness="0" AcceptsReturn="True" Height="290" Width="290" Margin="5,5,5,5" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />
            </Grid>
        </StackPanel>
        <StackPanel x:Name="stackpanel_SW" Background="LightYellow" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Bottom"  Height="100" Width="300">
            <Grid x:Name="grid_SW">
                <TextBox x:Name="textbox_SW" Background="LightYellow" BorderThickness="0" AcceptsReturn="True" Height="90" Width="290" Margin="5,5,5,5" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />
            </Grid>
        </StackPanel>
        <StackPanel x:Name="stackpanel_SE" Background="LightYellow" HorizontalAlignment="Right" Margin="0,0,0,0" VerticalAlignment="Bottom"  Height="100" Width="300">
            <Grid x:Name="grid_SE">
                <TextBox x:Name="textbox_SE" Background="LightYellow" BorderThickness="0" AcceptsReturn="True" Height="90" Width="290" Margin="5,5,5,5" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />
            </Grid>
        </StackPanel>

    </Grid>
</Window>

Code-Behind (VB):

'Reference: http://www.wpf-tutorial.com/common-interface-controls/toolbar-control/

Imports System
Imports System.Windows
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Shapes

Namespace WpfTutorialSamples.Common_interface_controls

    Public Class ToolbarSample
        Inherits Window

        Dim CausedByCode As Boolean
        Dim ZoomFactor As Double = 0.1
        Dim ZoomMax As Double = 4.95
        Dim ZoomMin As Double = 0.05
        Dim NumberOfLevels As Integer = 6
        Dim NumberOfCellsPerLevel As Integer = 2
        Dim LvlBuffer As Double = 0.02
        Dim CellBuffer As Double = 0.05
        Dim LvlContainerColor As Brush = Brushes.LightGreen
        Dim CellContainerColor As Brush = Brushes.LightYellow
        Dim LvlDividerColor As Brush = Brushes.Red

        Public Sub New()
            MyBase.New
            InitializeComponent()
        End Sub

        Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
            'HideWindows(False, New StackPanel() {stackpanel_NW, stackpanel_SE, stackpanel_SW})
            HideWindows(True, New StackPanel() {}) 'all
        End Sub

        Private Sub MainWindow_Loaded(sender As Object, e As EventArgs) Handles Me.Loaded
            'add dividers
            For a = 1 To (NumberOfLevels - 1)
                Dim top As Double = a * (manRect.Height / NumberOfLevels)
                Dim LvlDivider As New Canvas
                LvlDivider.Background = LvlDividerColor
                LvlDivider.Width = manRect.Width
                LvlDivider.Height = 0.25
                manRect.Children.Add(LvlDivider)
                Canvas.SetLeft(LvlDivider, 0)
                Canvas.SetTop(LvlDivider, top)
            Next
            'add levels
            For a = 0 To (NumberOfLevels - 1)
                Dim LvlWidth As Double = manRect.Width
                Dim LvlHeight As Double = manRect.Height / NumberOfLevels
                Dim LvlTop As Double = (a * LvlHeight) + (LvlBuffer * LvlHeight)
                Dim LvlBottom As Double = LvlHeight - (2 * LvlBuffer * LvlHeight)
                Dim LvlContainer As New Canvas
                LvlContainer.Background = LvlContainerColor
                LvlContainer.Width = LvlWidth
                LvlContainer.Height = LvlBottom
                manRect.Children.Add(LvlContainer)
                Canvas.SetLeft(LvlContainer, 0)
                Canvas.SetTop(LvlContainer, LvlTop)
                'add cells
                For b = 0 To (NumberOfCellsPerLevel - 1)
                    Dim CellWidth As Double = LvlWidth / NumberOfCellsPerLevel
                    Dim CellWidthBuffered As Double = CellWidth - (2 * CellBuffer * CellWidth)
                    Dim CellHeight As Double = LvlBottom
                    Dim CellTop As Double = (CellBuffer * CellHeight)
                    Dim CellBottom As Double = CellHeight - (2 * CellBuffer * CellHeight)
                    Dim CellLeft As Double = (b * CellWidth) + (CellBuffer * CellWidth)
                    Dim CellContainer As New Canvas
                    CellContainer.Background = CellContainerColor
                    CellContainer.Width = CellWidthBuffered
                    CellContainer.Height = CellBottom
                    LvlContainer.Children.Add(CellContainer)
                    Canvas.SetLeft(CellContainer, CellLeft)
                    Canvas.SetTop(CellContainer, CellTop)
                Next
            Next
            '
            ResetMatrix_Click(button_ResetMatrix, Nothing)
        End Sub

        Private Sub CommonCommandBinding_CanExecute(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
            e.CanExecute = True
        End Sub

        Private Sub HideWindows_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles button_HideWindows.Click
            HideWindows(False, New StackPanel() {stackpanel_NE, stackpanel_NW, stackpanel_SE, stackpanel_SW})
        End Sub

        Private Sub ClearWindows_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles button_ClearWindows.Click
            ClearWindows(New StackPanel() {stackpanel_NE, stackpanel_NW, stackpanel_SE, stackpanel_SW})
        End Sub

        Private Sub HideWindows(ByVal Hide As Boolean, StackPanels() As StackPanel)
            If StackPanels.Length = 0 Then
                HideWindows(False, New StackPanel() {stackpanel_NE, stackpanel_NW, stackpanel_SE, stackpanel_SW})
            Else
                For Each s In StackPanels
                    If StackPanels.Length = 4 Then
                        s.Visibility = IIf((s.Visibility = Visibility.Hidden), Visibility.Visible, Visibility.Hidden)
                    Else
                        s.Visibility = IIf(Hide, Visibility.Visible, Visibility.Hidden)
                    End If
                Next
            End If
        End Sub

        Private Sub ClearWindows(StackPanels() As StackPanel)
            For Each s In StackPanels
                Dim g As Grid = (From a In s.Children Where a.GetType = GetType(Grid)
                                 Select TryCast(a, Grid)).FirstOrDefault
                Dim t As TextBox = (From a In g.Children Where a.GetType = GetType(TextBox)
                                    Select TryCast(a, TextBox)).FirstOrDefault
                If s.Visibility = Visibility.Visible Then t.Clear()
            Next
        End Sub

        Private Sub ResetMatrix_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles button_ResetMatrix.Click
            'CenterAboutPoint(New Point(150, 150), e) 'top-left
            CenterAboutPoint(New Point(Me.Width / 2, (Me.Height / 2) - myToolBar.Height), False, False, False, e) 'middle of window
        End Sub

        Private Sub label_ZoomIn_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles label_ZoomIn.MouseDown
            CenterAboutPoint(New Point(Me.Width / 2, (Me.Height / 2) - myToolBar.Height), True, True, False, e)
        End Sub

        Private Sub label_ZoomOut_MouseDown(ByVal sender As Object, ByVal e As MouseEventArgs) Handles label_ZoomOut.MouseDown
            CenterAboutPoint(New Point(Me.Width / 2, (Me.Height / 2) - myToolBar.Height), True, False, False, e)
        End Sub

        Private Sub CenterAboutPoint(ByVal p As Point, ByVal JustZoom As Boolean, ByVal ZoomDir As Boolean, ByVal JustTranslate As Boolean, ByVal e As RoutedEventArgs)

            'Dim rectToMove As Rectangle = CType(manRect, Rectangle)
            Dim rectToMove As Canvas = CType(manRect, Canvas)
            Dim rectsMatrix As Matrix = CType(rectToMove.RenderTransform, MatrixTransform).Matrix
            Dim ZoomLevel As Double = rectsMatrix.M11
            Dim transform = TransformToVisual(manRect)
            'textbox_NE.Text = "zoom: " & ZoomLevel & ", width: " & manRect.Width & ", height: " & manRect.Height

            Dim elemPosTopLeft = manRect.TranslatePoint(New Point(0, 0), canvas_Main)
            'textbox_NE.AppendText(vbCrLf & "elemPosTopLeft: " & elemPosTopLeft.ToString)

            Dim elemPosBottomRight = manRect.TranslatePoint(New Point(manRect.ActualWidth, manRect.ActualHeight), canvas_Main)
            'textbox_NE.AppendText(vbCrLf & "elemPosBottomRight: " & elemPosBottomRight.ToString)

            Dim elemPosWidth = (elemPosBottomRight.X - elemPosTopLeft.X) '* -ZoomLevel
            'textbox_NE.AppendText(vbCrLf & "elemPosWidth: " & elemPosWidth.ToString)

            Dim elemPosHeight = (elemPosBottomRight.Y - elemPosTopLeft.Y) '* -ZoomLevel
            'textbox_NE.AppendText(vbCrLf & "elemPosHeight: " & elemPosHeight.ToString)

            Dim elemPosLeft = elemPosTopLeft.X
            'textbox_NE.AppendText(vbCrLf & "elemPosLeft: " & elemPosLeft.ToString)

            Dim elemPosTop = elemPosTopLeft.Y
            'textbox_NE.AppendText(vbCrLf & "elemPosTop: " & elemPosTop.ToString)

            Dim elemPosBottom = elemPosBottomRight.Y
            'textbox_NE.AppendText(vbCrLf & "elemPosBottom: " & elemPosBottom.ToString)

            Dim elemPosRight = elemPosBottomRight.X
            'textbox_NE.AppendText(vbCrLf & "elemPosRight: " & elemPosRight.ToString)

            Dim elemPosCenterX = elemPosLeft + (elemPosWidth / 2)
            'textbox_NE.AppendText(vbCrLf & "elemPosCenterX: " & elemPosCenterX.ToString)

            Dim elemPosCenterY = elemPosTop + (elemPosHeight / 2)
            'textbox_NE.AppendText(vbCrLf & "elemPosCenterY: " & elemPosCenterY.ToString)

            'offsets from point
            Dim elemPosLeftOffset = p.X - elemPosCenterX
            'textbox_NE.AppendText(vbCrLf & "elemPosLeftOffset: " & elemPosLeftOffset.ToString)

            Dim elemPosTopOffset = p.Y - elemPosCenterY
            'textbox_NE.AppendText(vbCrLf & "elemPosTopOffset: " & elemPosTopOffset.ToString)

            'scale
            If JustTranslate = False Then
                If JustZoom = False Then
                    If ZoomLevel <> 1 Then
                        rectsMatrix.ScaleAt((1 / ZoomLevel), (1 / ZoomLevel), elemPosCenterX, elemPosCenterY)
                    End If
                Else
                    Dim NewZoomLevel = IIf(ZoomDir = True, (1 + ZoomFactor), (1 - ZoomFactor))
                    'textbox_NE.AppendText(vbCrLf & "NewZoomLevel: " & NewZoomLevel.ToString)
                    If ((ZoomLevel * NewZoomLevel) >= ZoomMin) And ((ZoomLevel * NewZoomLevel) <= ZoomMax) Then
                        rectsMatrix.ScaleAt(NewZoomLevel, NewZoomLevel, elemPosCenterX, elemPosCenterY)
                    End If
                End If
            End If

            'translate
            If JustZoom = False Then
                rectsMatrix.Translate(elemPosLeftOffset, elemPosTopOffset)
            End If
            'transform
            rectToMove.RenderTransform = New MatrixTransform(rectsMatrix)
            If Not IsNothing(e) Then e.Handled = True
        End Sub

        Private Sub Window_ManipulationStarting(ByVal sender As Object, ByVal e As ManipulationStartingEventArgs)
            e.ManipulationContainer = Me
            e.Handled = True
        End Sub

        Private Sub Window_ManipulationDelta(ByVal sender As Object, ByVal e As ManipulationDeltaEventArgs)
            'Reference: https://www.tutorialspoint.com/wpf/wpf_multi_touch.htm
            'Dim rectToMove As Rectangle = CType(e.OriginalSource, Rectangle)
            Dim rectToMove As Canvas = CType(e.OriginalSource, Canvas)
            Dim rectsMatrix As Matrix = CType(rectToMove.RenderTransform, MatrixTransform).Matrix
            Dim ZoomLevel As Double = rectsMatrix.M11
            'scale
            Dim scale As Double = (e.DeltaManipulation.Scale.X + e.DeltaManipulation.Scale.Y) / 2
            rectsMatrix.ScaleAtPrepend(scale, scale, e.ManipulationOrigin.X, e.ManipulationOrigin.Y)
            'translate
            If CausedByCode = False AndAlso (Math.Abs(e.DeltaManipulation.Translation.X) > 1 Or Math.Abs(e.DeltaManipulation.Translation.Y) > 1) Then
                rectsMatrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y)
                CausedByCode = True
            Else
                CausedByCode = False
            End If
            'apply transformation
            If (rectsMatrix.M11 >= ZoomMin) And (rectsMatrix.M11 <= ZoomMax) Then
                rectToMove.RenderTransform = New MatrixTransform(rectsMatrix)
            End If
            Dim containingRect As Rect = New Rect(CType(e.ManipulationContainer, FrameworkElement).RenderSize)
            Dim shapeBounds As Rect = rectToMove.RenderTransform.TransformBounds(New Rect(rectToMove.RenderSize))
            If (e.IsInertial AndAlso Not containingRect.Contains(shapeBounds)) Then
                e.Complete()
            End If

            e.Handled = True
        End Sub

        Private Sub Window_InertiaStarting(ByVal sender As Object, ByVal e As ManipulationInertiaStartingEventArgs)
            e.TranslationBehavior.DesiredDeceleration = (10 * (96 / (1000 * 1000))) 'inches per second squared
            e.ExpansionBehavior.DesiredDeceleration = (0.1 * (96 / (1000 * 1000))) 'inches per second squared
            e.RotationBehavior.DesiredDeceleration = (720 / (1000 * 1000)) 'degrees per second squared
            e.Handled = True
        End Sub

    End Class

End Namespace

The program works great, although there is an issue when making long drags of the matrix after it has been scaled down...it might be due to inertia.


Solution

  • The following allows basic manipulations on a specified image:

    XAML

    <Image Source="https://placehold.it/200x200" IsManipulationEnabled="True" ManipulationDelta="OnManipulationDelta" />
    

    Code-Behind (C#)

    private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
    {
        var transformGroup = GetTransformGroup(sender);
        var translateTransform = transformGroup.Children.OfType<TranslateTransform>().First();
        var rotateTransform = transformGroup.Children.OfType<RotateTransform>().First();
        var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().First();
    
        translateTransform.X += e.DeltaManipulation.Translation.X;
        translateTransform.Y += e.DeltaManipulation.Translation.Y;
        rotateTransform.Angle += e.DeltaManipulation.Rotation;
        scaleTransform.ScaleY *= e.DeltaManipulation.Scale.Y;
        scaleTransform.ScaleX *= e.DeltaManipulation.Scale.X;
    }
    
    private TransformGroup GetTransformGroup(object sender)
    {
        var element = sender as UIElement;
    
        element.RenderTransformOrigin = new Point(.5, .5);
    
        var transformGroup = element.RenderTransform as TransformGroup;
    
        if (transformGroup == null)
        {
            transformGroup = new TransformGroup();
            transformGroup.Children.Add(new TranslateTransform());
            transformGroup.Children.Add(new ScaleTransform());
            transformGroup.Children.Add(new RotateTransform());
    
            element.RenderTransform = transformGroup;
        }
    
        return transformGroup;
    }