午後わてんのブログ

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

WPF、変形した要素を指定位置に移動、NotifyProperty

 
要素を指定した位置に移動させる

yyyy_20170629_131223.gif
 水色の四角枠は目印、サイズ100x100、x,y=100,100
赤の四角が目的の要素、サイズ100x100
移動ボタン1と2どちらも100,100へ移動させるボタンだけど基準が違う
基準A:ボタン1は見た目上の位置
基準B:ボタン2は内部的な位置
 
今回のアプリのダウンロード先
 
変形後も内部の位置やサイズは変化しないから見た目とズレるから、このズレを計算しての位置指定
変形後の要素を他の要素にピッタリ重ねたいとか、横にくっつけたいとかしたいので基準Aの方法が必要だった
x,y=100,100のとき20度回転させると
イメージ 2
x,y=85.9,85.9にズレる
これを100,100にするにはズレの分だけずらせばいい
ズレは-14.1なので
100+(-(-14.1))=114.1
イメージ 3
100,100ぴったりになった
 
 
 
要素の中心を軸に回転させるとピッタリ収まる四角枠は位置もサイズも変化する
 
今回の赤の四角はThumbなんだけど
Canvasを入れたControlTemplateを作ってそれをThumbのTemplateに指定して、そのCanvasに赤のBorderを入れている
Thumb.Template
		┗Canvas
			┗Border	
 
変形させているのはTemplateのCanvas
 
 
 
用意したPriorityは全部NotifyPriority通知プロパティにした
MyLeft	X座標、左位置
MyTop	Y座標、上位置
MyAngle	回転角度
DiffPoint	見た目と中身の位置の差分
OutSize	見た目の大きさ、ぴったり枠のサイズ
MyOutBounds	ぴったり枠の位置とサイズ
 
 
 
 
 
TransformToVisualとTransformBoundsを使うと要素の変形後のRect(サイズや位置)を取得することができる
 
Dim gt As GeneralTransform = 要素.TransformToVisual(今回はThumb)
Dim r As Rect = gt.TransformBounds(New Rect(New Size(要素.Width, 要素.Height)))
イメージ 4
この場合だと位置はズレの値(Thumbとの相対的な位置)になっている(-14.05…)
これをDiffPointに入れておいて
サイズ(128.17…)はOutSizeに入れておく
 

 
変形(回転)させたらぴったり枠も更新するので
MyAngleのSetのところでそれを実行する
イメージ 5
RootRotate.Angle=Valueが実際に回転指定しているところ
 
 
DiffPoint 見た目と中身の位置の差分
OutSize 見た目の大きさ、ぴったり枠のサイズ
を更新して
 

f:id:gogowaten:20191031142638p:plain

MyOutBounds ぴったり枠の位置とサイズ
これも更新
 

f:id:gogowaten:20191031142647p:plain


 
移動させたときは
MyOutBoundsの位置の更新だけなので
MyLeftとMyTopのSetのところで
イメージ 8
Canvas.SetLeft(Me, Value)が実際に位置指定しているところ
そのあとのCall SetOutBoundsで

f:id:gogowaten:20191031142620p:plain

x,yだけ差分を足して、サイズはそのまま
 

 
 
ぴったり枠基準(基準A)で指定位置に移動
 
指定された値に差分を足した値を指定
なのでMainWindowからは単純に
MyExThumb.SetPoint2(100, 100)
だけで見た目上の100,100の位置に移動させることができる
 
イメージ 11

 
 
デザイン画面XAMLを書くと投稿エラーになるから画像で 

f:id:gogowaten:20191031142706p:plain

 
VBコード
MainWindowとThumbを継承したExThumb
 
Imports System.ComponentModel
Imports System.Windows.Controls.Primitives

