none
WPF TextBoxの双方向バインディングが動作しない RRS feed

  • 質問

  • サンプルプログラムを掲載します。
    pp1.test1 の文字列をTextBox1.textに双方向バインドしています。
    上のボタン(B1)を押すと、pp1.test1を表示します。TextBox1にキー入力して改変すると、正常に結果が表示されます。

    ところが、下のボタン(B2)でpp1.test1を改変しているのにTextBox1.textが書き換わりません。
    この原因は何ですか?解決方法を教えて下さい。


    <<XAML>>
    <Window x:Class="MainWindow"
            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:WpfApp1"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Grid>
            <TextBox x:Name="TextBox1" HorizontalAlignment="Left" Height="29" Margin="119,70,0,0"  Text="{Binding test1,Mode=TwoWay}" VerticalAlignment="Top" Width="243"/>
            <Button x:Name="B1" Content="test1 Propertyを表示" HorizontalAlignment="Left" Height="65" Margin="432,62,0,0" VerticalAlignment="Top" Width="163" Click="Button_Click1"/>
            <Button x:Name="B2" Content="test1 Propertyを改変" HorizontalAlignment="Left" Height="65" Margin="432,155,0,0" VerticalAlignment="Top" Width="163" Click="Button_Click2"/>
        </Grid>
    </Window>



    <<VB.net>>
    Imports System.ComponentModel
    Imports System.Threading.Tasks

    Class MainWindow
        Public Property pp1 As New Class1

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

            Me.DataContext = pp1

            pp1.test1 = "started..." '←これでMe.TB1.textは書き換わっている
        End Sub

        'test1 Propertyを表示 ボタン
        Private Sub Button_Click1(sender As Object, e As RoutedEventArgs)
            Debug.Print(pp1.test1)
        End Sub

        'test1 Propertyを改変 ボタン
        Private Async Sub Button_Click2(sender As Object, e As RoutedEventArgs)
            Await Task.Run(Sub() System.Threading.Thread.Sleep(100))
            'こうしてもMe.TB1.textが書き換わらない
            pp1.test1 = Now
        End Sub

    End Class

    Public Class Class1
        Implements INotifyPropertyChanged

        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        Private Property _test1 As String

        Public Property test1 As String
            Get
                Return _test1
            End Get
            Set(ByVal value As String)
                _test1 = value
                OnPropertyChanged("Dummy")
            End Set
        End Property

        Public Sub OnPropertyChanged(ByVal v As String)
            Debug.Print("OnPropertyChanged")
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(v))
        End Sub

    End Class
    2020年8月11日 7:58

回答

  • Await Task.Run(Sub() System.Threading.Thread.Sleep(100))

    本題と関係ないですが、Await Task.Delay(100)としたほうがいいでしょう。

    OnPropertyChanged("Dummy")

    ここは本当にこうなっているのでしょうか? だとしたら、Dummyという名前のプロパティが更新されたことがバインド先に通知されますが、TextBoxは「自分はtest1という名前のプロパティにバインドしているから、Dummyというプロパティの変更は関係ないな」となって表示が更新されないことになりますが。

    // こういうのを避けるために、OnPropertyChanged(NameOf(test1))と記述したり、あるいは以下のような記述をしたりします。

    Protected Sub OnPropertyChanged(<CallerMemberName()> Optional propName As String = Nothing)
        ' 中身は変わらないので省略
    End Sub
    
    Public Property test1 As String
        Set(ByVal value As String)
            _test = value
            OnPropertyChanged() ' 引数省略すると呼び出したメンバ名test1が自動的に渡される
        End Set
        ' Getは略
    End Property

    • 編集済み Hongliang 2020年8月11日 8:10
    • 回答としてマーク huahi11112 2020年8月12日 7:22
    2020年8月11日 8:09

すべての返信

  • Await Task.Run(Sub() System.Threading.Thread.Sleep(100))

    本題と関係ないですが、Await Task.Delay(100)としたほうがいいでしょう。

    OnPropertyChanged("Dummy")

    ここは本当にこうなっているのでしょうか? だとしたら、Dummyという名前のプロパティが更新されたことがバインド先に通知されますが、TextBoxは「自分はtest1という名前のプロパティにバインドしているから、Dummyというプロパティの変更は関係ないな」となって表示が更新されないことになりますが。

    // こういうのを避けるために、OnPropertyChanged(NameOf(test1))と記述したり、あるいは以下のような記述をしたりします。

    Protected Sub OnPropertyChanged(<CallerMemberName()> Optional propName As String = Nothing)
        ' 中身は変わらないので省略
    End Sub
    
    Public Property test1 As String
        Set(ByVal value As String)
            _test = value
            OnPropertyChanged() ' 引数省略すると呼び出したメンバ名test1が自動的に渡される
        End Set
        ' Getは略
    End Property

    • 編集済み Hongliang 2020年8月11日 8:10
    • 回答としてマーク huahi11112 2020年8月12日 7:22
    2020年8月11日 8:09
  • 御回答ありがとうございました。望んだ通りの動作が確認できました。
    続けて、Class1を共有クラスとしてコーディングしたいのですが、

    Public Sub OnPropertyChangedをsharedにすると RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))のコンパイルが通らなくなります。
    一つ目は、「PropertyChanged」の部分のコンパイルエラーで、「クラスの明示的なインスタンスを指定しないで共有メソッドまたは共有メンバー初期化子内からクラスのインスタンスメンバーを参照することはできません。」という内容です。
    もう一つは、Meの部分ですが、「Meは、インスタンス メソッド内でのみ有効です。」
    この2つのいずれも、表示されている意味が分かりません。ずうずうしいのを承知でぶっつけでお願いしますが、Class1を共有クラスにするにはどう書けば良いのか、教えて下さい。

    Public Class Class1
        Implements INotifyPropertyChanged

        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        Private Property _test1 As String

        Public Property test1 As String
            Get
                Return _test1
            End Get

            Set(ByVal value As String)
                _test1 = value
                OnPropertyChanged()
            End Set
        End Property


        Public Sub OnPropertyChanged(<CallerMemberName()> Optional propName As String = Nothing)
            Debug.Print("OnPropertyChanged")
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
        End Sub

    End Class

    2020年8月12日 7:35
  • そもそもSharedプロパティをバインディングすることはできません。

    なんか既視感あると思ってたけどこれだ WPF CheckBox.IsCheckedを双方向バインディングする方法

    • 編集済み Hongliang 2020年8月12日 8:08
    2020年8月12日 8:03
  • そうでしたね。対象のコントロールがCheckBoxでもTextBoxでも動かないものは同じなのですね。現在、次のソリューションに向けて初期構想を練っているところなので、ここで初めからしっかりした設計をしたいと思っています。この問題についての理解が深まりましたので、御回答に感謝致します。
    2020年8月14日 2:51