2014年09月16日

株価一覧表示 WPFで作るサンプルプログラム MVVM編

作成するプログラム

StockListImage.png
上図のようなものを作ります。
前回は普通にイベントドリブンで作りました。
XAMLに画面構成があり、コードビハインドでボタンクリックのハンドラを書いた1クラスから成ってました。
これをMVVMパターンで書いてみます。沢山のクラスが必要になります。
フレームとか共通化を考慮した継承関係とか考慮してない、素に近い実装です。

MVVMとは

MVVMとはM-V-VMModel,View,ViewModelから成る構成パターンを言います。
それぞれの責務については、いろんなところの解説を読んでも差異があるのですが、以下のような感じです。

ViewとはUIのことでWindow,Form,Page,UserCotrolなどが該当する。
ViewModelとはUIロジックを責務とし、ViewとModelとの橋渡し。
Modelとはビジネスロジックを担います。エンティティのことを指す場合と、ドメインを指す場合とがあるよう(これが混乱の元?)。Wikiでは「全部の機能からViewとViewModelとを除いた全部」とありました。
View→ViewModel→Modelという関係。つまり、Modelは何もしらない。ViewModelはModelは知っているけどViewは知らない。ViewはViewModelを知っている。

Modelの実装

データを取得するクラスになります。
using System;
using System.Net;
using System.Text;

namespace Stocks.Model
{
    public class StocksRepository
    {
        public string[] GetCSVData()
        {
            var client = new WebClient();

            byte[] buffer = client.DownloadData("http://k-db.com/?p=all&download=csv");

            string str = Encoding.Default.GetString(buffer);

            return str.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

        }
    }
}

以下のようなデータが取得されます。

2014-09-12,全銘柄日足,http://k-db.com/
コード,銘柄名,市場,業種,始値,高値,安値,終値,出来高,売買代金
I101,日経平均株価,東証,指数,15885.04,15984.90,15885.04,15948.29,-,-
I102,TOPIX,東証,指数,1311.09,1315.92,1309.09,1313.72,-,-
I103,JPX日経インデックス400,東証,指数,11879.51,11922.38,11861.67,11905.53,-,-
I104,東証2部株価指数,東証,指数,4142.45,4144.77,4128.22,4140.28,-,-
I105,東証マザーズ指数,東証,指数,956.64,965.05,949.90,952.11,-,-
I111,TOPIX コア 30,東証,指数,676.02,679.71,675.16,678.45,-,-


1行目がタイトル、2行目がヘッダ、3行目以下がデータになっています。
改行コードで分けて一行分のデータを1つの文字列としてその配列として返却しています。

この1行分に相当するモデルを用意します。
namespace Stocks.Model
{
    public class StockEntity 
    {
        public string Code { get; set; }
        public string Name { get; set; }
        public string Market { get; set; }
        public string Category { get; set; }
        public string Opening { get; set; }
        public string High { get; set; }
        public string Low { get; set; }
        public string Closing { get; set; }
        public string Volume { get; set; }
        public string Turnover { get; set; }
    }
}

INotifyPropertyChangedを継承(実現)するのが常道らしいですが今回はなしで

ViewModelの実装

イベントハンドラに相当する部分はICommandインタフェースを実現(継承)した1つのクラスとして定義します
今回は、実際の処理はコンストラクタの引数で指定されるデリゲートを実行する仕組みです。
    public class DownloadCommand : ICommand
    {
        public bool CanExecute(object parameter){return true;}

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            _action();
        }

        private Action _action;
        public DownloadCommand(Action action)
        {
            _action = action;
        }
    }

ViewModelはINotifyPropertyChangedを継承しViewからバインドされる項目をプロパティとして公開していきます。
今回はタイトル用の文字列とボタン押下時のコマンドと取得結果のデータリストを公開します。
using Stocks.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;

