午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

WPFとVB.NET、Bindingしたままコントロールを直接変形、TransformGroupの中のRotateTransform

 
WPFVB.NET、アプリでの編集状態保存、名前を付けて保存、回転角度を指定する2つの方法 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14093723.html
これをPixtack紫陽花2ndに組み込もうとして少し躓いたのでメモ
 
イメージ 3
期待通りに動いているところ
 
デザイン画面とXAML

f:id:gogowaten:20191025133419p:plain

 
VBコード

f:id:gogowaten:20191025133430p:plain

Imports System.ComponentModel

Class MainWindow
    Private FocusBorder As Border '選択中のBorderを入れておく用

    'Borderをクリックした時
    Private Sub Border_Click(b As Border, e As RoutedEventArgs)
        'StackPanelのDataContextを変更する
        sPanel1.DataContext = b.DataContext
        '選択中のBorderを変更する
        FocusBorder = b
    End Sub
    ''' <summary>
    ''' TransformGroupの中から指定したTransformを返す
    ''' </summary>
    ''' <param name="tGroup">RenderTransformとか指定</param>
    ''' <param name="tType">取得したいTransformの指定、RotateTransformとか</param>
    ''' <returns></returns>
    Private Function GetTransform(tGroup As TransformGroup, tType As Type) As Transform
        For Each c As Transform In tGroup.Children
            If tType = c.GetType Then
                Return c
                Exit For
            End If
        Next
        Return Nothing
    End Function
    'アプリの起動直後
    Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized

        'Borderを作成してStackPanelに追加表示
        For i As Integer = 0 To 1

            '回転角度、拡大率、傾斜角度を同時に指定するのでTransformGroupを使う
            Dim tg As New TransformGroup
            tg.Children.Add(New RotateTransform)
            tg.Children.Add(New ScaleTransform)
            tg.Children.Add(New SkewTransform)

            'Border作成
            Dim b As New Border
            With b
                .Background = New SolidColorBrush(Colors.Tomato)
                .Width = 50
                .Height = 50
                .Margin = New Thickness(20)
                .RenderTransformOrigin = New Point(0.5, 0.5)
                .RenderTransform = tg 'さっきのTransformGroupを指定
            End With

            'Bindingの設定
            'RenderTransformの中からそれぞれのTransformを取得する
            Dim ro As RotateTransform = GetTransform(tg, GetType(RotateTransform))
            Dim sc As ScaleTransform = GetTransform(tg, GetType(ScaleTransform))
            Dim sk As SkewTransform = GetTransform(tg, GetType(SkewTransform))

            '回転角度
            Dim bind As New Binding("Angle")
            bind.Mode = BindingMode.TwoWay
            BindingOperations.SetBinding(ro, RotateTransform.AngleProperty, bind)
            '拡大率
            bind = New Binding("ScaleX")
            BindingOperations.SetBinding(sc, ScaleTransform.ScaleXProperty, bind)
            '傾斜角度
            bind = New Binding("SkewX")
            BindingOperations.SetBinding(sk, SkewTransform.AngleXProperty, bind)

            'TransformGroupに入っている順番がわかりきっているならTransformの取得は
            'tg.Children.Item(0)とか決め打ちでもいいかも?
            'BindingOperations.SetBinding(tg.Children.Item(0), RotateTransform.AngleProperty, bind)
            'BindingOperations.SetBinding(tg.Children.Item(1), ScaleTransform.ScaleXProperty, bind)
            'BindingOperations.SetBinding(tg.Children.Item(2), SkewTransform.AngleXProperty, bind)

            sPanel1.Children.Add(b) 'BorderをStackPanelに追加表示

            'SaveDataを作成してStackPanelとBorderのDataContextに指定
            Dim sd As New SaveData With {.Angle = 0, .ScaleX = 1, .SkewX = 0}
            sPanel1.DataContext = sd
            b.DataContext = sd
            'Borderをクリックした時
            AddHandler b.MouseLeftButtonDown, AddressOf Border_Click
            '選択中のBorder
            FocusBorder = b

        Next
    End Sub
    Private Sub bt1_Click(sender As Object, e As RoutedEventArgs) Handles bt1.Click
        '選択中のBorderの回転角度を30にする

        'BorderのRenderTransformの中のRotateTransformのAngleを直接変更する場合
        'OK
        'Dim ro As RotateTransform = GetTransform(FocusBorder.RenderTransform, GetType(RotateTransform))
        'ro.Angle = 30

        'BorderのDataContextに入れてあるSaveDataのAngleを変更する場合
        'OK
        Dim sd As SaveData = FocusBorder.DataContext
        sd.Angle = 30


        '以下失敗例
        'BorderのRenderTransformにRotateTransformを指定する?場合
        '無視される
        'FocusBorder.RenderTransform.SetValue(RotateTransform.AngleProperty, 30.0R)

        'BorderのRenderTransformに新しいRotateTransformを上書き
        'バインディングが無効になってしまう
        'FocusBorder.RenderTransform = New RotateTransform(30)

    End Sub
