午後わてんのブログ

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

WPFとVB.NETでDataContextやBinding、INotifyPropertyChangedの練習

DataContextやBindingのテスト
 
 
キーワードは
DataBinding:縛る、束ねる 紐付けるかな
DataContext:文脈、脈絡 これも繋がりみたいな感じね
INotifyPropertyChanged:通知 Notifyが通知でこれはそのまま
Implements:用具、実装 ググってると実装はたまによく聞く
 
イメージ 17
 
オレンジと緑の四角はBorderコントロール
このBorderの縦横の大きさや色をスライダーやボタンを使って変化させている
クリックした方のBorderだけ変化する
 
こっちを変えたらあっちも変わる、あっちを変えたらこっちも変わるとかの
コントロール同士の連携みたいなのをBindingを使うと自動で処理してくれる

デザイン画面とXAML
 

f:id:gogowaten:20191025131548p:plain

 
VBコード
 

f:id:gogowaten:20191025131600p:plain

Imports System.ComponentModel

Class MainWindow
    'Private DataList As New List(Of ExData) 'これをObservableに変更すれば良さそう↓
    Private DataList As New System.Collections.ObjectModel.ObservableCollection(Of ExData)
    Private ImaBoder As Border '選択中のBorderを入れておく用
    Private BoderList As New ObjectModel.ObservableCollection(Of Border) '全部のBorderを入れておく用
    'DataContextの設定
    Private Sub DataReset()
        DataList.Clear()
        'DataList.Add(New ExData With {.Height = 50, .Width = 50, .BackgroundColor = New SolidColorBrush(Colors.Orange)})
        'DataList.Add(New ExData With {.Height = 10, .Width = 70, .BackgroundColor = New SolidColorBrush(Colors.Green)})
        Dim data As New ExData
        With data
            .Height = 50
            .Width = 50
            .BackgroundColor = New SolidColorBrush(Colors.Orange)
        End With
        DataList.Add(data)

        data = New ExData
        With data
            .Height = 10 : .Width = 70
            .BackgroundColor = New SolidColorBrush(Colors.Green)
        End With
        DataList.Add(data)

        For i As Integer = 0 To BoderList.Count - 1
            BoderList(i).DataContext = DataList(i)
        Next

        'grid1はGridでスライダーなどが配置されている
        'grid1.DataContext = DataList(CuBoder.Tag)
        grid1.DataContext = ImaBoder.DataContext '↑と同じ結果だけどこっちのほうがいいかな
    End Sub
    Private Sub buttonReset_Click(sender As Object, e As RoutedEventArgs) Handles btxReset.Click
        Call DataReset()
    End Sub
    Private Sub MainWindow_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
        'Border作成してStackPanelに追加
        For i As Integer = 0 To 1 ' 2つ作る
            Dim b As New Border

            With b
                'Binding設定
                .SetBinding(WidthProperty, New Binding("Width"))
                .SetBinding(HeightProperty, New Binding("Height"))
                .SetBinding(BackgroundProperty, New Binding("BackgroundColor"))

                .Tag = i
            End With

            sp1.Children.Add(b) 'sp1はStackPanel、そこに作ったBorderを配置
            AddHandler b.MouseDown, AddressOf Border_Click '左クリックした時の動作
            ImaBoder = b '選択中のBorder
            BoderList.Add(b) 'Borderのリストに追加
        Next

        Call DataReset()

        'sld1.SetBinding(Slider.ValueProperty, New Binding("Width"))
        '↑↓のBindingは同じ、vbコードで指定するかXAMLで指定するかの違い
        '<Slider x:Name="sld1" Maximum="100" Value="{Binding Width}"/>

    End Sub
    'Borderを左クリックした時の動作
    Private Sub Border_Click(sender As Object, e As RoutedEventArgs)
        Dim b As Border = DirectCast(sender, Border)
        ImaBoder = b
        grid1.DataContext = ImaBoder.DataContext 'スライダーのDataContextを変更
        tbxCB.Text = ImaBoder.Tag.ToString
    End Sub

    Private Sub button1_Click(sender As Object, e As RoutedEventArgs) Handles button1.Click
        Dim d As ExData = ImaBoder.DataContext
        d.BackgroundColor = New SolidColorBrush(Colors.Cyan)
        d.Width = 70
    End Sub
    Private Sub button2_Click(sender As Object, e As RoutedEventArgs) Handles button2.Click
        Dim d As ExData = DataList(ImaBoder.Tag)
        d.BackgroundColor = New SolidColorBrush(Colors.Pink)
        d.Width = 40
    End Sub
    'これは失敗、実行するとBindingが無効になってしまう
    Private Sub button3_Click(sender As Object, e As RoutedEventArgs) Handles button3.Click
        'ImaBoder.Background = New SolidColorBrush(Colors.Blue)
        'ImaBoder.Width = 20
    End Sub

