none
WPF CheckBox.IsCheckedを双方向バインディングする方法 RRS feed

  • 質問

  • 下の様なテストプログラムを作成したのですが、動作しません。
    不具合は3つあります。
    1.Window_Loadedイベントの中でlib_1.P1 = Trueとしてもコントロールに反映されない。
    2.CheckBox_1コントロールでチェックマークをON/OFFできるが、Debug.Print("### " & value)の行が実行されない。
    3.XLS0518 インスタンス メンバーである型 'lib_1' の 'P1' が必要です。というコンパイルエラーが間歇的に発生する。リビルドするとエラーが無くなるが、Visual Studioを閉じて再起動するとまた発生する。XAMLの <CheckBox Content=" -----" Height="20" ~ の行で発生しているのだが、どこに原因があるのか分からない。

    不具合の解消法を教えて下さい。

    開発環境 
    Win7 64ビット
    Microsoft Visual Studio Community 2017
    Version 15.9.9
    VisualStudio.15.Release/15.9.9+28307.518
    Microsoft .NET Framework
    Version 4.7.02558


    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="xxx" Height="628.145" Width="619.398"
        xmlns:local="clr-namespace:qqqq"
             DataContext="{DynamicResource Custom01}">

        <Window.Resources>
            <local:lib_1 x:Key="Custom01"  />
        </Window.Resources>

        <Grid Margin="0,0,0,0">
             <CheckBox Content=" -----" Height="20" IsChecked="{Binding Custom01.P1, Mode=TwoWay}" HorizontalAlignment="Left" Margin="0,0,0,0" x:Name="CheckBox_1" VerticalAlignment="Top" Width="154" />
             </Grid>
    </Window>

    ソースコード

    Imports System.ComponentModel

    Class MainWindow

        Public Sub New()
            ' この呼び出しはデザイナーで必要です。
            InitializeComponent()
            ' InitializeComponent() 呼び出しの後で初期化を追加します。
        End Sub

        Protected Overrides Sub Finalize()
            MyBase.Finalize()
        End Sub

        Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
            lib_1.P1 = True
            Debug.Print(lib_1.P1)
        End Sub

    End Class 'MainWindow


    Public Class lib_1
        Private Shared Property _p1 As Boolean
        Public Shared Property P1 As Boolean
            Get
                Return _p1
            End Get

            Set(value As Boolean)
                _p1 = value
                Debug.Print("### " & value) '←チェックボックスを切り替えてもここに飛ばない
                NotifyPropertyChanged(New PropertyChangedEventArgs("1@"))
            End Set
        End Property



        Private Shared Event PropertyChanged As PropertyChangedEventHandler

        Private Shared Sub NotifyPropertyChanged(e As PropertyChangedEventArgs)
            RaiseEvent PropertyChanged(Nothing, e)
        End Sub

    End Class

    2019年5月22日 22:58

回答

  • 普通に書いてるので、自分はやったことないけどまあできるんだろうな、と思ってスルーしていたのですが。

    改めて確認したところ、Sharedプロパティでは双方向バインディングはできませんね。ソースからターゲットへの通知が行われない。SharedなPropertyChangedにはイベントハンドラが登録されない。

    ということでSharedプロパティを使うのは諦めてください。アプリケーション全体で1つのインスタンスを参照したいのであれば、Sharedインスタンスのインスタンスプロパティを見る形にしましょう。例えば以下のような。

    Public Class lib_1
        Implements INotifyPropertyChanged
    
        Public Shared ReadOnly Property Instance As New lib_1
    
        Private Property _p1 As Boolean
        Public Property P1 As Boolean
            Get
                Return _p1
            End Get
    
            Set(value As Boolean)
                If _p1 <> value Then
                    _p1 = value
                    NotifyPropertyChanged(New PropertyChangedEventArgs(NameOf(P1)))
                End If
            End Set
        End Property
    
        ' INotifyPropertyChanged周りの実装は省略
    End Class
    <Window ...
        DataContext="{x:Static local:lib_1.Instance}">
      <CheckBox IsChecked={Binding P1, Mode=TwoWay}"/>
    </Window>
    • 回答としてマーク huahi11112 2019年5月23日 7:43
    2019年5月23日 4:02

