[Silverlight][バインディング] Blendを使ってサンプルデータをバインディングする

今回は Visual Studio ではなく、Blendを使用して Silverlightのサンプルアプリケーションを作成します。(ノンコーディングなので、今回の操作はVB.NETもC#も共通です)

まずは Blendを起動し「新規プロジェクト」をクリックします。

Blendを起動

「新しいプロジェクト」ダイアログが開くので、「Silverlightアプリケーション」を選択し[OK]ボタンをクリックします。(名前や場所は自由に付けてください)

サンプルプロジェクトの作成

次に「アセット」タブを選択して、「コントロール」の中からListBoxを見つけ、MainPage.xaml にドラッグ&ドロップをします。ListBoxのサイズは適当に設定しておきます。

ListBoxの配置

ここからサンプルデータの作成に入ります。

最初に「データ」タブを選択します。

次に、どこにサンプルデータを作成するかを選択するのですが今回はプロジェクト全体で使えるようにする「プロジェクト」を選択状態にし、「サンプルデータの作成」ボタンをクリックします。開いているドキュメントだけで使用する場合は「このドキュメント」を選択します。

サンプルデータの作成

「新しいサンプルデータ」をクリックします。

「新しいサンプルデータ」を選択

 

「新しいサンプルデータ」ダイアログが表示されるので、データソース名を入力し[OK]ボタンをクリックします。今回はデフォルトのままとします。

データソース名を設定する

追加したデータソースにはコレクションがあり、2つのプロパティがあることを確認できます。

コレクションとプロパティが2つあることを確認

Collectionにぶら下がっている2つのプロパティの名前を「Img」と「Name」に変更します(プロパティ名を変更するには、表示されているプロパティ名をゆっくりと2回クリックします。文字を入力できるようになるので、元々のプロパティ名を削除して新しい名称を入力します。)

プロパティ名の変更

次に、「Img」と「Name」のデータ型を変更します。

「Img」プロパティは「イメージ型」に変更します。場所には、サンプルで使用するイメージが格納されているフォルダを指定します。サンプルイメージを持っていない場合は空欄にします。

空欄にすると Blendが持っているサンプルイメージが使用されるようになります。

今回は「場所」は空欄にしておきます。

「Img」プロパティのデータ型を「イメージ」に変更

 

「Name」プロパティは、文字列型にし、形式に「名前」を指定します。形式にはいくつか種類があるので、サンプルとしてふさわしいものを選択してください。「名前」を選択した場合は人の名前がサンプルデータとして設定されます。

データ型を文字列型にし、形式に「名前」を設定

 

ここまでできたら、SampleDataSource にある Collection を ListBoxにドラッグ&ドロップします。

CollectionをListBoxにドラッグ&ドロップ

すると、ListBoxにはサンプルデータが表示されます。

サンプルデータが表示されたListBox

それでは、実行してみましょう。

実行例

ブラウザでもサンプルデータが表示されます。

このように、データを一から準備する必要はなく、簡単にサンプルデータを作成することができます。

[Silverlight][バインディング] ComboBoxへのデータ表示

Silverlight のComboBoxへの最も簡単なデータの表示は、XAMLコードによるものだと思います。

たとえば「Eric」「Paul」「Billy」「Pat」というデータ表示するXAMLコードは以下のようになります。

ComboBoxにデータを表示するXAMLコードの例

    <ComboBox HorizontalAlignment="Left" Margin="10,10,0,0" 
        VerticalAlignment="Top" Width="120">
        
        <ComboBoxItem Content="Eric" />
        <ComboBoxItem Content="Paul" />
        <ComboBoxItem Content="Billy" />
        <ComboBoxItem Content="Pat" />
        
    </ComboBox>

 

データ数が少なく固定化されたデータであれば上記でも十分かと思います。
しかし、表示値以外に内部値も持たせる場合はどうでしょうか。

例えば社員名と社員番号を持つリストを表示させたいとしましょう。

社員名をコンボボックスに表示させ、内部値として社員IDを持たせる場合を考えます。

この場合は、XAMLだけで表現するよりもデータバインドを利用した方が効率が良さそうです。

まずは、社員情報を表すEmployee クラスを作成します。

VB.NET(Employee.vb)

Private _empData As ObservableCollection(Of Employee)

Public Class EmployeeData

    Public Property Id As Integer

    Public Property Name As String

End Class

C#(Employee.vb)

namespace SampleComboBoxCS
{
    public class Employee 
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

次に、上記で作成した Employeeクラスを使用して4人分のデータを作成し、MainPage のDataContextへバインドします。

以下は MainPage でのコード例です。

VB.NET(MainPage.xaml.vb の Sub Newを抜粋)

Public Sub New()
    InitializeComponent()

    _empData = New ObservableCollection(Of Employee)() From
    {
        New Employee() With {.Id = 1, .Name = "Eric"},
        New Employee() With {.Id = 2, .Name = "Paul"},
        New Employee() With {.Id = 3, .Name = "Billy"},
        New Employee() With {.Id = 4, .Name = "Pat"}
    }

    Me.DataContext = _empData
End Sub

C#(MainPage.xaml.cs の mainを抜粋)

private ObservableCollection<Employee> _empData;

public MainPage()
{
    InitializeComponent();

    _empData = new ObservableCollection<Employee>()
    {
        new Employee { Id = 1, Name = "Eric"},
        new Employee { Id = 2, Name = "Paul"},
        new Employee { Id = 3, Name = "Billy"},
        new Employee { Id = 4, Name = "Pat"},
    };

    this.DataContext = _empData;
}

ObservableCollectionクラスは、System.Collections.ObjectModel名前空間にあるクラスです。
このクラスはコレクションに対する挿入・削除・設定を行った場合にそれらの変更をクラス外部にイベントとして通知することができるようになってます。ObservableCollectionクラスは、ユーザーインターフェイスとコレクションを同期したい場合などに使用されます。

さて、4人分のデータを作成し、MainPageへバインドすることができました。

今度は、MainPage.xaml を開いて ComboBoxを1つ貼り付け、XAMLのコード(ComboBox部分)を以下のようにします。

MainPage.xaml(ComboBox部分のみ抜粋)

<ComboBox HorizontalAlignment="Left" 
          Margin="10,10,0,0" VerticalAlignment="Top" 
          Width="120"
          ItemsSource="{Binding}" SelectedValuePath="Id" 
          DisplayMemberPath="Name" />

上記のXAMLでは

  • ItemSource 部分でデータをバインディング
  • SelectedValuePath 部分でバインドされているデータのId項目をセット
  • DisplayMemberPath 部分でバインドされているデータのName項目をセット

ということを行っています。

SelectedValuePathが内部値を、DisplayMemberPathが表示値を管理するプロパティです。

ここまでできたら実行して見ます。

実行結果は以下の通りで、ComboBoxには4人分のデータがセットされていることを確認できます。

実行例

ComboBoxにデータを表示する手順がわかりました。

最後に、ComboBox で表示した名前に対する内部値が正しく選択されているかを確認してみます。

MainPage.xamlを開き、 CobmoBoxに「cmbName」という名前を付けます。次に ComboBoxの下に TextBlockコントロールを1つ貼り付け XAMLコードを以下のように修正します。

<ComboBox x:Name="cmbName" HorizontalAlignment="Left" 
           Margin="10,10,0,0" VerticalAlignment="Top" 
           Width="120"
           ItemsSource="{Binding}" SelectedValuePath="Id" 
           DisplayMemberPath="Name" />
<TextBlock HorizontalAlignment="Left" Margin="10,35,0,0" 
           TextWrapping="Wrap"  VerticalAlignment="Top"
           Text="{Binding ElementName=cmbName, 
           Path=SelectedItem.Id, Mode=OneWay}"/>

ここで注意して見ていただきたいのは TextBlockの Textプロパティ部分です。

まず、ElementNameにcmbNameを指定し、ComboBoxをデータソースとします。 Pathには SelectedItem.Idを指定し、ComboBoxで選択されている項目のIdが表示されるようにしています。

実行結果は以下の通りです。

表示されている Billyは Id = 3 なので、正しく動作していることを確認できます。

実行結果2

 

[Silverlight][バインディング] コマンドバインディングの基礎

今回は、ICommandインターフェースを使用して、ビューとビューモデル間のコマンド通知の方法について見ていきます。


従来のWindowsアプリケーションでは、ビュー側(画面)の状態の通知はイベントによって行われています(例:ボタンをクリックしたらクリックイベントが発生する)。

Silverlight でもイベントによる状態の通知は行うことができるので、ボタンがクリックされたらクリックイベントを発生させてその中でデータ処理を行うことは可能です。

MVVMモデルの場合は、モデル、ビュー、ビューモデルに分離して考え、状態の通知はイベントではなくコマンドをバインディングするという手法で行うのが一般的です(イベントを使用することも当然可能です)。

モデル、ビューモデル間のバインディングの関係を図に表すと以下のようになります。

モデル、ビューモデル間のバインディング

INotifyPropertyChangedインターデースを使用したデータのバインドに関してはクラスのインスタンスをバインドする3 ~データを更新する~ の記事を参照してください。


サンプルプロジェクトを作成する

今回作成するサンプルプログラムは、「ボタンがクリックされたら挨拶を表示する」というものです。

「ボタンがクリックされたら」という部分をコマンドのバインディングによって行います。

最初に今回のサンプルプログラムの実行例を以下に示します。

サンプルプログラムの実行例

Visual Studioを起動し、Silverlightアプリケーションを作成します。

プロジェクト名は、C#はCommandSampleCS,、VB.NETはCommandSampleVBとします。


 画面のデザイン

MainPage.xaml のデザインは以下のようにします。

XAMのデザイン

XAMLのコード

<Grid x:Name="LayoutRoot" Background="White">
    <TextBox HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Text="{Binding Name, Mode=TwoWay}"/>
    <Button Content="挨拶する" HorizontalAlignment="Left" Margin="10,38,0,0" VerticalAlignment="Top" Width="75""/>
</Grid>

まずはデータバインディング

最初にビューモデルを作成します。

クラス名を ViewModel とし、以下のようにコードを記述します。

VB.NET

Imports System.ComponentModel
Public Class ViewModel
    Implements INotifyPropertyChanged

    Private _name As String
    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            '値に変更があったかどうか
            If _name <> value Then
                _name = value

                '値に変更があったので、変更のあったプロパティ名を通知
                RaisePropertyChanged("Name")
            End If

        End Set
    End Property

    ''' <summary>
    ''' プロパティ変更用イベント
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged

    ''' <summary>
    ''' プロパティ変更通知
    ''' </summary>
    ''' <param name="propertyName"></param>
    ''' <remarks></remarks>
    Private Sub RaisePropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

End Class

C#

using System.Windows;
using System.Windows.Input;
using System.ComponentModel;

namespace CommandSampleCS
{
    public class ViewModel : INotifyPropertyChanged
    {

        private string _name;

        /// <summary>
        /// Nameプロパティ
        /// XAMLページの TextBox へバインドする値
        /// </summary>
        public string Name
        {
            get { return _name; }
            set
            {
                // 値に変更があったかどうか
                if (_name != value)
                {
                    _name = value;

                    // 値に変更があったので、変更のあったプロパティ名を通知
                    RaisePropertyChanged("Name");
                }
            }
        }

        /// <summary>
        /// プロパティ変更通知用イベント
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// プロパティ変更通知処理
        /// </summary>
        /// <param name="propertyName"></param>
        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

ViewModel クラスは INotifyPropertyChangedインターフェースを実装します

クラスには以下を実装しています。

  • Nameプロパティ
    XAMLページに配置した TextBoxのTextプロパティ用
  • PropertyChangedイベント
    プロパティの変更があった場合に通知を行います。このクラスではNameプロパティの編個通知用として使用します。
  • RaisePropertyChangedメソッド
    Nameプロパティに変更があった場合にこのメソッドを呼び出し、PropertyChangedイベントを実行します。

以上ができたら、MainPage の DataContext にバンディングします。

VB.NET(MainPage.xaml.vb)

Partial Public Class MainPage
    Inherits UserControl

    Public Sub New()
        InitializeComponent()

        Me.DataContext = New ViewModel()
    End Sub

End Class

C#(MainPage.xaml.cs)

using System.Windows.Controls;

namespace CommandSampleCS
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();

            this.DataContext = new ViewModel();
        }
    }
}

 コマンドバインディング

ビューからビューモデルにコマンドを通知するために、ICommandインターフェースを実装した DelegateCommandという名前のクラスを作成します。

VB.NET(DelegateCommand.vb)

Public Class DelegateCommand
    Implements ICommand

    Public Property ExecuteHandler As Action(Of Object)
    Public Property CanExecuteHandler As Func(Of Object, Boolean)

    Public Event CanExecuteChanged(sender As Object, e As EventArgs) Implements ICommand.CanExecuteChanged

    ''' <summary>
    ''' 実行できるかどうかの判定
    ''' </summary>
    ''' <param name="parameter"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Dim d = CanExecuteHandler

        ' d(parameter) は GreetingCommandExecuteメソッドの実行
        ' GreetingCommandCanExecuteメソッドは TextBoxが空の場合は falseを返してくる
        Return IIf(IsNothing(d), True, d(parameter))
    End Function


    ''' <summary>
    ''' コマンドの実行
    ''' </summary>
    ''' <param name="parameter"></param>
    ''' <remarks></remarks>
    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        Dim d = ExecuteHandler
        If Not IsNothing(d) Then
            '  ViewModel の GreetingCommandExecute を実行
            d(parameter)
        End If
    End Sub

    ''' <summary>
    ''' 実行確認メソッドの起動
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub RaiseCanExecuteChanged()
        ' CanExecuteメソッドの起動
        RaiseEvent CanExecuteChanged(Me, Nothing)
    End Sub
End Class

C#(DelegateCommand.cs)

using System;
using System.Windows.Input;

namespace CommandSampleCS
{
    public class DelegateCommand : ICommand
    {
        public Action<object> ExecuteHandler { get; set; }
        public Func<object, bool> CanExecuteHandler { get; set; }

        public event EventHandler CanExecuteChanged;

        /// <summary>
        /// 実行できるかどうかの判定
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        public bool CanExecute(object parameter)
        {
            var d = CanExecuteHandler;

            // d(parameter) は GreetingCommandExecuteメソッドの実行
            // GreetingCommandCanExecuteメソッドは TextBoxが空の場合は falseを返してくる
            return d == null ? true : d(parameter);
        }

        /// <summary>
        /// コマンドの実行
        /// </summary>
        /// <param name="parameter"></param>
        public void Execute(object parameter)
        {
            var d = ExecuteHandler;
            if (d != null)
                // ViewModel の GreetingCommandExecute を実行
                d(parameter);
        }

        /// <summary>
        /// 実行確認メソッドの起動
        /// </summary>
        public void RaiseCanExecuteChanged()
        {
            // CanExecuteメソッドの起動
            CanExecuteChanged(this, null);
        }
    }
}

DelegateCommandクラスでは、コマンドを実行できるかどうかの判定と、コマンドの実行を担当するクラスです。

詳しい解説は後回しにして、今作成した DelegateCommand クラスを ViewModel で使用するよう改造をします。


ViewModelクラスの改造

DelegateCommandクラスができたので、最初に作成したViewModelクラスを改造します。

今回のサンプルプログラムでは、

  • テキストボックスに1文字も入力されていない場合はボタンをクリックできないようにする
  • ボタンがクリックされたときにメッセージボックスを表示する

という機能を持たせることとします。

ソースコードは以下の通りです。

VB.NET(ViewModel.vb 改造後完全ソース)

Imports System.ComponentModel
Public Class ViewModel
    Implements INotifyPropertyChanged

    Private _name As String
    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            '値に変更があったかどうか
            If _name <> value Then
                _name = value

                '値に変更があったので、変更のあったプロパティ名を通知
                RaisePropertyChanged("Name")

                'コマンドを実行できるかどうかをチェックする
                CType(GreetingCommand, DelegateCommand).RaiseCanExecuteChanged()
            End If

        End Set
    End Property

    ''' <summary>
    ''' プロパティ変更用イベント
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged

    ''' <summary>
    ''' プロパティ変更通知
    ''' </summary>
    ''' <param name="propertyName"></param>
    ''' <remarks></remarks>
    Private Sub RaisePropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub


    ''' <summary>
    ''' コマンドを実行
    ''' ボタンがクリックされたときに実行する
    ''' </summary>
    ''' <param name="parameter"></param>
    ''' <remarks></remarks>
    Private Sub GreetingCommandExecute(parameter As Object)
        MessageBox.Show("こんにちは" + _name + "さん")
    End Sub

    Private Function GreetingCommandCanExecute(parameter As Object) As Boolean
        '_nameプロパティが 「null以外」または「空以外」であれば true を返し実行可能とする
        Return Not String.IsNullOrEmpty(_name)
    End Function

    Private _greetingCommand As ICommand

    ''' <summary>
    ''' 挨拶実行コマンドプロパティ
    ''' 挨拶を実行するコマンド(GreetingCommandExecute)と
    ''' コマンドを実行できるかどうかのチェック(GreetingCommandCanExecute)
    ''' を_greetingCommandにセット
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property GreetingCommand() As ICommand
        Get
            If _greetingCommand Is Nothing Then
                _greetingCommand = New DelegateCommand() With {
                    .ExecuteHandler = AddressOf GreetingCommandExecute,
                    .CanExecuteHandler = AddressOf GreetingCommandCanExecute
                }
            End If

            Return _greetingCommand
        End Get
    End Property

End Class

C#(ViewModel.vb 改造後完全ソース)

using System.Windows;
using System.Windows.Input;
using System.ComponentModel;

namespace CommandSampleCS
{
    public class ViewModel : INotifyPropertyChanged
    {

        private string _name;

        /// <summary>
        /// Nameプロパティ
        /// XAMLページの TextBox へバインドする値
        /// </summary>
        public string Name
        {
            get { return _name; }
            set
            {
                // 値に変更があったかどうか
                if (_name != value)
                {
                    _name = value;

                    // 値に変更があったので、変更のあったプロパティ名を通知
                    RaisePropertyChanged("Name");

                    // コマンドを実行できるかどうかをチェックする
                    ((DelegateCommand)GreetingCommand).RaiseCanExecuteChanged();
                }
            }
        }

        /// <summary>
        /// プロパティ変更通知用イベント
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// プロパティ変更通知処理
        /// </summary>
        /// <param name="propertyName"></param>
        protected void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// コマンドを実行
        /// ボタンがクリックされたときに実行する
        /// </summary>
        /// <param name="parameter"></param>
        private void GreetingCommandExecute(object parameter)
        {
            MessageBox.Show("こんにちは" + _name + "さん");
        }

        /// <summary>
        /// コマンドを実行できるかどうかをチェック
        /// </summary>
        /// <param name="parameter"></param>
        /// <returns></returns>
        private bool GreetingCommandCanExecute(object parameter)
        {
            // _nameプロパティが 「null以外」または「空以外」であれば true を返し実行可能とする
            return !string.IsNullOrEmpty(_name);
        }

        private ICommand _greetingCommand;

        /// <summary>
        /// 挨拶実行コマンドプロパティ
        /// 挨拶を実行するコマンド(GreetingCommandExecute)と
        /// コマンドを実行できるかどうかのチェック(GreetingCommandCanExecute)
        /// を_greetingCommandにセット
        /// </summary>
        public ICommand GreetingCommand
        {
            get
            {
                if (_greetingCommand == null)
                    _greetingCommand = new DelegateCommand
                {
                    ExecuteHandler = GreetingCommandExecute,
                    CanExecuteHandler = GreetingCommandCanExecute,
                };

                return _greetingCommand;
            }
        }
    }
}

DelegateCommandクラスの解説

まずは DelegateCommandクラスから見ていきます。

DelegateCommandクラスは、コマンドを実行できるかどうかを判断し、コマンドを実行するという機能を持っています。

CanExecuteメソッドは、コマンドを実行できるかどうかを True/Falseで返します。return 文にある d(parameter)は ViewModelクラスの GreetingCommandCanExecuteメソッドを呼び出してします。これにより TextBoxが空であれば Falseを返し、空以外の場合は True を返します。MainPageに貼り付けられているButtonコントロールには、コマンドが実行できないが伝わるとクリックできない状態に自動で変化します。

Executeメソッドは、コマンドを実行します。ここでいうコマンドとは ViewModelクラスにある GreetingCommandメソッドです。よってメッセージボックスが表示され、挨拶を表示します。

RaiseCanExecuteChanged メソッドは CanExecuteChnangedイベントを発行し、CanExecuteメソッドキックします。


改造後のViewModelクラスの解説

今度は改造後の ViewModelクラスについて見ていきます。

Nameプロパティ では、Nameプロパティに変更があった場合に(要するにTextBoxの入力に変更があった場合)に、コマンドが実行可能かをチェックするよう、DelegateCommandクラスのRaiseCanExecuteChangedを呼び出す処理を加えています(VB.NETは19行目、C#は30行目)。

GreetingCommandExecuteメソッド(VB.NETは49行目、C#は54行目)を新たに追加しています。このメソッドは、コマンドが実行可能な場合に呼び出され、メッセージボックスに挨拶を表示します。

GreetingCommandCanExecuteメソッド(VB.NETは53行目、C#は64行目)を新たに追加しています。このメソッドは、Nameプロパティ(要するにTextBox)が空かどうかを判断し、空の場合には Trueを返します。

GreetingCommandメソッド(VB.NETは69行目、C#は78行目)を新たに追加しています。このメソッドでは プライベート変数 \greetingCommandに DelegateCommandクラスのインス端をセットしています。ExecuteHandlerには、ViewModelクラスの GreetingCommandExecuteメソッドをセットし、 CanExecuteHandlerには、同じくViewModlクラスの GreetingCOmmangCanExecuteメソッドをセットしています。


XAMLページのボタンにコマンドをバインドする

最後に、ここまでに作成したコマンドを XAMLページに配置したボタンにバインドします。

XAMLのコードは以下の通りです。

<Grid x:Name="LayoutRoot" Background="White">
    <TextBox HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    <Button Content="挨拶する" HorizontalAlignment="Left" Margin="10,38,0,0" VerticalAlignment="Top" Width="75" Command="{Binding GreetingCommand}"/>
</Grid>

まず Button の XAMLから見ていきます。Command属性に GreetingCommandをバインディングしています。たったこれだけです。クリックイベントは必要ないことがわかります。

そして TextBoxですが、TextプロパティのBindingのところが一部変更になっています。UpdateSourceTrigerプロパティにPropertyChangedをセットしています。これはバインディングソースの更新タイミングを指示するものです。PropertyChangedとしているので、Textプロパティが変更されたときに更新されるようになります。

 

だいぶ長い記事となってしまいましたが、MVVMではこの基礎を踏まえてコマンドを作成します。

 

[Silverlight][バインディング] クラスのインスタンスをバインドする3 ~データを更新する~

前回と前々回の記事で、クラスのインスタンスをバインドする方法について説明しました。
前回および前々回は、インスタンスにセットされたデータを表示するだけでしたが、今回はインスタンスのデータを変更し表示に反映されるようにしてみます。
また、今回使用する技術は INotifyPropertyChagedインターフェースです。

まずは、前々回の記事を参照して Silverlightアプリケーションプロジェクトを作成します。
次に MainPage.xaml に以下のようにボタンを追加します。

MainPage.xamlのデザイン
Buttonコントロールの Contentプロパティは「変更」にしておきます。

次に、貼り付けた Button コントロールをダブルクリックして以下のコードを入力します。

VB.NET

Partial Public Class MainPage
    Inherits UserControl

    Private _health As New Health()


    Public Sub New()
        InitializeComponent()

        _health.Height = 162.5
        _health.Weight = 60.5

        Me.DataContext = _health
    End Sub

    ''' <summary>
    ''' ボタンクリック時の処理
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
        _health.Height = 170.5
        _health.Weight = 55.5
    End Sub
End Class

C#

public partial class MainPage : UserControl
{
    private Health _health;

    public MainPage()
    {
        InitializeComponent();

        _health = new Health
        {
            Height = 162.5,
            Weight = 60.5
        };

        this.DataContext = _health;
    }

    /// <summary>
    /// ボタンクリック時の処理
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        _health.Height = 170.5;
        _health.Weight = 55.5;
    }
}

上記を入力したら、実行してみましょう。
あれ?ボタンをクリックしても画面に表示されている値が更新されません。
データが更新されないのは、MainPageに表示されている TextBlockコントロールが フィールドの値が変更をされていることを知らないためです。
この問題を解決するには、Healthクラスに INotifyPropertyChangedインターフェースを実装する必要があります。
以下に INotyfyPropertyChangedインターフェースを実装した Healthクラスのコードを示します。

VB.NET

'INotifyPropertyChangedを実装するために以下を追記します
Imports System.ComponentModel

Public Class Health
    Implements INotifyPropertyChanged

    Private _height As Double
    Private _weight As Double

    ''' <summary>
    ''' 身長用プロパティ
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Height() As Double
        Get
            Return _height
        End Get
        Set(ByVal value As Double)
            _height = value

            'Heightプロパティを変更した時に、PropertyChangedイベントをキックします
            NotifyPropertyChanged("Height")
        End Set
    End Property

    ''' <summary>
    ''' 体重用プロパティ
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Weight() As Double
        Get
            Return _weight
        End Get
        Set(ByVal value As Double)
            _weight = value

            'Weightプロパティを変更した時に、PropertyChangedイベントをキックします
            NotifyPropertyChanged("Weight")
        End Set
    End Property

    '以下のイベントを追加
    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged

    ''' <summary>
    ''' 上記のイベントをキックするために、下記のNotifyPropertyChangedメソッドを追加
    ''' </summary>
    ''' <param name="p"></param>
    ''' <remarks></remarks>
    Private Sub NotifyPropertyChanged(p As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(p))
    End Sub

End Class

C#

// INotifyPropertyChangedを実装するために以下を追記します
using System.ComponentModel;

namespace SilverlightApplication1
{
    public class Health : INotifyPropertyChanged
    {
        private double _height;

        private double _weight;

        /// <summary>
        /// 身長用プロパティ
        /// </summary>
        public double Height 
        { 
            get
            {
                return _height;
            }
            set
            {
                _height = value;

                // Heightプロパティを変更した時に、PropertyChangedイベントをキックします
                NotifyPropertyChanged("Height");
            }
        }

        /// <summary>
        /// 体重用プロパティ
        /// </summary>
        public double Weight 
        { 
            get
            {
                return _weight;
            }
            set
            {
                _weight = value;

                // Weightプロパティを変更した時に、PropertyChangedイベントをキックします
                NotifyPropertyChanged("Weight");
            }
        }

        // 以下のイベントを追加
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// 上記のイベントをキックするために、下記のNotifyPropertyChangedメソッドを追加
        /// </summary>
        /// <param name="p"></param>
        private void NotifyPropertyChanged(string p)
        {
            if (PropertyChanged  != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(p));
            }
        }
    }
}

INotyfyPropertyChangedインターフェースを実装する場合は以下に注意しましょう。

  • プロパティ値を変更したら(プロパティのSet部) NotifyPropertyChangedを呼ぶ
  • PropertyChangedEventHandlerを追加する

コードの入力が終わったら、実行して[変更]ボタンをクリックしてみましょう。
以下のように、表示値が変更されることを確認してください。
実行結果

[Silverlight][バインディング] クラスのインスタンスをバインドする2 ~複数のDataContextを使用する~

ああ前回は MainPageのDataContextにクラスのインスタンスをバインドしました。

MainPageのDataContextにバインドをすると、データソースは1つしか使用することができません。
今回は、複数のデータソースをバインドする方法について見ていきます。

まずは Visual Studioを起動して、Silverlightアプリケーションプロジェクトを作成します。
次に、MainPageを以下のようにデザインします。

MainPage.xamlの例

上記の XAMLコード例を以下に示します。

<Grid x:Name="LayoutRoot" Background="White">
    
    <Grid HorizontalAlignment="Left" Height="59" Margin="10,10,0,0" VerticalAlignment="Top" Width="225" Background="#FFE6F015">
        <TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="身長" VerticalAlignment="Top"/>
        <TextBlock HorizontalAlignment="Left" Margin="67,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top"
                   Text="{Binding Height}"/>
        <TextBlock HorizontalAlignment="Left" Margin="10,31,0,0" TextWrapping="Wrap" Text="体重" VerticalAlignment="Top"/>
        <TextBlock HorizontalAlignment="Left" Margin="67,31,0,0" TextWrapping="Wrap" VerticalAlignment="Top"
                   Text="{Binding Weight}"/>
    </Grid>
    <Grid HorizontalAlignment="Left" Height="33" Margin="10,90,0,0" VerticalAlignment="Top" Width="225" Background="#FFE6F015">
        <TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="性別" VerticalAlignment="Top"/>
        <TextBlock HorizontalAlignment="Left" Margin="67,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top"
                   Text="{Binding gender}"/>
    </Grid>
    
</Grid>

今回は上記 MainPageに配置した2つのGridに DataContextを設定します。
DataContextに設定するクラス、HealthとGenderを最初に作成します。

Health.vb

Public Class Health
    Public Property Height As Double
    Public Property Weight As Double
End Class

Health.cs

public class Health
{
    public double Height { get; set; }
    public double Weight { get; set; }
}

Gender.vb

Public Class Gender
    Public Property gender As String
End Class

Gender.cs

public class Gender
{
    public string gender { get; set; }
}

次に、2つのGrid(gridHeath と gridGender)のDataContextを設定します。
これにより、同じMainPage内でも複数のデータソースを取り扱うことが可能となります。

MainPage.xaml.vb の例

Partial Public Class MainPage
    Inherits UserControl

    Public Sub New()
        InitializeComponent()

        Me.gridHealth.DataContext = New Health() With {
            .Height = 162.5,
            .Weight = 60.5
            }

        Me.gridGender.DataContext = New Gender() With {
            .Gender = "男"
            }
    End Sub

End Class

MainPage.xaml.cs の例

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();

        this.gridHealth.DataContext = new Health
        {
            Height = 162.5,
            Weight = 60.5
        };

        this.gridGender.DataContext = new Gender
        {
            gender = "男"
        };
    }
}

以上を入力したら、実行して確認しましょう。
2つのデータソースの値がそれぞれ表示されることが確認できます。
実行結果