Class MainWindow
    Private WithEvents MyExThumb As ExThumb

    Private Sub MyCheck()
        Dim root = MyExThumb.testRootCanvas
    End Sub
    Private Sub MyCheck2()
        MyExThumb.SetPoint2(100, 100)
    End Sub
    Private Sub MyMove()
        MyExThumb.MyLeft = 100
        MyExThumb.MyTop = 100
    End Sub
    Private Sub MyMove2()
        MyExThumb.SetPoint2(0, 0)
    End Sub
    Private Sub MyMove3()
        MyExThumb.MyLeft = 0
        MyExThumb.MyTop = 0
    End Sub
   '数値確認用のTextBlockへのBinding
    Private Sub SetTextBlockBinding(so As Object, sName As String, tb As TextBlock)
        Dim b As New Binding(sName) With {.Source = so, .StringFormat = sName & " = {0:0.0}"}
        tb.SetBinding(TextBlock.TextProperty, b)
    End Sub
    Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
        AddHandler btnCheck.Click, AddressOf MyCheck
       'AddHandler btn1.Click, AddressOf MyMove2
        AddHandler btn2.Click, AddressOf MyCheck2
       'AddHandler btn3.Click, AddressOf MyMove3
        AddHandler btn4.Click, AddressOf MyMove

       'ExThumbに100x100の赤Borderを追加してMyCanvasに表示
        Dim ext As New ExThumb(New Border With {
                               .Width = 100, .Height = 100, .Background = Brushes.Red, .Opacity = 0.5})
        Canvas.SetLeft(ext, 0) : Canvas.SetTop(ext, 0)
        MyCanvas.Children.Add(ext)
        MyExThumb = ext

       '回転角度をSliderにBinding
        Dim b As Binding
        b = New Binding(NameOf(ExThumb.MyAngle)) With {.Source = MyExThumb, .Mode = BindingMode.TwoWay}
        sldAngle.SetBinding(Slider.ValueProperty, b)

       '数値確認用のTextBlockへのBinding
        Call SetTextBlockBinding(MyExThumb, NameOf(ExThumb.MyAngle), tbAngle) '角度
        Call SetTextBlockBinding(MyExThumb, NameOf(ExThumb.DiffPoint), tbRect) '差分座標
        Call SetTextBlockBinding(MyExThumb, NameOf(ExThumb.MyLeft), tbLeft) '実際のX座標
        Call SetTextBlockBinding(MyExThumb, NameOf(ExThumb.MyOutBounds), tbBounds) '見た目のピッタリ枠

    End Sub
    Private Sub MyExThumb_DragDelta(sender As Object, e As DragDeltaEventArgs) Handles MyExThumb.DragDelta
        MyExThumb.MyLeft += e.HorizontalChange
        MyExThumb.MyTop += e.VerticalChange
    End Sub
End Class


Public Class ExThumb
    Inherits Thumb 'Thumbを継承
    Implements ComponentModel.INotifyPropertyChanged '通知プロパティ用
    Private RootCanvas As Canvas
    Private RootRotate As RotateTransform
    Public testRootCanvas As Canvas
   'OutBoundsの左上座標を指定
    Public Sub SetPoint2(x As Double, y As Double)
        MyLeft = x + (-DiffPoint.X)
        MyTop = y + (-DiffPoint.Y)
    End Sub
   'DiffPointとOutBoundsの更新、変形時に実行する
    Private Sub SetDiffPointAndOutSize()
        Dim gt As GeneralTransform = RootCanvas.TransformToVisual(Me)
        Dim r As Rect = gt.TransformBounds(New Rect(New Size(RootCanvas.Width, RootCanvas.Height)))
        DiffPoint = r.Location
        OutSize = r.Size
        Call SetOutBounds()
    End Sub
   'OutBoundsの更新、移動時に実行する
    Private Sub SetOutBounds()
        Dim r As Rect = New Rect(New Point(DiffPoint.X + MyLeft, DiffPoint.Y + MyTop), OutSize)
        MyOutBounds = r

    End Sub