すべての返信

  • 1, 2については、IsCheckedのバインディングのPathがCustom01.P1になっているためにバインディングに失敗しているからかと思います。

    データコンテキストは既にlib_1型のCustom01になっているので、Pathに指定が必要なのはP1だけです。

    3については、Sharedメンバにバインディングするやり方を取る限りは仕方ないと思われます。標準外の手法なので。

    2019年5月23日 0:48
  • あと、P1のSetで、通知するプロパティ名が変なことになっています。

    VS2017であれば、NameOf演算子を使うのが良いかと思います。

    New PropertyChangedEventArgs(NameOf(P1))

    2019年5月23日 1:08
  • 直接の回答ではありませんが、DynamicResourceを使っている意味は何でしょうか? もし、無いのであれば使わない方法もあります。
    ただし、こうしても「
    インスタンス メンバーである型 'lib_1' の 'P1' が必要です」というエラーが無くなるわけではありません。

    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="xxx" Height="628.145" Width="619.398"
        xmlns:local="clr-namespace:qqqq"
       >
    
        <Window.DataContext>
            <local:lib_1 />
        </Window.DataContext>
    
        <Grid Margin="0,0,0,0">
             <CheckBox Content=" -----" Height="20" IsChecked="{Binding P1, Mode=TwoWay}" HorizontalAlignment="Left" Margin="0,0,0,0" x:Name="CheckBox_1" VerticalAlignment="Top" Width="154" />
             </Grid>
    </Window>


    ★良い回答には質問者は回答済みマークを、閲覧者は投票を!

    2019年5月23日 1:25
    モデレータ
  • 普通に書いてるので、自分はやったことないけどまあできるんだろうな、と思ってスルーしていたのですが。

    改めて確認したところ、Sharedプロパティでは双方向バインディングはできませんね。ソースからターゲットへの通知が行われない。SharedなPropertyChangedにはイベントハンドラが登録されない。

    ということでSharedプロパティを使うのは諦めてください。アプリケーション全体で1つのインスタンスを参照したいのであれば、Sharedインスタンスのインスタンスプロパティを見る形にしましょう。例えば以下のような。

    Public Class lib_1
        Implements INotifyPropertyChanged
    
        Public Shared ReadOnly Property Instance As New lib_1
    
        Private Property _p1 As Boolean
        Public Property P1 As Boolean
            Get
                Return _p1
            End Get
    
            Set(value As Boolean)
                If _p1 <> value Then
                    _p1 = value
                    NotifyPropertyChanged(New PropertyChangedEventArgs(NameOf(P1)))
                End If
            End Set
        End Property
    
        ' INotifyPropertyChanged周りの実装は省略
    End Class
    <Window ...
        DataContext="{x:Static local:lib_1.Instance}">
      <CheckBox IsChecked={Binding P1, Mode=TwoWay}"/>
    </Window>
    • 回答としてマーク huahi11112 2019年5月23日 7:43
    2019年5月23日 4:02
  • ありがとうございます。お二方の御回答を取り入れ、下記の様にして動作させました。
    (1)  Me.DataContext は仕方無くインスタンスを代入しました。
    (2) CheckBox.IsCheckedをバインドしたプロパティー(P1)で操作したかったのですが、動かなかったのでCheckBox.IsCheckedを直接操作しています。

    VB.netで回答して下さる方がいらっしゃるので本当にこのフォーラムは役に立ちます。御回答誠にありがとうございました。

    <<MainWindowのプロパティーに双方向バインドしたCheckBoxの作り方>>

    <Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="xxx" Height="228.145" Width="481.398" >

        <Grid Margin="0,0,0,0" >
                <CheckBox Content=" -----" Height="20" IsChecked="{Binding P1, Mode=TwoWay}" HorizontalAlignment="Left" Margin="38,53,0,0" Name="CheckBox_1" VerticalAlignment="Top" Width="154" />
            <Button x:Name="Button1" Content="Check ON/OFF" HorizontalAlignment="Left" Height="47" Margin="227,53,0,0" VerticalAlignment="Top" Width="159" />
            <Button x:Name="Button2" Content="Show P1" HorizontalAlignment="Left" Height="36" Margin="227,133,0,0" VerticalAlignment="Top" Width="108" />
    </Grid>
    </Window>

    ''''''''''''''''''''''''
    Imports System.ComponentModel

    Class MainWindow

        Public Property li1 As New lib_1 'lib1を共有クラスにして使うことはできない ①の行でコンパイルできなくなる

        Public Sub New()
            ' この呼び出しはデザイナーで必要です。
            InitializeComponent()
            ' InitializeComponent() 呼び出しの後で初期化を追加します。
        End Sub

        Protected Overrides Sub Finalize()
            MyBase.Finalize()
        End Sub

        Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded

            Me.DataContext = Me.li1 '① 右辺はインスタンスでなければならない

        End Sub

        Private Sub B11_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click
            Dim b As Boolean = Not li1.P1
            'li1.P1 = b ← こうすると動かない(CheckBoxコントロールに反映されない)
            Me.CheckBox_1.IsChecked = b '← こうしないと動かない
        End Sub

        Private Sub Button2_Click(sender As Object, e As RoutedEventArgs) Handles Button2.Click
            Debug.Print(li1.P1)
        End Sub

    End Class 'MainWindow



    Public Class lib_1
        Implements INotifyPropertyChanged

        Private Property _p1 As Boolean = True

        Public Property P1 As Boolean 'MainWindow.CheckBox1.IsCheckedとバインド
            Get
                Return _p1
            End Get

            Set(value As Boolean)
                If _p1 <> value Then
                    _p1 = value
                    Debug.Print("##" & value)

                    NotifyPropertyChanged(New PropertyChangedEventArgs(NameOf(P1)))
                End If
            End Set
        End Property


        Private Event PropertyChanged As PropertyChangedEventHandler
        Private Event INotifyPropertyChanged_PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        Private Sub NotifyPropertyChanged(e As PropertyChangedEventArgs)
            RaiseEvent PropertyChanged(Nothing, e)
        End Sub

    End Class

    2019年5月23日 7:54
  • 'li1.P1 = b ← こうすると動かない(CheckBoxコントロールに反映されない)
    Me.CheckBox_1.IsChecked = b '← こうしないと動かない

    これについては、PropertyChangedイベントの発生させ方の問題です。

    RaiseEvent PropertyChanged(Nothing, e)

    となっていますが、第1引数にはMeを渡してください。

    WPFのバインディング機構は、これで渡されてきたsenderを見て、通知を受け入れるかどうかを判定しているようです。

    2019年5月23日 8:04
  • HongLiang様、早速の御指摘ありがとうございました。これを取り入れさせていただきます。今後もよろしくお願い致します。
    2019年5月23日 8:20