End Class

'プロパティ -Programming / .NET Framework - 総武ソフトウェア推進所
'http://smdn.jp/programming/netfx/property/#INotifyPropertyChanged

Public Class ExData
    Implements ComponentModel.INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    Private Sub OnPropertyChanged(name As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))

    End Sub

    Private Property _Width As Double
    Public Property Width As Double
        Get
            Return _Width
        End Get
        Set(value As Double)
            _Width = value
            Call OnPropertyChanged("Width")
        End Set
    End Property

    Private Property _Height As Double
    Public Property Height As Double
        Get
            Return _Height
        End Get
        Set(value As Double)
            _Height = value
            Call OnPropertyChanged("Height")
        End Set
    End Property

    Private Property _BackgroundColor As SolidColorBrush
    Public Property BackgroundColor As SolidColorBrush
        Get
            Return _BackgroundColor
        End Get
        Set(value As SolidColorBrush)
            _BackgroundColor = value
            OnPropertyChanged("BackgroundColor")
        End Set
    End Property

End Class

イメージ 20
簡単なBindingの例だと上のアプリではスライダーとその値を表示しているTextBlockがBindingで連携している
このBindingはXAMLのほうで指定している
 
TextblockのTextプロパティにsld1と名前をつけたSliderの値ValueをBinding
 
ElementNameに連携するコントロールを指定、ここではsld1
Pathに連携するプロパティを指定、ここではValue
StringFormatは表示形式の指定で0.0だと小数点一桁まで表示になる
 
これだけでスライダーを動かすと自動でその値をTextBlockに表示されるようになる
TextBlockのTextとSliderのValueが同期する感じ
 
SliderのValueに{Binding Width}ってBinding指定をしているのはTextBlockとは関係なくてDataContextのWidthプロパティとのBindingになる
今回の場合だと
grid1(Gridコントロール)のDataContextにいろいろなプロパティを入れたものを指定しておいて、その中のWidthプロパティとSliderのValueプロパティをBindingしている
 
いろいろなプロパティを入れておくクラスを用意
ExDataって名前をつけたクラスを作ったのがこの部分で、今回はまだだけど最終的にはシリアライズしてファイルに保存する予定

f:id:gogowaten:20191025131627p:plain

Width、HeightがDouble型、BackgroundColorはSolidColorBrush型?
基本的には3つのプロパティをもたせただけの簡単なクラスなんだけど
例えばこのクラスのWidthとスライダーのValueをBindingさせた状態で、どちらか片方の値が変化した時にもう一方の値も変化させるっていう双方向のBindingにしたい場合は、このクラスのWidthを変化させた時に相手側に通知する必要がある
そのためには通知する機能を持たせる必要がある、それが
INotifyPropertyChangedっていうインターフェイスってものらしい
これは
プロパティ - Programming/.NET Framework - 総武ソフトウェア推進所
http://smdn.jp/programming/netfx/property/#INotifyPropertyChanged
ここを参照した、丁寧に説明されているのでここ見たほうが早い
 

f:id:gogowaten:20191025131643p:plain

Implements ComponentModel.INotifyPropertyChanged
って打ってエンターキー押すと
 

f:id:gogowaten:20191025131705p:plain

Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
ってのが自動で入力される
どうやら必要なEventらしい、これが実行されると変更が通知されるってことなのかな
続いて、このEventを実行させるメソッド?が必要みたいで

f:id:gogowaten:20191025131722p:plain

    Private Sub OnPropertyChanged(name As String)
        RaiseEvent 
    End Sub
RaiseEventってのがEventを実行させる宣言みたいなものかな、ここまで入力するとEventの一覧表が出てくる、一覧表と言ってもさっき自動で追加されたPropertyChanged一個しかないけどね、面白い
 