#Region "Property"
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    Private Sub NotifyPropertyChanged(<System.Runtime.CompilerServices.CallerMemberName> Optional propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
   '変形前後の左上座標の差分
    Private Property _DiffPoint As Point
    Public Property DiffPoint As Point
        Get
            Return _DiffPoint
        End Get
        Set(value As Point)
            _DiffPoint = value
            Call NotifyPropertyChanged()
        End Set
    End Property
   '要素がピッタリ収まるサイズ
    Private Property _OutSize As Size
    Public Property OutSize As Size
        Get
            Return _OutSize
        End Get
        Set(value As Size)
            _OutSize = value
            Call NotifyPropertyChanged()
        End Set
    End Property
   '回転角度
    Private Property _MyAngle As Double
    Public Property MyAngle As Double
        Get
            Return _MyAngle
        End Get
        Set(value As Double)
            _MyAngle = value
            RootRotate.Angle = value
            Call NotifyPropertyChanged()
            Call SetDiffPointAndOutSize()
        End Set
    End Property
   'X座標
    Private Property _MyLeft As Double
    Public Property MyLeft As Double
        Get
            Return _MyLeft
        End Get
        Set(value As Double)
            _MyLeft = value
            Canvas.SetLeft(Me, value)
            Call NotifyPropertyChanged()
            Call SetOutBounds()
        End Set
    End Property
   'Y座標
    Private Property _MyTop As Double
    Public Property MyTop As Double
        Get
            Return _MyTop
        End Get
        Set(value As Double)
            _MyTop = value
            Canvas.SetTop(Me, value)
            Call NotifyPropertyChanged()
            Call SetOutBounds()
        End Set
    End Property
   '要素がピッタリ収まる四角枠
    Private Property _MyOutBounds As Rect
    Public Property MyOutBounds As Rect
        Get
            Return _MyOutBounds
        End Get
        Set(value As Rect)
            _MyOutBounds = value
            Call NotifyPropertyChanged()

        End Set
    End Property
#End Region

   'ControlTemplate作成、Canvasを一個入れるだけ
    Private Function CreateTemplate() As ControlTemplate
        Dim ct As New ControlTemplate(GetType(Thumb))
        Dim c As New FrameworkElementFactory With {.Name = "RootCanvas", .Type = GetType(Canvas)}
        ct.VisualTree = c
        Return ct
    End Function
   'コンストラクタ
   '渡された要素をTemplateの中のCanvasに追加する
    Public Sub New(elm As FrameworkElement)
        Template = CreateTemplate()
        ApplyTemplate() 'Templateを再構築、必要
       'TemplateのCanvasを取得して渡された要素を追加
        RootCanvas = Me.Template.FindName("RootCanvas", Me)
        With RootCanvas
            .Children.Add(elm)
            .Height = elm.Height
            .Width = elm.Width
        End With
        testRootCanvas = RootCanvas 'test

       '各種TransformをGroupにしてTemplateのCanvasのRenderTransformに指定
        RootRotate = New RotateTransform
        Dim sc As New ScaleTransform
        Dim sk As New SkewTransform
        Dim tg As New TransformGroup
        With tg.Children
            .Add(sc) : .Add(sk) : .Add(RootRotate)
        End With
        With RootCanvas
            .RenderTransformOrigin = New Point(0.5, 0.5)
            .RenderTransform = tg
            .Background = Brushes.Transparent
        End With

       'ピッタリ枠とかを更新するために角度指定
        MyAngle = 0.0

    End Sub
End Class
 
 
  
前回までのDependencyPropertyを最初に使って試したけどうまく書けなくて
今回はNotifyPropertyっていう値が変更されたら通知を出せるプロパティを使った
うまくできなかったのは変形(Angleを変更)したときにTransformToVisualを使って変形後のRectを求めるところ、こんなふうに書いてみたけど
 

f:id:gogowaten:20191031142929p:plain

 

f:id:gogowaten:20191031142947p:plain

DependencyPropertyのPropertyMetadataのPropertyChangedCallbackのところでTransformToVisualを使ってみたんだけどこのタイミングだと変形する直前みたいで変形前のRectしか取得できなかった
 
なので今回のようにNotifyPropertyって言う値の変更時に通知を出せる通知プロパティを使った、これは一年前の方法とほとんど変わらないけど少しうまく書けた気がする
 
 
参照したところ
INotifyPropertyChanged インターフェイス (System.ComponentModel)
https://msdn.microsoft.com/ja-jp/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
 
特に紫の文字のコトロがすごい便利だった

Private Sub NotifyPropertyChanged(<System.Runtime.CompilerServices.CallerMemberName> Optional propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub

これがないと例えば

'変形前後の左上座標の差分
Private Property _DiffPoint As Point
Public Property DiffPoint As Point
Get
Return _DiffPoint
End Get
Set(value As Point)
_DiffPoint = value
Call NotifyPropertyChanged()
End Set
End Property

 
赤文字のところを
Call NotifyPropertyChanged("DiffPoint")
って書くか
Call NotifyPropertyChanged(NameOf(DiffPoint))
って書くことになる
これがなんにも書かなくても良くなるから打ち間違えることもないしラク
 
 
 
 
 
 
関連記事
 
前回の記事 2017/6/24
WPF、CanvasLeftTopとSliderValueをBinding ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14988621.html
 
次の記事 2017/07/01は2日後
WPF、変形後の要素(Thumb)のグリッドスナップ移動 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15001512.html
 
 
 
昔の関連記事
2016/3/1は1年4ヶ月前
WPFVB.NETCanvasの中に回転表示したコントロールのドラッグ移動で気づいたこと ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/13947862.html
こんなふうに回転させるとその上で動くマウスの位置情報も回転されてめんどくさいので
ThumbのTemplateの中に入れたCanvasを回転させているのが今回の記事