none
[WPF]バインディングが機能しない RRS feed

  • 質問

  • [質問]
    目標通りの動作しない理由分かりません。気になる点は下記①②③です。
    依存プロパティの勉強中です。アドバイス宜しくお願いします。

     ①名前空間の指定方法および参照方法が正しいか?
     ②INotifyPropertyChangedの実装が正しいか?
     ③XAMLのバインディングの記述が正しいか?

    [目標の動作]
    ・SampleClassクラスのMessageプロパティが更新されたら、それに同期してTextBoxのTextプロパティを更新する。
    ・起動直後には"Start!"が表示されていて、Bottonをクリックすると、その表示が"Click!"に変化することを期待している。


    以下、ソースコード

    [C#] (データソース側)

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Markup;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;

    [assembly: XmlnsDefinition("http://www.BindingTest.com", "BindingTest")]
    namespace BindingTest
    {
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                _sample = new SampleClass("Start!");
            }
            private SampleClass _sample;

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                _sample.Message = "Click!";
            }
        }
        public sealed class SampleClass : INotifyPropertyChanged
        {
            public SampleClass(string message)
            {
                _message = message;
            }
            public event PropertyChangedEventHandler PropertyChanged;
            private void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("propertyName"));
                }
            }
            private string _message;
            public string Message
            {
                get { return _message; }
                set
                {
                    _message = value;
                    OnPropertyChanged("Message");
                }
            }
        }
    }

    [XAML] (UI側)

    <Window x:Class="BindingTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
            xmlns:w="http://www.BindingTest.com"
            x:Name="MainWin" Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <Button Content="Button" Click="Button_Click"/>
            <TextBox Height="23" TextWrapping="Wrap" Text="{Binding Message,ElementName=w:SampleClass,Mode=OneWay}"/>
        </StackPanel>
    </Window>


    C#開発者




    • 編集済み MicroVAX 2014年10月15日 10:18
    2014年10月15日 10:10

回答

  • 依存プロパティの勉強中です。アドバイス宜しくお願いします。

    とありますが、実装されているのは通常のプロパティです。掲載されているコードは依存プロパティ(依存関係プロパティが一般的な呼び名です)による実装ではなく、INotifyPropertyChangedインターフェースを使った実装ですが、その辺りの違いは認識されていますでしょうか?

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク MicroVAX 2014年10月16日 10:56
    2014年10月15日 12:58
    モデレータ
  • そもそもSampleClassというのはクラス名であり、インスタンスではありません。一体何をバインドするというのでしょうか。

    もちろん、バインディングソースとしてSampleClassのインスタンスを定義することもできます。ひらぽんさんのソースではWindowのDataContextとしてSampleClassのインスタンスを設定しているので、データソースを継承しているTextBoxでMessageプロパティにバインドできています。

    プロパティ名が衝突しようが、そもそもデータソースとしてUI要素が設定されていないなら何の関係もありません。なお、{Binding プロパティ名, RelativeSource={RelativeSource Self}}など、データソースを明示することが可能です。

    • 回答としてマーク MicroVAX 2014年10月17日 3:55
    2014年10月16日 11:26
  • > 例中のSampleClassのプロパティ名がUI要素のプロパティ名と衝突した場合、バインディング時のSourceがSampleClassのプロパティであることを明示する方法がありますか?

    私から言わせて頂けば 「UI要素のプロパティ名と衝突」 を考慮する必要はありません。どのバインディングソースのプロパティとバインドするか考慮すればいいだけです。何も指定しないなら DataContext を参照します。

    詳しくは Hongliang さんと trapemiya さんの回答に尽きますが、一応以下の記事も紹介させて頂きます。いずれも岩永さんの記事ですが、WPF のデータ・バインディングの概要を判りやすく纏めておられます。

    WPFの「データ・バインディング」を理解する

    データバインディング(WPF)



    MSDNフォーラムのヘルプは以下ご覧ください http://social.technet.microsoft.com/wiki/contents/articles/7359.forums-help-faq.aspx


    2014年10月17日 2:43
    モデレータ
  • 質問2の件、了解いたしました。
    ただ、最初の質問は解決し、次の質問に移っていますので、お手数ですが、質問2を新たなスレッドとして作成願えませんでしょうか?1つのスレッドに1つの質問にしておかないと、後から解決策を探す人にとって探しにくくなってしまいます。よろしくお願いいたします。

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク MicroVAX 2014年10月17日 5:24
    2014年10月17日 4:09
    モデレータ

