Livetを使ったC#のWPFプログラミング

システムとして機能が網羅されたサンプルをもとに、Livetを使ったC#のWPFシステムの開発技能を身に付ける為の学習サイト


4.サンプル


ObservableCollectionを使ったサンプル

モデルとObservableCollectionの関係をDataGridを使っての解説なので、それ以外の機能は極力省いています。


下のテキストボックスに、No、名前、就任日、退任日を入力して、「追加」ボタンをクリックしますと、ObservableCollectionにデータが追加されて、これとバインディングしているDataGridに追加されという仕組みです。


PropertyChanged

Noを入力するまでは、「追加」ボタンは非使用になっています。入力されると使用可能となります。これは、NoテキストボックスにUpdateSourceTrigger=PropertyChangedという記述をして実現しています。


「追加」ボタンのクリック後

このサンプルのZAMLは下記の通ります。DataGridとデータとの結びつけが、「ItemsSource="{Binding personList~"」の ところです。データの追加は、「IsReadOnly="false"」の記述で可能となっています。

View
<Window x:Class="LivetWPFDataGrid.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:LivetWPFDataGrid.Views"
        xmlns:vm="clr-namespace:LivetWPFDataGrid.ViewModels"
        Title="MainWindow" Height="350" Width="525"
        WindowStartupLocation="CenterScreen">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/> 
    </Window.DataContext>
    
    <i:Interaction.Triggers>

        <!--WindowのContentRenderedイベントのタイミングでViewModelのInitialize
        					メソッドが呼ばれます-->
        <i:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodTarget="{Binding}" 
            						MethodName="Initialize"/>
        </i:EventTrigger>

        <!--Windowが閉じたタイミングでViewModelのDisposeメソッドが呼ばれます-->
        <i:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction/>
        </i:EventTrigger>

    </i:Interaction.Triggers>
    
    <Grid>

        <DataGrid Name="dataGrid" ItemsSource="{Binding personList, 
        					Mode=OneWay}" IsReadOnly="false"
                  AutoGenerateColumns="False" Margin="9,12,12,74">
            <DataGrid.Columns>
                <DataGridTextColumn Header="No" Width="40"
                                    Binding="{Binding No, StringFormat=D2}" />
                <DataGridTextColumn Header="名前" Width="80"
                                    Binding="{Binding Name}" />
                <DataGridTextColumn Header="就任日" Width="70"
                                    Binding="{Binding InaugurationDay, 
                                    		StringFormat=yyyy/MM/dd}" />
                <DataGridTextColumn Header="退任日" Width="70"
                                    Binding="{Binding RetiringDay, 
                                    		StringFormat=yyyy/MM/dd}" />
            </DataGrid.Columns>
        </DataGrid>

        <Button Content="追加" Command="{Binding InsertData}" 
                Height="23" HorizontalAlignment="Left" Margin="416,276,0,0" 
                					Name="button1" 
                VerticalAlignment="Top" Width="75"  />

        <TextBox Text="{Binding Path=txtNo, Mode=TwoWay}" Height="18" 
                 HorizontalAlignment="Left" Margin="9,243,0,0" Name="textBox1" 
                 VerticalAlignment="Top" Width="82" />
        <TextBox Text="{Binding Path=txtName, Mode=TwoWay}" Height="18" 
                 HorizontalAlignment="Left" Margin="97,243,0,0" Name="textBox2" 
                 VerticalAlignment="Top" Width="90" />
        <TextBox Text="{Binding Path=txtInaugurationDay, Mode=TwoWay}" Height="18" 
                 HorizontalAlignment="Left" Margin="193,243,0,0" Name="textBox3" 
                 VerticalAlignment="Top" Width="104" />
        <TextBox Text="{Binding Path=txtRetiringDay, Mode=TwoWay}" Height="18" 
                 HorizontalAlignment="Left" Margin="303,243,0,0" Name="textBox4" 
                 VerticalAlignment="Top" Width="104" />
    </Grid>
</Window>


ViewModelであるMainWindowViewModel.csは下記です。
7~13行は、Livetのライブラリの宣言です。16行目は、ObservableCollectionを使用する為の宣言です。 Livetを使用するとプログラマーはICommandの宣言は不要です。
ICommandとは、WPFが提供するコマンド用のインターフェイスです。
24~37行はtxtNoの変更通知プロパティの宣言ですが、 txtNoは入力値が変わったら、PropertyChangedイベントを発して、合わせて、34行目でRaiseCanExecuteChangedを発しています。
39行目は、txtNameの変更通知プロパティの宣言
40行目は、txtInaugurationDay(就任日)の変更通知プロパティの宣言
41行目は、txtRetiringDay(退任日)の変更通知プロパティの宣言
で、2.WPFの「One Point Lesson 【プロパティの自動実装】」で解説した「プロパティの自動実装」で記述しています。
48~58行でモデルPersonのプロパティを設定しています。
62~73行でPersonのObservableCollectionの設定をしています。

コンストラクターの82~92行で、new を使って PersonのObservableCollectionであるPersonsの
インスタンスを生成して、値を代入しています。

93~94行で、new を使ってViewModelCommandのインスタンスを生成います。
引数のExecuteInsertDataCommand, CanExecuteInsertDataCommand
は、下で記述しています。


ViewModel
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

using Livet;
using Livet.Commands;
using Livet.Messaging;
using Livet.Messaging.IO;
using Livet.EventListeners;
using Livet.Messaging.Windows;
using LivetWPFDataGrid.Models;

//ObservableCollection
using System.Collections.ObjectModel;