End Class

<Serializable>
Public Class SaveData
    Implements ComponentModel.INotifyPropertyChanged

    <NonSerialized>
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    Private Sub OnPropertyChanged(name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
    End Sub
    Private Property _ScaleX As Double
    '拡大率横用
    Public Property ScaleX As Double
        Get
            Return _ScaleX
        End Get
        Set(value As Double)
            _ScaleX = value
            Call OnPropertyChanged("ScaleX")
        End Set
    End Property
    Private Property _SkewX As Double
    '傾斜横用
    Public Property SkewX As Double
        Get
            Return _SkewX
        End Get
        Set(value As Double)
            _SkewX = value
            Call OnPropertyChanged("SkewX")
        End Set
    End Property
    Private Property _Angle As Double
    '回転角度用
    Public Property Angle As Double
        Get
            Return _Angle
        End Get
        Set(value As Double)
            '0から360の間に収めてからSet
            value = value Mod 360
            If value < 0 Then
                value += 360
            End If

            _Angle = value
            Call OnPropertyChanged("Angle")
        End Set
    End Property
End Class
 
コントロールの変形で使いたいのが3つあって
RotateTransform回転角度、ScaleTransform拡大率、SkewTransform傾斜角度
どれか1つなら前回の方法でよかったみたいだけど、3つ同時だとできなかった
 
Imageコントロールの回転角度を30に変形する場合

前回の方法
Image.RenderTransform.SetValue(RotateTransform.AngleProperty, 30.0R)

 
改善した?今回の方法
Dim ro As RotateTransform = GetTransform(FocusBorder.RenderTransform, GetType(RotateTransform))
ro.Angle = 30
 
    Private Function GetTransform(tGroup As TransformGroup, tType As Type) As Transform
        Dim tg As TransformGroup = tGroup
        For Each c As Transform In tg.Children
            If tType = c.GetType Then
                Return c
                Exit For
            End If
        Next
        Return Nothing
    End Function

 
コントロールの変形の指定方法
 
回転角度だけを指定、30度の場合
Image.RenderTransform = New RotateTransform(30)
 
拡大率縦横だけを指定、縦横2倍の場合
Image.RenderTransform = New ScaleTransform(2, 2)
 
どれか1つならこんなふうに1行で済むけど、組み合わせるときはTransformGroupを使って
回転角度と拡大率を指定
Dim tg As New TransformGroup
tg.Children.Add(New RotateTransform(30))
tg.Children.Add(New ScaleTransform(2, 2))
Image.RenderTransform = tg
 
こうなる、ここまでは良かったけど
 
前回の方法で回転角度を45に変更
Image.RenderTransform.SetValue(RotateTransform.AngleProperty, 45.0R)
これでいいのかと思ったら、これだと無視されて変更されない
どうやらTransformGroupの中にあるRotateTransformまで届いていないみたい
そこでFor Each~NextでTransformGroupの中からRotateTransformを取得して、それに直接指定するようにしたのが今回の方法
まともな方法がありそうだけど見つからなかった
 
For Each~Nextを使わないで取得する
TransformGroupに入っている順番は入れた時の順番みたいなので、さっきの例だと
tg.Children.Item(0)にRotateTransformに入っていて
tg.Children.Item(1)にScaleTransformが入っている
決め打ちみたいだけどこっちのほうがラクかも
 
これでなんとかなった
BindingやDataContextは設定が難しいけどあとが楽になる感じ

Pixtack紫陽花2ndの様子

f:id:gogowaten:20191025133450p:plain

SliderのValueChangedイベントで変形させていたのを、SliderとImageをBindingするようにしただけだから、見た目も動作も変わっていないw
でもコードも減ったし動きも軽くなったかも?
次は名前を付けて保存機能
 
2016年5月1日追記、名前を付けて保存機能できました~
Pixtack紫陽花2nd、編集状態をファイルに保存できるようにした ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
http://blogs.yahoo.co.jp/gogowaten/14103815.html
 
2017/06/19は1年後の記事
WPF、AnglePropertyと作成した依存プロパティをバインディング - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14979329.html