namespace Stocks.VM
{
    public class ViewModel : INotifyPropertyChanged
    {
        
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string name)
        {
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
        #endregion

        private string _title;
        private ObservableCollection<StockEntity> _stockList;
        private DownloadCommand _downloadCommand;
        private StocksRepository _repository;

        public ViewModel()
        {
            _repository = new StocksRepository();
            _downloadCommand = new DownloadCommand(Execute);
        }

        public string Title
        {
            get { return _title; }
            set { _title = value; OnPropertyChanged("Title"); }
        }

        public ObservableCollection<StockEntity> StockEntityList
        {
            get { return _stockList; }
            set { _stockList = value; OnPropertyChanged("StockEntityList"); }
        }

        public DownloadCommand DownloadCommand
        {
            get
            {
                return _downloadCommand;
            }
        }

        private void Execute()
        {
            string[] rows = _repository.GetCSVData();

            Title = rows.First();

            var list = new List<StockEntity>();

            Func<string, StockEntity> converter = row =>
                {
                    var fields = row.Split(new char[] { ',' });

                    return new StockEntity()
                    {
                        Code = fields[0],
                        Name = fields[1],
                        Market = fields[2],
                        Category = fields[3],
                        Opening = fields[4],
                        High = fields[5],
                        Low = fields[6],
                        Closing = fields[7],
                        Volume = fields[8],
                        Turnover = fields[9]
                    };
                };

            rows.Skip(2).ToList().ForEach(row => list.Add(converter(row)));

            StockEntityList = new ObservableCollection<StockEntity>(list);

        }
    }
}

Viewの実装

コードビハンド側はデフォで記述されるコンストラクタコード以外なにもないです。
using System.Windows;

namespace Stocks.View
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

XAML側のコード(MainWindow.xaml)の方はちょっと複雑です。
<Window x:Class="Stocks.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:Stocks.VM"
        Title="StockList" Height="350" Width="525" >
    <Window.DataContext>
        <vm:ViewModel x:Name="viewModel" />
    </Window.DataContext>
    <Window.Resources>
        <CollectionViewSource Source="{Binding StockEntityList}" x:Key="proxy" />
    </Window.Resources>
    <Grid>
        <Grid Background="Silver" Height="50" VerticalAlignment="Top">
            <Label Content="{Binding Path=Title}" HorizontalAlignment="Left" Margin="50,10,10,0" />
            <Button Command="{Binding DownloadCommand}" Content="DownLoad" HorizontalAlignment="Right" Margin="0,10,30,10"  Width="75" />
        </Grid>
        <ListView x:Name="lv" Margin="0,50,0,0" ItemsSource="{Binding Source={StaticResource proxy}}">
            <ListView.View>
                <GridView >
                    <GridViewColumn Header="Code"     DisplayMemberBinding="{Binding Code}" />
                    <GridViewColumn Header="Name"     DisplayMemberBinding="{Binding Name}"/>
                    <GridViewColumn Header="Market"   DisplayMemberBinding="{Binding Market}"/>
                    <GridViewColumn Header="Category" DisplayMemberBinding="{Binding Category}"/>
                    <GridViewColumn Header="Opening"  DisplayMemberBinding="{Binding Opening}"/>
                    <GridViewColumn Header="High"     DisplayMemberBinding="{Binding High}"/>
                    <GridViewColumn Header="Low"      DisplayMemberBinding="{Binding Low}"/>
                    <GridViewColumn Header="Closing"  DisplayMemberBinding="{Binding Closing}"/>
                    <GridViewColumn Header="Volume"   DisplayMemberBinding="{Binding Volume}"/>
                    <GridViewColumn Header="Turnover" DisplayMemberBinding="{Binding Turnover}"/>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>


6,7,8行目はViewModelクラスをインスタンス化して自身のDataContextプロパティにセットします。
this.DataContext = new ViewModel(); と同じです。
10行目は、リストビューの各カラムのようにViewModelで直接に公開されていないメンバにアクセスするために、一度プロクシとして受けます。
14行目はラベルの表示内容がViewModelのTitleプロパティの値と紐付けています。
15行目のボタンもコマンドがViewModelのDownloadCommandに紐付いています。
17行目のリストビューのバインドが10行目の設定に関係します。

まとめ

WPFやSilverlightが強力なDataBind機能を提供していることにより、MVVMパターンでの実装も珍しくなくなってきました。
但し、自動生成ツールやテンプレートなどによりコードの記述量が減るとはいえ、やはりコードビハンドにハンドラを書くのに比べれば複雑ですし大変です。
posted by RR at 22:01 | Comment(0) | サンプルプログラム  | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

※ブログオーナーが承認したコメントのみ表示されます。