namespace LivetWPFDataGrid.ViewModels
{
    public class MainWindowViewModel : ViewModel
    {
        #region 変更通知プロパティ
        //-----------------------------------------------
        private string _txtNo;
        public string txtNo
        {
            get { return this._txtNo; }
            set
            {
                if (this._txtNo != value)
                {
                    this._txtNo = value;
                    RaisePropertyChanged("txtNo");
                    this.InsertDataCommand.RaiseCanExecuteChanged(); 
                }
            }
        }
        //-----------------------------------------------
        public string txtName { get; set; }
        public string txtInaugurationDay { get; set; }
        public string txtRetiringDay { get; set; }
        //-----------------------------------------------
        #endregion

        #region Person変更通知プロパティ
        private Person _Person;

        public Person Person
        {
            get { return _Person; }
            set
            {
                if (_Person == value)
                    return;
                _Person = value;
                RaisePropertyChanged("Person");
            }
        }
        #endregion

        #region Persons変更通知プロパティ
        private ObservableCollection<Person> _Persons;
        public ObservableCollection<Person> Persons
        {
            get { return _Persons; }
            set
            {
                if (_Persons == value)
                    return;
                _Persons = value;
                RaisePropertyChanged("Persons");
            }
        }
        #endregion

        public void Initialize()
        {
        }
       
        public MainWindowViewModel()
        {
            Persons = new ObservableCollection<Person> {
                new Person { No = 1, Name = "吉田茂", InaugurationDay 
                	= new DateTime(1948, 10, 15), 
                    RetiringDay = new DateTime(1954, 12, 10) },
                new Person { No = 2, Name = "鳩山一郎", InaugurationDay 
                	= new DateTime(1954, 12, 10),
                    RetiringDay = new DateTime(1956, 12, 23) },
                new Person { No = 3, Name = "石橋湛山", InaugurationDay 
                	= new DateTime(1956, 12, 23),
                RetiringDay = new DateTime(1957, 02, 25) },
            };
            this.InsertDataCommand = new ViewModelCommand(ExecuteInsertDataCommand,
            	 CanExecuteInsertDataCommand); 
        }
        public ViewModelCommand InsertDataCommand { private set; get; } 
        private void ExecuteInsertDataCommand()
        {
            string strSTR = txtNo;
            txtNo = strSTR;
 
            //DoSomething(this.Text);
            string str = txtInaugurationDay.Replace("/", "");
            string strYear = str.Substring(0, 4);
            string strMounth = str.Substring(4, 2);
            string strDay = str.Substring(6, 2);
            //
            string str2 = txtRetiringDay.Replace("/", "");
            string strYear2 = str.Substring(0, 4);
            string strMounth2 = str.Substring(4, 2);
            string strDay2 = str.Substring(6, 2);

            Persons.Add(new Person
            {
                No = Convert.ToInt32(txtNo),
                Name = txtName,
                InaugurationDay = new DateTime(Convert.ToInt32(strYear), 
                	Convert.ToInt32(strMounth),
                                    Convert.ToInt32(strDay)),
                RetiringDay = new DateTime(Convert.ToInt32(strYear2), 
                	Convert.ToInt32(strMounth2),
                                    Convert.ToInt32(strDay2))
            });
        }
        private bool CanExecuteInsertDataCommand()
        {
            return !String.IsNullOrEmpty(this.txtNo);
        } 
    }
}

 
 

モデルPerson.csは下記です。


Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LivetWPFDataGrid.Models
{
    //classにpublicをつけないとアクセシビリティに一貫性がありませんのエラーになる。
    public class Person
    {
        public int No { get; set; }
        public string Name { get; set; }
        public DateTime InaugurationDay { get; set; }
        public DateTime RetiringDay { get; set; }
    }
}


Livetでは、RaiseCanExecuteChanged を呼び出すとNullReferenceException が発生しますので、その対策として、App.zaml.cs(App.zamlのコードビハインド)に背景色を付けた分を自分で追加します。


App
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;

using Livet;

namespace LivetWPFDataGrid
{
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            DispatcherHelper.UIDispatcher = Dispatcher;
        }
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            Livet.DispatcherHelper.UIDispatcher = this.Dispatcher;
        } 
    }
}
ここのサンプルのダウンロード(LivetWPFDataGrid.lzh)

One Point Lesson 【ButtonのいろいろなCommandの書き方】
下記の2つはZAMLは異なりますが同じ動きとなります。

View
<Button Command="{Binding CopyCommand}" Content="Commands" Margin="37,120,264,192" />
<Button Content="Event Trigger Commands" Margin="37,168,264,139"> <i:Interaction.Triggers>
    <i:EventTrigger EventName="Click"> <i:InvokeCommandAction Command="{Binding
    CopyCommand}"/> </i:EventTrigger> </i:Interaction.Triggers>
</Button>

ViewModel
#region 変更通知プロパティ
//-----------------------------------------------
private string _txtState;
{
  get { return _txtState; }
  set
  {
    if (_txtState != value)
    {
      _txtState = value;
      RaisePropertyChanged("txtState");
    }
  }
}
//
private string _txtMessage;
public string txtMessage
{
  get { return _txtMessage; }
  set
  {
    if (_txtMessage != value)
    {
      _txtMessage = value;
      RaisePropertyChanged("txtMessage");
    }
  }
}
//-----------------------------------------------
#endregion

public MainWindowViewModel()
{
  txtState = "対応中";
}

#region CopyCommand1
//----------------------------------
private ViewModelCommand _copyCommand;
public ViewModelCommand CopyCommand
{
  get
  {
    if (this._copyCommand == null)
    {
      this._copyCommand = new ViewModelCommand(CopyExecute, CanCopyExecute);
    }
    return this._copyCommand;
  }
}

private void CopyExecute()
{
  txtMessage = txtState;
}

private bool CanCopyExecute()
{
}
//----------------------------------
#endregion