すべての返信

  • private void OnPropertyChanged(string propertyName)
    {
       if (PropertyChanged != null)
       {
          PropertyChanged(this, new PropertyChangedEventArgs("propertyName"));
       }
    }

    引数propertyNameの文字列ではなく、文字列"propertyName"自体を通知しています。そんな名前のプロパティは存在していないので、変更通知は意味をなさないでしょう。

    <TextBox Height="23" TextWrapping="Wrap" Text="{Binding Message,ElementName=w:SampleClass,Mode=OneWay}"/>

    ElementNameはx:NameまたはNameで設定した名前を指定します。SampleClassは名前を付けられないので、ElementNameでデータソースを指定することはできません。

    バインディングのデータソースとしては、Window::DataContextにデータオブジェクトに設定することが多いです。DataContextは、基本的にそのまま子孫要素に継承されるので、Window::DataContextに設定したオブジェクトはTextBoxでもそのまま扱えます。その場合、{Binding}においてデータソースは一般には明示しません(ElementNameやRelativeSourceやSourceを指定しないで、PropertyPathのみ指定する)。

    2014年10月15日 10:52
  • <TextBox Height="23" TextWrapping="Wrap" Text="{Binding Message,ElementName=w:SampleClass,Mode=OneWay}"/>

    まずここがおかしいです。そもそも Binding.ElementName プロパティはXAML内の名前付き要素を参照するプロパティであり、SampleClass は要素に存在しないためバインドできません。 SampleClass のインスタンスを Window.DataContext に設定し、

    <TextBox Height="23" TextWrapping="Wrap" Text="{Binding Message, Mode=OneWay}"/>

    という風にバインドすべきだと思います。以下修正例ですが、これはButtonクリックで動作が確認できました。

    using System.ComponentModel;
    using System.Windows;
    
    namespace BindingTest {
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window {
            public MainWindow() {
                InitializeComponent();
                _sample = new SampleClass("Start!");
                this.DataContext = _sample;
            }
            private SampleClass _sample;
            private void Button_Click(object sender, RoutedEventArgs e) {
                _sample.Message = "Click!";
            }
        }
    
        public sealed class SampleClass : INotifyPropertyChanged {
            public SampleClass(string message) {
                _message = message;
            }
            public event PropertyChangedEventHandler PropertyChanged;
            private void OnPropertyChanged(string propertyName) {
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            private string _message;
            public string Message {
                get { return _message; }
                set {
                    _message = value;
                    OnPropertyChanged("Message");
                }
            }
        }
    }
    <Window x:Class="BindingTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            x:Name="MainWin" Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <Button Content="Button" Click="Button_Click"/>
            <TextBox Height="23" TextWrapping="Wrap" Text="{Binding Message, Mode=OneWay}"/>
        </StackPanel>
    </Window>



    MSDNフォーラムのヘルプは以下ご覧ください http://social.technet.microsoft.com/wiki/contents/articles/7359.forums-help-faq.aspx


    • 編集済み ひらぽんModerator 2014年10月15日 11:09 意図せぬspanタグが追加されデザインが崩れたため修正
    2014年10月15日 11:06
    モデレータ
  • 依存プロパティの勉強中です。アドバイス宜しくお願いします。

    とありますが、実装されているのは通常のプロパティです。掲載されているコードは依存プロパティ(依存関係プロパティが一般的な呼び名です)による実装ではなく、INotifyPropertyChangedインターフェースを使った実装ですが、その辺りの違いは認識されていますでしょうか?

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク MicroVAX 2014年10月16日 10:56
    2014年10月15日 12:58
    モデレータ
  • ご回答ありがとうございます。
    ご指摘の方法で意図通り動作することが確認できました。

    [追加の質問]
    例中のSampleClassのプロパティ名がUI要素のプロパティ名と衝突した場合、バインディング時のSourceがSampleClassのプロパティであることを明示する方法がありますか?
    「SampleClassに名前を付ける手段が提供されていないので、XAMLから明示する方法も無い。」という理解で正しいのでしょうか?

    XAMLの抜粋
    <TextBox ...中略 Text="{Binding Message, Mode=OneWay}"/>

    あるいは、プロパティ名を維持したまま衝突を回避する他の手段がありますか?


    C#開発者

    2014年10月16日 11:05
  • そもそもSampleClassというのはクラス名であり、インスタンスではありません。一体何をバインドするというのでしょうか。

    もちろん、バインディングソースとしてSampleClassのインスタンスを定義することもできます。ひらぽんさんのソースではWindowのDataContextとしてSampleClassのインスタンスを設定しているので、データソースを継承しているTextBoxでMessageプロパティにバインドできています。

    プロパティ名が衝突しようが、そもそもデータソースとしてUI要素が設定されていないなら何の関係もありません。なお、{Binding プロパティ名, RelativeSource={RelativeSource Self}}など、データソースを明示することが可能です。

    • 回答としてマーク MicroVAX 2014年10月17日 3:55
    2014年10月16日 11:26
  • 例中のSampleClassのプロパティ名がUI要素のプロパティ名と衝突した場合、バインディング時のSourceがSampleClassのプロパティであることを明示する方法がありますか?
    「SampleClassに名前を付ける手段が提供されていないので、XAMLから明示する方法も無い。」という理解で正しいのでしょうか?

    XAMLの抜粋
    <TextBox ...中略 Text="{Binding Message, Mode=OneWay}"/>

    あるいは、プロパティ名を維持したまま衝突を回避する他の手段がありますか?

    XAMLで、<TextBox ...中略 Text="{Binding Message, Mode=OneWay}"/>のようにBindingのソースを指定しなかった場合、ソースはDataContextに設定されているオブジェクトになります。もし、DataContextにSampleClassのインスタンスがセットされているのであれば、MessageはSampleClassのプロパティになります。
    このように、BindingはDataContextに入れたものに対して行われます。決して、SampleClassのような特定のインスタンスを直接指定してバインドしていないことに注意して下さい。
    くどいようですが、DataContextという入れ物を通してバインドが行われることに注意して下さい。

    「DataContextという入れ物を通してバインドが行われる」ということが理解できれば、DataContextに入っていないUI要素は全くバインドの対象とならないことも理解できるはずです。よって、UI要素にどのようなプロパティ名があろうとも衝突しないことも理解できるでしょう。

    なお、Bindingの対象はDataContext以外にすることもできます。この場合は、Source, RelativeSource, ElementNameの3つのオプションのどれかを使うことになります。これを使えば、UI要素などをBindingの対象にすることができます。

    (参考)
    方法 : バインディング ソースを指定する
    http://msdn.microsoft.com/ja-jp/library/ms746695(v=vs.110).aspx

    #質問されたことは解決されたようですが、どのように解決されたのでしょうか? もし、参考となった回答やコードがあれば、「回答としてマーク」も合わせてお願いいたします。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク MicroVAX 2014年10月17日 3:55
    • 回答としてマークされていない MicroVAX 2014年10月17日 3:55
    2014年10月17日 2:13
    モデレータ
  • > 例中のSampleClassのプロパティ名がUI要素のプロパティ名と衝突した場合、バインディング時のSourceがSampleClassのプロパティであることを明示する方法がありますか?

    私から言わせて頂けば 「UI要素のプロパティ名と衝突」 を考慮する必要はありません。どのバインディングソースのプロパティとバインドするか考慮すればいいだけです。何も指定しないなら DataContext を参照します。

    詳しくは Hongliang さんと trapemiya さんの回答に尽きますが、一応以下の記事も紹介させて頂きます。いずれも岩永さんの記事ですが、WPF のデータ・バインディングの概要を判りやすく纏めておられます。

    WPFの「データ・バインディング」を理解する

    データバインディング(WPF)



    MSDNフォーラムのヘルプは以下ご覧ください http://social.technet.microsoft.com/wiki/contents/articles/7359.forums-help-faq.aspx


    2014年10月17日 2:43
    モデレータ
  • ご回答ありがとうございます。

    私の不適切な表現のため、質問の意図を伝えることができず申し訳ありませんでした。
    例題2と質問2を使って再度説明を試みます。
    宜しくお願いします。

    [質問2]
    TextBox1のTextには、_sample1インスタンスのMessageプロパティ、FontSizeには、_sample2インスタンスのMessageプロパティをバインドする方法がありますか?

    質問2の捕捉説明

    データコンテキストに一方のインスタンスを設定すると、もう一方が参照できず困っています。
    下の例題2は、データコンテキストの_sample1を設定しているため、_sample1のインスタンスが設定され、起動時にフォントサイズ20Fの"Sample1 Start!"が表示され、ボタンをクリックすると、フォントサイズ40Fの"Sample1 Start!"に変化します。メッセージはそのままで、フォントサイズを_sample2を参照するように変更したい。つまり、起動時は、フォントサイズ50Fでボタンをクリックするとフォントサイズ100Fに変化することになります。

    データコンテキストに1つのインスタンスを設定して、XAMLからプロパティ名のみ指定する方法では対応できないように思えます。このように複数のCLRインスタンスを同時に参照したい場合、どのようにして解決するかが質問2の意図です。

    ※SampleClassと_sample1および_sample2インスタンスは、説明のためむりやり作成したデータ構造です。このデータ構造を見直すことで、結果的に問題が回避できるかも知れませんが、あくまでこの基本構造を維持しつつ解決したいというのが質問2の意図です。

    以下、例題2

    [C#]

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Markup;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;

    namespace BindingTest
    {
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                _sample1 = new SampleClass("Sample1",20.0F);
                _sample2 = new SampleClass("Sample2",50.0F);
                this.DataContext = _sample1;
            }
            private SampleClass _sample1;
            private SampleClass _sample2;

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                _sample1.FontSize = 30.0F;
                _sample2.FontSize = 75.0F;
            }
        }
        public sealed class SampleClass : INotifyPropertyChanged
        {
            public SampleClass(string message,float fontSize)
            {
                _message = message;
                _fontSize = fontSize;
            }
            public event PropertyChangedEventHandler PropertyChanged;
            private void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            private string _message;
            public string Message
            {
                get { return _message; }
                set
                {
                    _message = value;
                    OnPropertyChanged("Message");
                }
            }
            private float _fontSize;
            public float FontSize
            {
                get { return _fontSize; }
                set
                {
                    _fontSize = value;
                    OnPropertyChanged("FontSize");
                }
            }
        }
    }


    [XAML]

    <Window x:Class="BindingTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"       
            x:Name="MainWin" Title="MainWindow" Height="350" Width="525">
        <StackPanel>
            <Button Content="Button" Click="Button_Click"/>
            <!-- Textには_sample1インスタンスのMessageプロパティ、FontSizeには_sample2インスタンスのFontSizeプロパティ、をバインドしたい。-->
            <!-- 複数のCLRインスタンスを同時に参照する方法が知りたい。 -->
            <TextBox Height="100" TextWrapping="Wrap" Text="{Binding Message}" FontSize="{Binding FontSize}" Background="LightBlue"/>
        </StackPanel>
    </Window>


    C#開発者

    2014年10月17日 3:54
  • 質問2の件、了解いたしました。
    ただ、最初の質問は解決し、次の質問に移っていますので、お手数ですが、質問2を新たなスレッドとして作成願えませんでしょうか?1つのスレッドに1つの質問にしておかないと、後から解決策を探す人にとって探しにくくなってしまいます。よろしくお願いいたします。

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    • 回答としてマーク MicroVAX 2014年10月17日 5:24
    2014年10月17日 4:09
    モデレータ
  • 了解しました。

    「WPF XAMLから複数のCLRインスタンスへのバインド」というタイトルでスレッドで投稿させていただきました。


    C#開発者

    2014年10月17日 5:24
  • 以下のスレッドを新しく立てられたということですね。そのスレッドのリンク先も掲載しておきます。
    ご協力、ありがとうございました。

    WPF XAMLから複数のCLRインスタンスへのバインド
    https://social.msdn.microsoft.com/Forums/ja-JP/7a6136da-29d4-42ad-8a33-3142a7c23d8c/wpf-xamlclr?forum=csharpgeneralja


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    2014年10月17日 5:36
    モデレータ