C#

【WPF】データ・バインディング(Binding)とは?扱い方を理解する

C#のWPFを検索すると、データバインディングというワードを目にする事があることでしょう。

WPFは画面とロジックを分けて開発できます。この開発を実現する為の機能がデータバインディングです。

このデータバインディングはWPFで重要な役割を担っているので、アプリケーションを作成する前に理解しておきたい内容の1つです。

この記事はデータ・バインディング(Binding)を理解する手助けとなることでしょう。ぜひ最後まで読んでみてください。

データ・バインディング(Binding)とは?

データ・バインディングとは、MVVM(Model-View-ViewModel)パターンでいうViewとViewModelを結びつける為に提供されている仕組みの事です。

バインディング(Binding)という単語には、「結びつける」という意味があるように、ViewもしくはViewModelのどちらかのプロパティが変更されると、それに結びついているViewModelもしくはViewのプロパティも即座に更新されます。

データバインディングの使い方

WPFでViewに該当する部分がXAMLです。ViewとViewModelを結びつけるには、以下のルールがあります。

  1. プロパティに”{Binding プロパティ名}”を書く
  2. DataContextプロパティにデータソースを渡す

それぞれのルールについてみてみましょう。

プロパティに”{Binding プロパティ名}”を書く

XAML内のプロパティに”{Binding プロパティ名}”を記述します。こうすることでViewModelが保持するプロパティの値をViewに反映させる事ができるようになります。

実際にXAMLに書くと次のようになります。

View

<Window
    x:Class="Sample.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:local="clr-namespace:Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="300"
    Height="100"
    mc:Ignorable="d">
    <Grid>
        <TextBox Margin="20" Text="{Binding Input}" />
    </Grid>
</Window>

ViewModel

namespace Sample
{
    class ViewModel
    {
        public string Input { get; set; } = "初期値";
    }
}

上記の例では、TextBoxのTextプロパティに対して、”{Binding Input}”と書いてViewModelのInputプロパティを結びつけています。

ViewとViewModelを結びつけるには、もう1つ条件があります。

DataContextプロパティにデータソースを渡す

ViewとViewModel間のやり取りはDataContextプロパティを介して双方向のデータのやり取りをします。

DataContextプロパティにはViewModelのデータ・ソース(オブジェクト)を渡せば結びつけが完了します。こうする事でXAMLからDataContext(ViewModel)プロパティを参照可能になります。

DataContextプロパティを設定するには、XAMLもしくはコードビハインドのどちらかで設定をします。

XAMLで設定する場合

<Window.DataContext>
    <local:ViewModel />
</Window.DataContext>

コードビハインドで設定する場合

DataContext = new ViewModel();

これで最低限の設定は完了しました。

動作確認

設定したデータバインディングがどのように作用するのかを見てみましょう。

画面にTextBlockとButtonを追加し、ViewModelのプロパティをTextBlockのプロパティにバインドします。こうすればプロパティの更新有無を可視化できます。Buttonがクリックされたら、ViewModelのプロパティに「初期化」を代入する処理を記述します。

View

<Window
    x:Class="Sample.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:local="clr-namespace:Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="300"
    Height="160"
    mc:Ignorable="d">
    <Grid>
        <TextBox Margin="20,20,20,80" Text="{Binding Input}" />
        <TextBlock Margin="20,60,20,40" Text="{Binding Input}" />
        <Button Margin="20,95,20,10" Click="Button_Click" Content="プロパティの初期化" />
    </Grid>
</Window>

コードビハインド(MainWindow.xaml.cs)

using System.Windows;

namespace Sample
{
    public partial class MainWindow : Window
    {
        ViewModel vm = new ViewModel();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = vm;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            vm.Input = "初期化";
        }
    }
}

ではアプリケーションを動かしてみましょう。

あれれ。おかしな点がありますね。

  1. TextBoxへ入力した直後に、TextBlockの値が更新されない。
  2. Buttonをクリックしても、TextBoxとTextBlockの文字が「初期化」に更新されない。

まず①ですが、上のGifを確認するとTextBoxからフォーカスから外れた(ボタンをクリックした)タイミングでInputプロパティと同期しているTextBlockが更新されていることが分かります。

これは、UpdateSourceTriggerのデフォルト値がLostFoucusになっているからです。

TextBoxへ入力した直後にプロパティを更新する場合は、LostFoucusからPropertyChangedへ書き換えます。

UpdateSourceTriggerプロパティには以下の値が設定できます。

説明
LostFocusフォーカスが外れたタイミングでソースの値を更新します。
PropertyChangedプロパティの値が変化したタイミングでソースの値を更新します。
ExplicitUpdateSourceメソッドを呼び出して明示的にソースの更新を指示したときのみソースの値を更新します。

上記内容をXAMLに反映させると次のようになります。

View

<Window
    x:Class="Sample.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:local="clr-namespace:Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="300"
    Height="160"
    mc:Ignorable="d">
    <Grid>
        <TextBox Margin="20,20,20,80" Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}" />
        <TextBlock Margin="20,60,20,40" Text="{Binding Input}" />
        <Button Margin="20,95,20,10" Click="Button_Click" Content="プロパティの初期化" />
    </Grid>
</Window>

続いて②ですが、ViewModelからViewへの変更通知がないことが分かります。変更通知を有効にする為に、ViewModelのプロパティ値に変化があった場合に通知するイベントを用意します。