f:id:gogowaten:20191025131734p:plain

PropertyChangedイベントを選んで入力すると引数が要るみたいで
senderはこのクラス自身のMeで、もう一つはPropertyChangedEventArgs
 

f:id:gogowaten:20191025131745p:plain

引数はこのメソッドで受け取ったNameをそのまま渡す
これで通知機能を持たせることができたになるのかな
あとは通知するタイミング
変更(Set)した直後に通知したいから
例えばWidthってプロパティの場合は
イメージ 7
プロパティのSetのところにさっきのEventを実行させるメソッドのOnPropertyChangedを書く、引数に持たせるのはプロパティの名前
 
イメージ 8
残り二つのプロパティにもSetのところにOnPropertyChangedを書く
以上でプロパティの変更通知機能付きのExDataクラスの完成
 
 
 
 
Borderを全部を入れるリスト
ExDataを全部入れるリスト
選択中のBorderを入れる
 
 
アプリ起動時の初期設定

f:id:gogowaten:20191025131813p:plain

Borderを2つ作成してsp1(StackPanel)に追加
縦横の大きさや色の指定はしない
SetBindingを使ってBindingする各プロパティを指定
Tagには連番を入れているけど、必要じゃなかった
左クリックで選択中のBorderを切り替えるのでそのイベントに関連付け
作成したBorderをリストに入れる
最後の call DataResetは
 
イメージ 12
2つのExDataクラスを作成して各プロパティに値を指定してリストに追加しているのが、27行目まで
14行目から19行目までが1つ分のExDataクラスを作成して各プロパティに値を指定している
この6行分は
DataList.Add(New ExData With {.Height = 50, .Width = 50, .BackgroundColor = New SolidColorBrush(Colors.Orange)})
っていう1行でもできるみたいで、これは慣れたらこっちのほうがいいのかな?
 
29行目からの3行で各BorderのDataContextに各ExDataを指定している
これでBorderとExDataが連携できるBindingが完了
 
35行目はgrid1(Grid)のDataContextに今選択中のBorderのDataContext(中身はExData)を指定している、これでgrid1の中に配置されているコントロールはこのDataContextを参照できるみたいなので、わざわざ1つ1つのコントロールのDataContextに指定しなくても良くなっている
イメージ 13
赤色のところがgrid1の中になる、ほぼ全部
実際にこのDataContextとBindingしているのは
19行目と24行目のSliderでそれぞれ自身のValueをDataContextのなかのWidthとHeightにBindingしている
 
イメージ 14
Bindingの関係はこうなって、どれかの値が変更されたら他のも全部変更されて同じ値になるっていう動作、になるはずだったんだけど失敗していてBorderのWidthを変更してもExDataのWidthが変化しない、これを直したのは2日後の記事
 
 
Borderをクリックした時
イメージ 15
選択中のBorder用の変数ImaBorderにクリックされたBorderを入れる
grid1のDataContextを選択中のBorderのDataContextにする
これでクリックされたBorderの横幅、高さの値がSliderに反映されるしSliderを動かすとBorderのサイズが変更されるようになる
 
各ボタンをおした時の動作
イメージ 16
Button1は選択中のBorderのDataContextのExDataを読み取ってその中の色と横幅を変更している
 
Button2はExDataのリスト中のExDataの中の色と横幅を変更している
 
Button3は選択中のBorderの色と横幅を変更している、けど今回では失敗しているのでコメントアウトしている、これを実行するとBorderは変化するけどSliderとExDataとのBindingが外れてしまったような状態になって、Sliderを動かしてもサイズ変更しなくなる
 
 
イメージ 11
WindowsFormアプリ作成でのコントロール同士の連携はめんどくさかった
Aを変えたらBも変える、Bを変えたらAも変えるって普通に書くと無限ループのはずなんだけど空気を読んでくれているのかループしないようになっていた気がする
WPFでは同じように書くと無限ループするのでフラグを用意して処理していた
この前まで作っていたカラーピッカーがそれ
BindingってのはWPFの特徴みたいでBindingを使うとWPFっぽくなる感じ
でも慣れていないせいかBindingもめんどくさい感じがするし難しいw
 
参照したところ
 
 
 
 
コード全部
 
 
続きは2日後の

gogowaten.hatenablog.com