VB のたまご

作成日: 2017/03/07, 更新日: 2017/03/07



依存関係プロパティ

  •  依存関係プロパティとは、Xaml 上で扱うためのプロパティです。 よく分からなくていいので、とりあえず作ってみましょう。

  • スポンサーリンク


  •  依存関係プロパティのサンプルとして、ボタンを継承した自作ボタンを作成します。

  • Public Class MyButton3
        Inherits Button
    
        ' 第一引数・・・依存関係プロパティ名
        ' 第二引数・・・依存関係プロパティの型
        ' 第三引数・・・依存関係プロパティを管理するクラスの型
        Public Shared ReadOnly MyNameProperty As DependencyProperty = DependencyProperty.Register(
            "MyName",
            GetType(String),
            GetType(MyButton3))
    
        ' 依存関係プロパティをラッピングした、CLR ラッパープロパティ
        Public Property MyName As String
            Get
                Return CStr(GetValue(MyNameProperty))
            End Get
            Set(value As String)
                SetValue(MyNameProperty, value)
            End Set
        End Property
    
        ' 比較用の CLR プロパティ
        Public Property MyName2 As String = String.Empty
    
    End Class
    

  •  自作ボタンを作成したら一回ビルドします。Visual Studio が認識してくれるようになります。 依存関係プロパティは、依存関係プロパティが登録・管理します。依存関係プロパティの取得・設定は、GetValue・SetValue メソッドを通して行います。 静的メンバーで読み取り専用な扱いが特徴的です。

  •  それでは、この自作ボタンを Xaml 上で使ってみましょう。 自作ボタンの「MyButton3」を使うためには、頭に local: を付ける必要があります。 今のところは、名前空間.MyButton とフルネームで名指しして使うのがお作法だと思ってください。

  • <Window x:Class="DependencyPropertyWindow1"
            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:w06_WpfSystem2"
            mc:Ignorable="d"
            Title="DependencyPropertyWindow1" Height="300" Width="300">
      
        <Grid>
    
            <local:MyButton3 Content="mybutton31"
                             Width="100" Height="30"
                             Click="MyButton3_Click" />
    
        </Grid>
        
    </Window>
    

    Public Class DependencyPropertyWindow1
    
        Private Sub MyButton3_Click(sender As Object, e As RoutedEventArgs)
    
            MessageBox.Show("Hello, WPF!")
    
        End Sub
    
    End Class
    

  •  実行して、ボタンを押したらメッセージが表示されたら OK です。

  •  それでは確認していきます。

  • <Window x:Class="DependencyPropertyWindow2"
            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:w06_WpfSystem2"
            mc:Ignorable="d"
            Title="DependencyPropertyWindow2" Height="300" Width="300">
    
        <Grid>
    
            <local:MyButton3 
                MyName="aaa"
                MyName2="bbb"
                Content="mybutton31"
                Width="100" Height="30"
                Click="MyButton3_Click" />
    
        </Grid>
    
    </Window>
    

    Public Class DependencyPropertyWindow2
    
        Private Sub MyButton3_Click(sender As Object, e As RoutedEventArgs)
    
            Dim btn = TryCast(sender, MyButton3)
            If btn Is Nothing Then
                Exit Sub
            End If
    
            Console.WriteLine($"MyName = {btn.MyName}")
            Console.WriteLine($"MyName2 = {btn.MyName2}")
            Console.WriteLine($"")
    
        End Sub
    
    End Class
    

  •  Xaml 上で、依存関係プロパティである MyName プロパティと、通常の CLR プロパティである MyName2 に初期値をセットしています。 実行して、ボタンをクリックしてみてください。

  • 出力結果
    MyName = aaa
    MyName2 = bbb
    

  •  セットされた値が出力されました。 なんだ、CLR プロパティのままでも Xaml 上でも使えたじゃん、と思われたかもしれませんが、それでは次のサンプルです。

  • <Window x:Class="DependencyPropertyWindow3"
            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:w06_WpfSystem2"
            mc:Ignorable="d"
            Title="DependencyPropertyWindow3" Height="300" Width="300">
       
        <Grid>
    
            <local:MyButton3
                Content="mybutton31"
                MyName="{Binding BindItem1}"
                MyName2="{Binding BindItem2}" />
    
        </Grid>
        
    </Window>
    

  •  プロパティにデータをバインドしようとしたサンプルです。 記述したタイミングで「型 'MyButton3' の 'MyName2' プロパティで 'Binding' を設定することはできません。'Binding' は、DependencyObject の DependencyProperty でのみ設定できます。」 という波線が表示されます。

  •  私の環境では、この状態で無理やりデバッグ実行しようとすると、以下のように例外エラーで実行することができませんでした。 やはり、同じエラー内容です。

  • 型 'System.Windows.Markup.XamlParseException' のハンドルされていない例外が PresentationFramework.dll で発生しました
    
    追加情報:型 'MyButton3' の 'MyName2' プロパティで 'Binding' を設定することはできません。'Binding' は、DependencyObject の DependencyProperty でのみ設定できます。
    

  •  このことから、通常の CLR プロパティでは、データをバインドして実装することができません。 プロパティにデータをバインドして使う、というのは、WPF では、特に MVVM を採用するのであれば使用は必須となりますので、依存関係プロパティは大事な仕組みになります。

  • スポンサーリンク


  •  さて、この依存関係プロパティですが、単純にデータの保持だけではなく、関連機能をくっつけることができます。 1つ1つ見ていきます。

  •  まずは、初期値の設定です。

  • Public Class MyButton4
        Inherits Button
    
        ' 第一引数・・・依存関係プロパティ名
        ' 第二引数・・・依存関係プロパティの型
        ' 第三引数・・・依存関係プロパティを管理するクラスの型
        ' 第四引数・・・初期値
        Public Shared ReadOnly MyNameProperty As DependencyProperty = DependencyProperty.Register(
            "MyName",
            GetType(String),
            GetType(MyButton4),
            New PropertyMetadata("default value"))
    
        ' 依存関係プロパティをラッピングした、CLR ラッパープロパティ
        Public Property MyName As String
            Get
                Return CStr(GetValue(MyNameProperty))
            End Get
            Set(value As String)
                SetValue(MyNameProperty, value)
            End Set
        End Property
    
    End Class
    

    <Window x:Class="DependencyPropertyWindow4"
            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:w06_WpfSystem2"
            mc:Ignorable="d"
            Title="DependencyPropertyWindow4" Height="300" Width="300">
     
        <Grid>
    
            <local:MyButton4
                Content="mybutton41"
                Click="MyButton4_Click" />
    
        </Grid>
        
    </Window>
    

    Public Class DependencyPropertyWindow4
    
        Private Sub MyButton4_Click(sender As Object, e As RoutedEventArgs)
    
            Dim btn = TryCast(sender, MyButton4)
            If btn Is Nothing Then
                Exit Sub
            End If
    
            Console.WriteLine($"MyName = {btn.MyName}")
            Console.WriteLine($"")
    
        End Sub
    
    End Class
    

    出力結果
    MyName = default value
    

  •  Xaml 上に MyButton4 を配置した際、MyName プロパティには何も値はセットしていません。 この状態でボタンをクリックして、MyName プロパティの値を取得すると初期値「default value」が表示されていることが分かります。

  •  次に、値が変更された時に呼ばれる機能(=値変更イベント的なもの)です。

  • Public Class MyButton5
        Inherits Button
    
        ' 第一引数・・・依存関係プロパティ名
        ' 第二引数・・・依存関係プロパティの型
        ' 第三引数・・・依存関係プロパティを管理するクラスの型
        ' 第四引数・・・初期値、値変更時のコールバックメソッド
        Public Shared ReadOnly MyNameProperty As DependencyProperty = DependencyProperty.Register(
            "MyName",
            GetType(String),
            GetType(MyButton5),
            New PropertyMetadata("default value", AddressOf MyPropertyChangedCallback))
    
        ' 依存関係プロパティをラッピングした、CLR ラッパープロパティ
        Public Property MyName As String
            Get
                Return CStr(GetValue(MyNameProperty))
            End Get
            Set(value As String)
                SetValue(MyNameProperty, value)
            End Set
        End Property
    
        ' 値が変更されたときに呼ばれるメソッド
        Private Shared Sub MyPropertyChangedCallback(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    
            Console.WriteLine($"  MyPropertyChangedCallback / {e.OldValue}")
            Console.WriteLine($"  MyPropertyChangedCallback / {e.NewValue}")
    
        End Sub
    
    End Class
    

    <Window x:Class="DependencyPropertyWindow5"
            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:w06_WpfSystem2"
            mc:Ignorable="d"
            Title="DependencyPropertyWindow5" Height="300" Width="300">
        
        <Grid>
    
            <local:MyButton5
                Content="mybutton5"
                Click="MyButton5_Click" />
    
        </Grid>
        
    </Window>
    

    Public Class DependencyPropertyWindow5
    
        Private Sub MyButton5_Click(sender As Object, e As RoutedEventArgs)
    
            Dim btn = TryCast(sender, MyButton5)
            If btn Is Nothing Then
                Exit Sub
            End If
    
            Console.WriteLine($"MyButton5_Click / {btn.MyName}")
            Console.WriteLine($"MyName を変更します")
            btn.MyName = "jiro"
            Console.WriteLine($"MyName を変更しました")
            Console.WriteLine($"MyButton5_Click / {btn.MyName}")
    
        End Sub
    
    End Class
    

    出力結果
    MyButton5_Click / default value
    MyName を変更します
      MyPropertyChangedCallback / default value
      MyPropertyChangedCallback / jiro
    MyName を変更しました
    MyButton5_Click / jiro
    

  •  実行したらボタンをクリックしてみてください。値が変更されたタイミングで、イベントを拾えていますね。

  •  続いて、値を変更しようとしたときのチェック機能です。

  • Public Class MyButton6
        Inherits Button
    
        ' 第一引数・・・依存関係プロパティ名
        ' 第二引数・・・依存関係プロパティの型
        ' 第三引数・・・依存関係プロパティを管理するクラスの型
        ' 第四引数・・・初期値、値変更時のコールバックメソッド、妥当性チェックコールバックメソッド
        Public Shared ReadOnly MyNameProperty As DependencyProperty = DependencyProperty.Register(
            "MyName",
            GetType(String),
            GetType(MyButton6),
            New PropertyMetadata("default value", AddressOf MyPropertyChangedCallback, AddressOf MyCoerceValueCallback))
    
        ' 依存関係プロパティをラッピングした、CLR ラッパープロパティ
        Public Property MyName As String
            Get
                Return CStr(GetValue(MyNameProperty))
            End Get
            Set(value As String)
                SetValue(MyNameProperty, value)
            End Set
        End Property
    
        ' 値が変更され【た】ときに呼ばれるメソッド
        Private Shared Sub MyPropertyChangedCallback(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    
            Console.WriteLine($"  MyPropertyChangedCallback / {e.OldValue}")
            Console.WriteLine($"  MyPropertyChangedCallback / {e.NewValue}")
    
            ' MyName プロパティの変更を受けて影響する、他の依存メンバーへの通知や値変更なども、ここで行える
    
        End Sub
    
        ' 値が変更され【ようとした】時に呼ばれるメソッド。制約条件などがある場合ここでチェック、範囲外なら入力値を修正する
        Private Shared Function MyCoerceValueCallback(d As DependencyObject, value As Object) As Object
    
            If value.ToString() = "jiro" Then
                value = "taro"
                Console.WriteLine("    jiro がセットされたので、taro に修正しました")
            End If
    
            Return value
    
        End Function
    
    End Class
    

    <Window x:Class="DependencyPropertyWindow6"
            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:w06_WpfSystem2"
            mc:Ignorable="d"
            Title="DependencyPropertyWindow6" Height="300" Width="300">
        
        <Grid>
    
            <local:MyButton6
                Content="mybutton61"
                Click="MyButton6_Click" />
    
        </Grid>
        
    </Window>
    

    Public Class DependencyPropertyWindow6
    
        Private Sub MyButton6_Click(sender As Object, e As RoutedEventArgs)
    
            Dim btn = TryCast(sender, MyButton6)
            If btn Is Nothing Then
                Exit Sub
            End If
    
            Console.WriteLine($"MyButton6_Click / {btn.MyName}")
            Console.WriteLine($"MyName を変更します")
            btn.MyName = "jiro"
            Console.WriteLine($"MyName を変更しました")
            Console.WriteLine($"MyButton6_Click / {btn.MyName}")
    
        End Sub
    
    End Class
    

    出力結果
    MyButton6_Click / default value
    MyName を変更します
        jiro がセットされたので、taro に修正しました
      MyPropertyChangedCallback / default value
      MyPropertyChangedCallback / taro
    MyName を変更しました
    MyButton6_Click / taro
    

  •  値をセットしようとしたときに、制限に引っかかり強制的に別の値に変更されたことが分かると思います。

  •  最後に、値が変更されようとした時に、適正値ではない場合は例外エラーではじく処理です。

  • Public Class MyButton7
        Inherits Button
    
        ' 第一引数・・・依存関係プロパティ名
        ' 第二引数・・・依存関係プロパティの型
        ' 第三引数・・・依存関係プロパティを管理するクラスの型
        ' 第四引数・・・初期値、値変更時のコールバックメソッド、妥当性チェックコールバックメソッド
        ' 第五引数・・・依存関係プロパティの値検証コールバックメソッド
        Public Shared ReadOnly MyNameProperty As DependencyProperty = DependencyProperty.Register(
            "MyName",
            GetType(String),
            GetType(MyButton7),
            New PropertyMetadata("default value", AddressOf MyPropertyChangedCallback, AddressOf MyCoerceValueCallback),
            AddressOf MyValidateValueCallback)
    
        ' 依存関係プロパティをラッピングした、CLR ラッパープロパティ
        Public Property MyName As String
            Get
                Return CStr(GetValue(MyNameProperty))
            End Get
            Set(value As String)
                SetValue(MyNameProperty, value)
            End Set
        End Property
    
        ' 値が変更され【た】ときに呼ばれるメソッド
        Private Shared Sub MyPropertyChangedCallback(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    
            Console.WriteLine($"  MyPropertyChangedCallback / {e.OldValue}")
            Console.WriteLine($"  MyPropertyChangedCallback / {e.NewValue}")
    
            ' MyName プロパティの変更を受けて影響する、他の依存メンバーへの通知や値変更なども、ここで行える
    
        End Sub
    
        ' 値が変更され【ようとした】時に呼ばれるメソッド。制約条件などがある場合ここでチェック、範囲外なら入力値を修正する
        Private Shared Function MyCoerceValueCallback(d As DependencyObject, value As Object) As Object
    
            If value.ToString() = "jiro" Then
                value = "taro"
                Console.WriteLine("    jiro がセットされたので、taro に修正しました")
            End If
    
            Return value
    
        End Function
    
        ' 値が変更され【ようとした】時に呼ばれるメソッド。制約条件などがある場合ここでチェック、適正なら True, それ以外なら False を返して例外エラーを発生させる
        Private Shared Function MyValidateValueCallback(value As Object) As Boolean
    
            If value.ToString() = "jiro" Then
                Return False
            End If
    
            Return True
    
        End Function
    
    End Class
    

    <Window x:Class="DependencyPropertyWindow7"
            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:w06_WpfSystem2"
            mc:Ignorable="d"
            Title="DependencyPropertyWindow7" Height="300" Width="300">
       
        <Grid>
    
            <local:MyButton7
                Content="mybutton7"
                Click="MyButton7_Click" />
    
        </Grid>
        
    </Window>
    

    Public Class DependencyPropertyWindow7
    
        Private Sub MyButton7_Click(sender As Object, e As RoutedEventArgs)
    
            Dim btn = TryCast(sender, MyButton7)
            If btn Is Nothing Then
                Exit Sub
            End If
    
            Console.WriteLine($"MyButton7_Click / {btn.MyName}")
            Console.WriteLine($"MyName を変更します")
    
            Try
                btn.MyName = "jiro"
                Console.WriteLine($"MyName を変更しました")
            Catch ex As Exception
                Console.WriteLine($"{ex.Message}")
            End Try
    
            Console.WriteLine($"MyButton7_Click / {btn.MyName}")
    
        End Sub
    
    End Class
    

    出力結果
    MyButton7_Click / default value
    MyName を変更します
    'w06_WpfSystem2.vshost.exe' (CLR v4.0.30319: w06_WpfSystem2.vshost.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\WindowsBase.resources\v4.0_4.0.0.0_ja_31bf3856ad364e35\WindowsBase.resources.dll' が読み込まれました。モジュールがシンボルなしでビルドされました。
    例外がスローされました: 'System.ArgumentException' (WindowsBase.dll の中)
    'jiro' は、プロパティ 'MyName' の有効な値ではありません。
    MyButton7_Click / default value
    

  •  例外エラーで引っかかった際は「初期値変更なし」という扱いになりました。 セットされた値をプログラム上で強制的に微調整するか、例外エラーで飛ばして変更前の値を維持するか、実際にはどちらか1つを使う事と思います。