具体的には、INotifyPropertyChangedインターフェイス(System.ComponentModel名前空間)を継承してイベントの実装を行います。

ViewModelを以下のように修正します。

ViewModel

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Sample
{
    class ViewModel : INotifyPropertyChanged
    {
        // INotifyPropertyChanged を実装するためのイベントハンドラ
        public event PropertyChangedEventHandler PropertyChanged;

        // プロパティ名によって自動的にセットされる
        private void RaisePropertyChanged([CallerMemberName] string propertyName = null) =>
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        string _Input = "初期化";
        // 入力テキスト用のプロパティ
        public string Input
        {
            get { return _Input; }
            // 値をセットした後、画面側でも値が反映されるように通知する
            set { if (_Input != value) { _Input = value; RaisePropertyChanged(); } }
        }
    }
}

修正後のプログラムを動作させてみましょう。先ほどと同じ動作をさせてみました。

上図をみて頂ければ、全く異なる動作をしている事が分かると思います。TextBoxに入力した値が直ぐ、TextBlockへ反映されています。また、Buttonをクリックした時にTextBoxとTextBlockの値が「初期化」になっています。

DataContext以外のBindingの使い方

Bindingは、DataContentプロパティによるデータソースの指定以外に、いくつかの使い方があります。

ElementNameを指定して参照する方法

ElementNameは、UIコントロール間で直接プロパティの値の同期を取りたい時に使用します。コントロールのName(x:Name)プロパティで指定された名前をElementNameに指定します。

使い方の例として、Slider(スライダー)のValueプロパティの値をTextBlockのTextプロパティに表示します。ElementNameにSliderのNameを指定して、同期させるプロパティ名をPathに指定します。

View

<Window
    x:Class="Sample.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:local="clr-namespace:Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="300"
    Height="100"
    mc:Ignorable="d">
    <Grid>
        <Slider Name="sliderName" Margin="10,10,10,40" Value="0" />
        <TextBlock Margin="10,40,10,10" Text="{Binding ElementName=sliderName, Path=Value}" />
    </Grid>
</Window>

上記のXAMLは次のような動作になります。

スライダーを動かすとTextBlockの値が更新されます。

RelativeSourceを指定して参照する方法

RelativeSourceは、UIコントロールのプロパティ値やUIコントロールの親となる要素まで遡ってプロパティ値を取得したい場合に使います。

UIコントロールのプロパティ値を取得したい場合は、プロパティ名を指定し、RelativeSourceのModeプロパティを「Self」にします。

View

<Window
    x:Class="Sample.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:local="clr-namespace:Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="300"
    Height="80"
    mc:Ignorable="d">
    <Grid>
        <TextBox Margin="10,10,10,10"
                 Text="{Binding Margin, RelativeSource={RelativeSource Mode=Self}}" />
    </Grid>
</Window>

上記のXAMLは次のような動作になります。

Marginの設定値が表示されます。

今度はUIコントロールの親となる要素まで遡ってプロパティ値を取得します。

この場合、AncestorTypeプロパティに親となる要素の情報を記入します。ここではWindowのTitleを取得しています。

View

<Window
    x:Class="Sample.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:local="clr-namespace:Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="300"
    Height="80"
    mc:Ignorable="d">
    <Grid>
        <TextBox Margin="10,10,10,10"
                 Text="{Binding Title, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
    </Grid>
</Window>

上記のXAMLは次のような動作になります。

Windowのタイトルである「MainWindow」がTextBoxに表示されました。

BindingのMode設定

Bindingには、データバインディングの向きを指定する為のModeプロパティというのがあります。

モード説明
OneWayソースからターゲットへの一方通行の同期になります。
TwoWayソースとターゲットの双方向の同期になります。
OneWayToSourceターゲットからソースへの一方通行の同期になります。
OneTimeソースからターゲットへ初回の一度だけ同期されます。

双方向の同期が必要なコントロール(TextBox等)は、TwoWayを指定しておく必要があります。

まとめ

この記事では、WPFのデータバインディングについてサンプルを交えながら使い方を紹介しました。

データバインディングのルールをしては2つあります。

  1. プロパティに”{Binding プロパティ名}”を書く
  2. DataContextプロパティにデータソースを渡す

また、INotifyPropertyChangedインターフェースを継承してイベントを発生させて、ViewModelからViewへ変更通知を行います。

これらのことを踏まえて、MVVMパターンの仕組みを利用していきましょう。

以上、最後まで読んでいただきありがとうございました。

プログラミングを学習したいなら…

プログラミングスキルを身に付けるなら、プログラミングを効率良く学べる
プログラミングスクール」がオススメです。

特にこんな方にオススメ!!
これからエンジニアを目指したい
プログラミングの専門性を高めたい
プログラミングを学んで副業をしたい
エンジニアに転職して年収をアップさせたい

プログラミングを触ったことがない未経験からでも、プログラミングスクールで学習すれば、エンジニアへ就職・転職することも可能です。

あなたの「行動力」と「やる気」で、あなたの人生を大きく変えるチャンスになることでしょう。

プログラミングスクールに興味がある方は是非チェックしてみてください。

> プログラミングを学ぶ <

COMMENT

メールアドレスが公開されることはありません。

CAPTCHA