Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

SPREAD for WPFでMVVMパターンのアプリケーションを作成する

  • LINEで送る
  • このエントリーをはてなブックマークに追加

 WPFの最大の特長といえば、UIとコマンドロジックを分離できるMVVM(Model-View-ViewModel)パターンでのアプリケーション開発が可能であることです。MVVMは、WPFだけではなくモバイルアプリやUWPでも適用でき、MVVMの発表から10年以上経つ現在も注目されています。SPREAD for WPFでももちろんMVVMの利用をサポートしています。本記事では、SPREAD for WPFを用いて、MVVMパターンを使用したアプリケーションを作成する方法を紹介します。

SPREAD for WPFとは

 累計販売ライセンス140,000本を超え、グレープシティを代表する製品の一つ「SPREAD」シリーズのWPF向けコンポーネントです。SPREADの最大の特長であるExcelライクな外観や操作性のほか、1レコード複数行表示やグループ集計など、独自機能も備えます。詳しい機能は、製品情報サイトで紹介しておりますので、是非ご一読ください。また、ClickOnceデモも公開しています。

対象読者

  • Visual Basic、C#を使ってプログラムを作ったことのある方
  • 業務アプリケーションにグリッドを使用している方
  • MVVMを使用したWPFアプリケーション開発に興味のある方

準備

 本記事で紹介するサンプルを実行するには、Visual Studio 2010/2012/2013/2015およびSPREAD for WPF 1.0Jの製品版またはトライアル版のインストールが必要です。トライアル版はこちらからダウンロードしていただけます。インストール方法や、Visual Studioのツールボックスにコントロールを追加する方法については、過去記事「いよいよ登場! WPFをブレイクスルーする魅惑のコンポーネント「SPREAD for WPF 1.0J」で詳しく解説されていますので、そちらをご覧ください。

サンプルアプリケーションの概要

 コード、製品名、価格というフィールドを持った製品データ一覧をSPREADに表示し、SPREADのボタン型セルを使用してコマンドを実行するという簡単なWPFアプリケーションを、MVVMパターンで作成します。

データをSPREADに表示する(1)

 まずは、製品データを単純にSPREADに表示するだけのアプリケーションを作成しましょう。SpreadWPF_MVVMという名前で新規プロジェクトを作成します。

モデルを作成

 ここでは、単純にデータをモデル化したものをモデルとして定義します。ProductModelという名称のクラスを作成し、製品(コード、名称、価格)データのコレクションを生成、そのコレクションを返すメソッドを用意します。

VB(ProductModel.vb)
Imports System.Collections.ObjectModel

Public Class Product
    Public Property Code As String
    Public Property Name As String
    Public Property Price As Integer
End Class

Public Class ProductModel
    Private _items As ObservableCollection(Of Product)

    Public Sub New()
        _items = New ObservableCollection(Of Product) From
        {
            New Product() With {.Code = "0000001", .Name = "アーモンド", .Price = 200},
            New Product() With {.Code = "0000002", .Name = "グレープシード", .Price = 200},
            New Product() With {.Code = "0000003", .Name = "オリーブ", .Price = 320},
            New Product() With {.Code = "0000004", .Name = "ゴマ油", .Price = 300},
            New Product() With {.Code = "0000005", .Name = "ひまわり", .Price = 200},
            New Product() With {.Code = "0000006", .Name = "えごま", .Price = 300},
            New Product() With {.Code = "0000007", .Name = "アルガン", .Price = 800},
            New Product() With {.Code = "0000008", .Name = "ココナッツ", .Price = 720},
            New Product() With {.Code = "0000009", .Name = "ウォールナッツ", .Price = 400},
            New Product() With {.Code = "0000010", .Name = "亜麻仁油", .Price = 700}
        }
    End Sub

    ' データコレクションを返します。
    Public Function GetProducts() As ObservableCollection(Of Product)
        Return _items
    End Function
End Class
C#(ProductModel.cs)
// 次の名前空間宣言を追加しています
// using System.Collections.ObjectModel;

public class Product
{
    public string Code { get; set; }
    public string Name { get; set; }
    public int Price { get; set; }
    public int Number { get; set; }
}

public class ProductModel
{
    private ObservableCollection<Product> _items;
    public ProductModel()
    {
        _items = new ObservableCollection<Product>
        {
            new Product() { Code = "0000001", Name = "アーモンド", Price = 200 },
            new Product() { Code = "0000002", Name = "グレープシード", Price = 200 },
            new Product() { Code = "0000003", Name = "オリーブ", Price = 320 },
            new Product() { Code = "0000004", Name = "ゴマ油", Price = 300 },
            new Product() { Code = "0000005", Name = "ひまわり", Price = 200 },
            new Product() { Code = "0000006", Name = "えごま", Price = 300 },
            new Product() { Code = "0000007", Name = "アルガン", Price = 800 },
            new Product() { Code = "0000008", Name = "ココナッツ", Price = 720 },
            new Product() { Code = "0000009", Name = "ウォールナッツ", Price = 400 },
            new Product() { Code = "0000010", Name = "亜麻仁油", Price = 700 }
        };
    }

    public ObservableCollection<Product> GetProducts()
    {
        return _items;
    }
}

データをSPREADに表示する(2)

ビューモデルを作成

 次に、ビューモデルに該当するクラスをProductViewModelという名称で、新規に作成します。ビューモデルクラスは値が変更された場合に、ビューに変更を通知するため、INotifyPropertyChangedインタフェースを実装します。まずは、データをSPREADに表示するだけですので、モデルからデータを取得します。ビューモデルにプロパティとして定義されるデータコレクションにデータが取得されると、ビューに通知が行くという仕組みです。

VB(ProductViewModel.vb)
Imports System.Collections.ObjectModel
Imports System.ComponentModel

Public Class ProductViewModel
    Implements INotifyPropertyChanged

    Private _products As ObservableCollection(Of Product)
    Private _model As ProductModel

    Public Sub New()
        ' モデルからデータを取得します。
        _model = New ProductModel()
        _products = _model.GetProducts()
    End Sub

    ' データのコレクション
    Public Property Products As ObservableCollection(Of Product)
        Get
            Return _products
        End Get
        Set(value As ObservableCollection(Of Product))
            If _products.Equals(value) Then
                _products = value
                ' ビューに値の変更を通知します。
                OnPropertyChanged("Products")
            End If
        End Set
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    ' 値が変更されるとビューに変更を通知します。
    Protected Overridable Sub OnPropertyChanged(propname As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
    End Sub
End Class
C#(ProductViewModel.cs)
// 次の名前空間宣言を追加しています。
// using System.Collections.ObjectModel;
// using System.ComponentModel;

public class ProductViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Product> _products;
    private ProductModel _model;

    public ProductViewModel()
    {
        // モデルからデータを取得します。
        _model = new ProductModel();
        _products = _model.GetProducts();
    }

    // データのコレクション
    public ObservableCollection<Product> Products
    {
        get { return _products; }
        set
        {
            if (_products != value)
            {
                _products = value;
                // Viewに値の変更を通知します。
                OnPropertyChanged("Products");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    // 値が変更されるとビューに変更を通知します。
    protected virtual void OnPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

ビューを作成

 最後にビューに該当するUIを作成します。データを表示するUIには、SPREAD for WPFを使用します。MainWindow.xamlをVisual Studioのデザイン画面で開き、ツールボックスからSPREAD(gcSpreadGrid1)を配置します。

ツールボックスからSPREADを配置
ツールボックスからSPREADを配置

 WindowのDataContextプロパティにビューモデルを設定し、GcSpreadGridクラスのItemSourceプロパティに、ProductViewModelクラスで定義したデータコレクションにバインドします。

備考

 XAMLコードでは、次のような名前空間を宣言しています。

xmlns:local="clr-namespace:SpreadWPF_MVVM"
XAML(MainWindow.xaml)
<Window.DataContext>
    <local:ProductViewModel/>
</Window.DataContext>
<StackPanel>
    <sg:GcSpreadGrid x:Name="gcSpreadGrid1" ItemsSource="{Binding Products}" >
    </sg:GcSpreadGrid>
</StackPanel>

 コードビハインドでも実装できます。

VB.NET(MainWindow.xaml.vb)
Class MainWindow
    Sub New()
        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()

        Dim viewModel As New ProductViewModel()
        Me.DataContext = viewModel

        ' SPREADとビューモデルのデータコレクションをバインドします。
        Dim binding1 As New Binding("Products")
        GcSpreadGrid1.SetBinding(GcSpreadGrid.ItemsSourceProperty, binding1)
    End Sub
End Class
C#(MainWindow.xaml.cs)
public MainWindow()
{
    InitializeComponent();

    ProductViewModel viewModel = new ProductViewModel();
    this.DataContext = viewModel;

    // SPREADとビューモデルのデータコレクションをバインドします。
    Binding binding1 = new Binding("Products");
    gcSpreadGrid1.SetBinding(GcSpreadGrid.ItemsSourceProperty, binding1);
}

 ビューでの設定は以上です。モデルで定義されたデータがビューモデルを介してビューに表示されるようになります。上記のコードを実行すると次のような画面となります。

MVVMパターンでデータ表示したSPREAD
MVVMパターンでデータ表示したSPREAD

 SPREADには連結するデータのデータフィールドを自動的に列として生成する機能があります。既定では列の自動生成が有効になっているため、上記のように連結したデータはすべてSPREADに表示されます。特定のデータ列のみ表示したい場合は、列の自動生成機能を無効にし、列のDataFieldプロパティを使用してデータソースのフィールドに関連付けます。列の自動生成機能は、GcSpreadGridクラスのAutoGenerateColumnsプロパティをfalseに設定すると無効となります。次のコードでは、「Price」列のみ非表示にします。

XAML(MainWindow.xaml)
<Window.DataContext>
    <local:ProductViewModel/>
</Window.DataContext>
<StackPanel>
    <sg:GcSpreadGrid x:Name="gcSpreadGrid1" ItemsSource="{Binding Products}" AutoGenerateColumns="False">
        <sg:GcSpreadGrid.Columns>
            <sg:Column>
                <sg:Column.DataField>
                    <sg:PropertyDataField Property="Code" />
                </sg:Column.DataField>
            </sg:Column>
            <sg:Column>
                <sg:Column.DataField>
                    <sg:PropertyDataField Property="Name" />
                </sg:Column.DataField>
            </sg:Column>
        </sg:GcSpreadGrid.Columns>
    </sg:GcSpreadGrid>
</StackPanel>

 コードビハインドでの同じ設定は、以下のようなコードで可能です。

VB.NET(MainWindow.xaml.vb)
‘ 次の名前空間宣言を追加しています。
‘ Imports GrapeCity.Windows.SpreadGrid

Sub New()
    ' この呼び出しはデザイナーで必要です。
    InitializeComponent()

    ' InitializeComponent() 呼び出しの後で初期化を追加します。
    Dim viewModel As New ProductViewModel()
    Me.DataContext = viewModel

    ' 列の自動生成を無効にします。
    GcSpreadGrid1.AutoGenerateColumns = False

    ' Code、Name列のみ表示します。
    GcSpreadGrid1.Columns(0).DataField = New PropertyDataField() With {.Property = "Code"}
    GcSpreadGrid1.Columns(1).DataField = New PropertyDataField() With {.Property = "Name"}

    ' SPREADとビューモデルのデータコレクションをバインドします。
    Dim spreadBinding As New Binding("Products")
    GcSpreadGrid1.SetBinding(GcSpreadGrid.ItemsSourceProperty, spreadBinding)
End Sub
C#(MainWindows.xaml.cs)
// 次の名前空間宣言を追加しています。
// using GrapeCity.Windows.SpreadGrid;

public MainWindow()
{
    InitializeComponent();

    ProductViewModel viewModel = new ProductViewModel();
    this.DataContext = viewModel;

    // 列の自動生成を無効にします。
    gcSpreadGrid1.AutoGenerateColumns = false;

    // Code、Name列のみ表示します。
    gcSpreadGrid1.Columns[0].DataField = new PropertyDataField() { Property = "Code" };
    gcSpreadGrid1.Columns[1].DataField = new PropertyDataField() { Property = "Name" };

    // SPREADとビューモデルのデータコレクションをバインドします。
    Binding spreadBinding = new Binding("Products");
    gcSpreadGrid1.SetBinding(GcSpreadGrid.ItemsSourceProperty, spreadBinding);

}
特定列のみ表示
特定列のみ表示

備考

 ここでは特に表示列数の指定はしていないため、既定の10列が表示されます。

コマンドを作成する

 現時点で、SPREADには「Code」と「Name」のデータだけが表示されていますので、選択された製品の「Price」を取得するというコマンドを作成しましょう。ビュー側でコマンドをバインディングできるのは、基本的にMenuItemやButtonといったICommandインタフェースを実装したコントロールです。SPREADでは、ボタン型セルにコマンド機能を設定することができます。ただし、ボタン型セルのCommandプロパティは、依存関係プロパティではないため、ビューモデルのプロパティとして公開されたコマンドに直接バインドすることはできません。そこで、ボタン型セルに設定するカスタムコマンドのパラメータに、ビューで作成したビューモデルのインスタンスを渡す方法を紹介します。

ビューモデルにプロパティを追加

 ProductViewModelクラスにビューとバインディングするデータをプロパティとして追加します。ここでは、選択された製品「SelectedProduct」と、製品名「ProductName」、価格「ProductPrice」の3つのプロパティを追加し、それぞれビューに変更が通知されるよう、OnPropertyChangedメソッドを実装します。

VB.NET(ProductViewModel.vb)
Private Property _productName
Public Property ProductName As String
    Get
        Return _productName
    End Get
    Set(value As String)
        _productName = value
        OnPropertyChanged("ProductName")
    End Set
End Property

Private Property _productPrice
Public Property ProductPrice As Integer
    Get
        Return _productPrice
    End Get
    Set(value As Integer)
        _productPrice = value
        OnPropertyChanged("ProductPrice")
    End Set
End Property

Private Property _selectedProduct
Public Property SelectedProduct As Product
    Get
        Return _selectedProduct
    End Get
    Set(value As Product)
        _selectedProduct = value
        OnPropertyChanged("SelectedProduct")
    End Set
End Property
C#(ProductViewModel.cs)
private string _ProductName;
public string ProductName
{
    get { return _ProductName; }
    set
    {
        _ProductName = value;
        OnPropertyChanged("ProductName");
    }
}

private int _ProductPrice;
public int ProductPrice
{
    get { return _ProductPrice; }
    set
    {
        _ProductPrice = value;
        OnPropertyChanged("ProductPrice");
    }
}

private Product _SelectedProduct;
public Product SelectedProduct
{
    get { return _SelectedProduct; }
    set
    {
        _SelectedProduct = value;
        OnPropertyChanged("SelectedProduct");
    }
}

コマンドを実装

 次に、選択された製品から、製品名と価格を取得するコマンドを実装します。コマンドはICommandインタフェースを実装します。ボタン型セルにコマンドを設定すると、Executeメソッドのパラメータからは、セルの情報を保存したCellCommandParameterオブジェクトを取得できます。CellCommandParameterクラスのCustomCommandParameterプロパティには、任意の情報を保存できます。ここでは、ProductViewModelオブジェクトを保存し、ビューモデルクラスのメンバにアクセスできるようにします。

VB.NET(ProductViewModel.vb)
‘ 次の名前空間宣言を追加しています。
‘ Imports GrapeCity.Windows.SpreadGrid

Public Class GetPriceCommand
    Implements ICommand

    Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Return True
    End Function

    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        ' CustomCommandParameterに指定したProductViewModelオブジェクトを取得します。
        Dim cp As CellCommandParameter = parameter
        Dim viewModel As ProductViewModel = cp.CustomCommandParameter
        ' ビューモデルのプロパティを更新します。
        viewModel.ProductName = viewModel.SelectedProduct.Name
        viewModel.ProductPrice = viewModel.SelectedProduct.Price
    End Sub
End Class
C#(ProductViewModel.cs)
// 次の名前空間宣言を追加しています。
// using GrapeCity.Windows.SpreadGrid;

public class GetPriceCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        // CustomCommandParameterに指定したProductViewModelオブジェクトを取得します。
CellCommandParameter cp = parameter as CellCommandParameter;
        ProductViewModel viewModel = cp.CustomCommandParameter as ProductViewModel;
        // ビューモデルのプロパティを更新します。
        viewModel.ProductName = viewModel.SelectedProduct.Name;
        viewModel.ProductPrice = viewModel.SelectedProduct.Price;
    }
}

ビューのコントロールにバインディング

 最後に、ビューモデルで定義したプロパティや、コマンドをビューのコントロールにバインディングします。まずは、選択された製品の製品名と値段を表示する、TextBlockコントロールを次のように定義します。TextプロパティにProductViewModelクラスのProductNameとProductPriceプロパティをバインディングしています。

XAML(MainWindow.xaml)
<StackPanel Orientation="Horizontal">
    <TextBlock Text="{Binding ProductName}" FontSize="20" Foreground="Red" VerticalAlignment="Bottom"/>
    <TextBlock Text=" の値段は " VerticalAlignment="Bottom"/>
    <TextBlock Text="{Binding ProductPrice}" FontSize="20" Foreground="Red" VerticalAlignment="Bottom"/
    <TextBlock Text=" 円です。" VerticalAlignment="Bottom"/>
</StackPanel>

 次に、SPREADで選択された項目とProductViewModelクラスのSelectedProductプロパティをバインドします。SPREAD側での値選択がトリガーとなる双方向バインディングであるため、ModeプロパティはTwoWayにします。

XAML(MainWindow.xaml)
<sg:GcSpreadGrid x:Name="gcSpreadGrid1" ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct, Mode=TwoWay}" AutoGenerateColumns="False">
    <sg:GcSpreadGrid.Columns>
        <sg:Column>
            <sg:Column.DataField>
                <sg:PropertyDataField Property="Code" />
            </sg:Column.DataField>
        </sg:Column>
        <sg:Column>
            <sg:Column.DataField>
                <sg:PropertyDataField Property="Name" />
            </sg:Column.DataField>
        </sg:Column>
    </sg:GcSpreadGrid.Columns>
</sg:GcSpreadGrid>

 コードビハインドでの同じ設定は、以下のようなコードで可能です。

VB.NET(MainWindow.xaml.vb)
' 選択行にViewModelのSelectedProductプロパティをバインドします。
Dim selectedBinding = New Binding("SelectedProduct")
selectedBinding.Mode = BindingMode.TwoWay
GcSpreadGrid1.SetBinding(GcSpreadGrid.SelectedItemProperty, selectedBinding)
C#(MainWindow.xaml.cs)
// 選択行にViewModelのSelectedProductプロパティをバインドします。
Binding selectedBinding = new Binding("SelectedProduct");
selectedBinding.Mode = BindingMode.TwoWay;
gcSpreadGrid1.SetBinding(GcSpreadGrid.SelectedItemProperty, selectedBinding);

 そして、SPREADのボタン型セルにコマンドを設定します。ここでは、3列目にボタン型セルを設定します。ButtonCellTypeオブジェクトを作成し、CommandプロパティにはGetPriceCommandを、CustomCommandParameterプロパティには、ProductViewModelクラスのインスタンスを設定します。ボタン型セルの設定は、コードビハインドの方がわかりやすいです。

VB.NET(MainWindow.xaml.vb)
Class MainWindow
    Sub New()
        InitializeComponent()

        Dim viewModel As New ProductViewModel()
        Me.DataContext = viewModel

        ' 列の自動生成を無効にします。
        GcSpreadGrid1.AutoGenerateColumns = False

        ' Code、Name列のみ表示します。
        GcSpreadGrid1.Columns(0).DataField = New PropertyDataField() With {.Property = "Code"}
        GcSpreadGrid1.Columns(1).DataField = New PropertyDataField() With {.Property = "Name"}

        ' SPREADとビューモデルのデータコレクションをバインドします。
        Dim spreadBinding As New Binding("Products")
        GcSpreadGrid1.SetBinding(GcSpreadGrid.ItemsSourceProperty, spreadBinding)

        ' 選択行にビューモデルのSelectedProductプロパティをバインドします。
        Dim selectedBinding = New Binding("SelectedProduct")
        selectedBinding.Mode = BindingMode.TwoWay
        GcSpreadGrid1.SetBinding(GcSpreadGrid.SelectedItemProperty, selectedBinding)

        ' ボタン型セルを作成します。
        Dim Button As New ButtonCellType()
        Button.Content = "値段"
        ' ボタンにコマンドを設定します。
        Button.Command = New GetPriceCommand()
        Button.CustomCommandParameter = viewModel
        GcSpreadGrid1.Columns(2).CellType = Button
    End Sub
End Class
C#(MainWindow.xaml.cs)
public MainWindow()
{
    InitializeComponent();

    ProductViewModel viewModel = new ProductViewModel();
    this.DataContext = viewModel;

    // 列の自動生成を無効にします。
    gcSpreadGrid1.AutoGenerateColumns = false;

    // Code、Name列のみ表示します。
    gcSpreadGrid1.Columns[0].DataField = new PropertyDataField() { Property = "Code" };
    gcSpreadGrid1.Columns[1].DataField = new PropertyDataField() { Property = "Name" };

    // SPREADとビューモデルのデータコレクションをバインドします。
    Binding spreadBinding = new Binding("Products");
    gcSpreadGrid1.SetBinding(GcSpreadGrid.ItemsSourceProperty, spreadBinding);

    // 選択行にビューモデルのSelectedProductプロパティをバインドします。
    Binding selectedBinding = new Binding("SelectedProduct");
    selectedBinding.Mode = BindingMode.TwoWay;
    gcSpreadGrid1.SetBinding(GcSpreadGrid.SelectedItemProperty, selectedBinding);

    // ボタン型セルを作成します。
    ButtonCellType button = new ButtonCellType();
    button.Content = "値段";
    // ボタンにコマンドを設定します。
    button.Command = new GetPriceCommand();
    button.CustomCommandParameter = viewModel;
    gcSpreadGrid1.Columns[2].CellType = button;
}

 XAMLでボタン型セルにコマンドを設定するには、コマンドをリソースとして定義します。また、DataContextに設定しているProductViewModelクラスに名前をつけて、CustomCommandParameterプロパティに設定します。

XAML(MainWindow.xaml)
<Window.DataContext>
    <local:ProductViewModel x:Name="productViewModel" />
</Window.DataContext>
<Window.Resources>
    <local:GetPriceCommand x:Key="getPriceCommand" />
</Window.Resources>
<StackPanel>
    <sg:GcSpreadGrid x:Name="gcSpreadGrid1" ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct, Mode=TwoWay}" AutoGenerateColumns="False">
        <sg:GcSpreadGrid.Columns>
            <sg:Column>
                <sg:Column.DataField>
                    <sg:PropertyDataField Property="Code" />
                </sg:Column.DataField>
            </sg:Column>
            <sg:Column>
                <sg:Column.DataField>
                    <sg:PropertyDataField Property="Name" />
                </sg:Column.DataField>
            </sg:Column>
            <sg:Column>
                <sg:Column.CellType>
                    <sg:ButtonCellType Content="価格" Command="{StaticResource getPriceCommand}" CustomCommandParameter="{x:Reference productViewModel}"  />
                </sg:Column.CellType>
            </sg:Column>
        </sg:GcSpreadGrid.Columns>
    </sg:GcSpreadGrid>
</StackPanel>

 以上で完成です。実行し、知りたい製品の「値段」ボタンをクリックすると、次のように表示されます。

コマンドの実行
コマンドの実行

コマンドでSPREADを操作する

 SPREADの行、列、セルを生成するRow、Column、Cellクラスのメンバは、依存関係プロパティを持っていません。そのため、これらのクラスのメンバをバインディングターゲットとすることができず、ビューモデルからセルや列に対して直接バインディングができません。しかし、ボタン型セルに設定したコマンドが実行されると、Executeメソッドのパラメータからは、セルの情報を保存したCellCommandParameterオブジェクトを取得できます。また、コマンドクラスにSPREADオブジェクトを示すプロパティを追加することで、コマンドによりSPREADを操作できるようになります。ここでは、そのテクニックについて紹介します。

 「値段」ボタン型セルのクリックで、4列目に値段を表示し、さらに値段が300円以上の場合は、行の背景色を変更するというコマンドを作成します。

コマンドを変更する

 先ほど作成したGetPriceCommandクラスを変更します。GcSpreadGridオブジェクトを示すプロパティを追加します。

VB.NET(ProductViewModel.vb)
Private _spread As GcSpreadGrid
Public Property spread As GcSpreadGrid
    Get
        Return _spread
    End Get
    Set(value As GcSpreadGrid)
        _spread = value
    End Set
End Property
C#(ProductViewModel.cs)
private GcSpreadGrid _spread;
public GcSpreadGrid spread
{
    get { return _spread; }
    set { _spread = value; }
}

 次に、Executeメソッドを変更します。パラメータ「parameter」から、ボタンが押されたセル情報を格納しているCellCommandParameterクラスを取得できます。CellCommandParameterクラスのCellPositionプロパティからボタンが押されたセルの行インデックスを取得できるため、変更を行いたいSPREADの行にアクセスすることができます。

VB.NET(ProductViewModel.vb)
Public Sub Execute(parameter As Object) Implements ICommand.Execute
    ' CustomCommandParameterに指定したProductViewModelオブジェクトを取得します。
    Dim cp As CellCommandParameter = parameter
    Dim viewModel As ProductViewModel = cp.CustomCommandParameter

    ' ビューモデルのプロパティを更新します。
    viewModel.ProductName = viewModel.SelectedProduct.Name
    viewModel.ProductPrice = viewModel.SelectedProduct.Price

    ' ボタン型セルの右側のセルに価格を表示します。
    _spread(cp.CellPosition.Row, cp.CellPosition.Column + 1).Value = viewModel.SelectedProduct.Price

    If _spread IsNot Nothing Then
        ' ボタン型セルの右側のセルに価格を表示します。
        _spread(cp.CellPosition.Row, cp.CellPosition.Column + 1).Value = viewModel.SelectedProduct.Price

        ' 価格が300円以上のものは、行の背景色を変更します。
        If viewModel.SelectedProduct.Price > 300 Then
            _spread.Rows(cp.CellPosition.Row).Background = Brushes.Pink
        End If
    End If
End Sub
C#(ProductViewModel.cs)
public void Execute(object parameter)
{
    // CustomCommandParameterに指定したProductViewModelオブジェクトを取得します。
    CellCommandParameter cp = parameter as CellCommandParameter;
    ProductViewModel viewModel = cp.CustomCommandParameter as ProductViewModel;

    // ビューモデルのプロパティを更新します。
    viewModel.ProductName = viewModel.SelectedProduct.Name;
    viewModel.ProductPrice = viewModel.SelectedProduct.Price;

if (_spread != null)
    {
        // ボタン型セルの右側のセルに価格を表示します。
        _spread[cp.CellPosition.Row, cp.CellPosition.Column + 1].Value = viewModel.SelectedProduct.Price;

        // 価格が300円以上のものは、行の背景色を変更します。
        if (viewModel.SelectedProduct.Price > 300)
        {
            _spread.Rows[cp.CellPosition.Row].Background = Brushes.Pink;
        }
        else
        {
            _spread.Rows[cp.CellPosition.Row].Background = Brushes.White;
        }
    }
}

 最後に、ビューでボタン型セルを生成するとき、Commandプロパティに設定するGetPriceCommandオブジェクトのspreadプロパティにGcSpreadGridオブジェクトを設定します。

VB.NET(MainWindow.xaml.vb)
' ボタン型セルを作成します。
Dim Button As New ButtonCellType()
Button.Content = "値段"
' ボタンにコマンドを設定します。
Button.Command = New GetPriceCommand() With {.spread = GcSpreadGrid1}
Button.CustomCommandParameter = viewModel
GcSpreadGrid1.Columns(2).CellType = Button
C#(MainWindow.xaml.cs)
// ボタン型セルを作成します。
ButtonCellType button = new ButtonCellType();
button.Content = "値段";
// ボタンにコマンドを設定します。
button.Command = new GetPriceCommand() { spread = gcSpreadGrid1 };
button.CustomCommandParameter = viewModel;
gcSpreadGrid1.Columns[2].CellType = button;

 XAMLでは、リソースとして定義したコマンドを次のように実装します。

XAML(MainWindow.xaml)
<Window.Resources>
    <local:GetPriceCommand x:Key="getPriceCommand" spread="{x:Reference gcSpreadGrid1}"  />
</Window.Resources>

 アプリケーションを実行し、いくつかボタン型セルをクリックすると、次のような結果となります。

コマンドでSPREADを操作
コマンドでSPREADを操作

コマンドのコンストラクタ引数を使用する

 コマンドクラスから、ビューモデルやSPREADオブジェクトへアクセスする方法として、CustomCommandParameterプロパティの使用や、コマンドクラスへのプロパティ追加を紹介しましたが、この他に、コマンドクラスのコンストラクタ引数にオブジェクトを渡す方法も考えられます。ただし、XAMLにはコンストラクタに引数を渡す方法が用意されていないため、この方法はコードビハインドでの設定のみ有効です。まず、コマンドを定義するGetPriceCommandクラスを次のように実装します。

VB.NET(ProductViewModel.vb)
Public Class GetPriceCommand
    Implements ICommand

    ' SPREADとビューモデルを示すプロパティを定義します。
    Private _spread As GcSpreadGrid
    Private _viewModel As ProductViewModel

    ' 引数付きコンストラクタ
    Public Sub New(ByRef spread As GcSpreadGrid, ByVal viewModel As ProductViewModel)
        _spread = spread
        _viewModel = viewModel
    End Sub

    Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Return True
    End Function

    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        Dim cp As CellCommandParameter = parameter

        ' ビューモデルのプロパティを更新します。
        _viewModel.ProductName = _viewModel.SelectedProduct.Name
        _viewModel.ProductPrice = _viewModel.SelectedProduct.Price

        ' ボタン型セルの右側のセルに価格を表示します。
        _spread(cp.CellPosition.Row, cp.CellPosition.Column + 1).Value = _viewModel.SelectedProduct.Price

        ' 価格が300円以上のものは、行の背景色を変更します。
        If _viewModel.SelectedProduct.Price > 300 Then
            _spread.Rows(cp.CellPosition.Row).Background = Brushes.Pink
        End If
    End Sub
End Class
C#(ProductViewModel.cs)
public class GetPriceCommand : ICommand
{
    public event EventHandler CanExecuteChanged;

    // SPREADとビューモデルを示すプロパティを定義します。
    private GcSpreadGrid _spread;
    private ProductViewModel _viewModel;

    // 引数付きコンストラクタ
    public GetPriceCommand(GcSpreadGrid spread, ProductViewModel viewModel)
    {
        _spread = spread;
        _viewModel = viewModel;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        CellCommandParameter cp = parameter as CellCommandParameter;

        //ビューモデルのプロパティを更新します。
        _viewModel.ProductName = _viewModel.SelectedProduct.Name;
        _viewModel.ProductPrice = _viewModel.SelectedProduct.Price;

        // ボタン型セルの右側のセルに価格を表示します。
        _spread[cp.CellPosition.Row, cp.CellPosition.Column + 1].Value = _viewModel.SelectedProduct.Price;

        // 価格が300円以上のものは、行の背景色を変更します。
        if (_viewModel.SelectedProduct.Price > 300)
        {
            _spread.Rows[cp.CellPosition.Row].Background = Brushes.Pink;
        }
        else
        {
            _spread.Rows[cp.CellPosition.Row].Background = Brushes.White;
        }
    }
}

 ビュー側のボタン型セルの設定は、以下の通りです。Commandプロパティに設定するGetPriceCommandクラスのコンストラクタ引数に、SPREADオブジェクトとビューモデルのオブジェクトを渡します。

VB.NET(MainWindow.xaml.vb)
' ボタン型セルを作成します。
Dim Button As New ButtonCellType()
Button.Content = "値段"
' ボタンにコマンドを設定します。
Button.Command = New GetPriceCommand(GcSpreadGrid1, viewModel)
GcSpreadGrid1.Columns(2).CellType = Button
C#(MainWindow.xaml.cs)
// ボタン型セルを作成します。
ButtonCellType button = new ButtonCellType();
button.Content = "値段";
// ボタンにコマンドを設定します。
button.Command = new GetPriceCommand(gcSpreadGrid1, viewModel);
gcSpreadGrid1.Columns[2].CellType = button;

 以上で、前項と同様の動作を実現することができます。

まとめ

 本記事では、SPREAD for WPFを使用して、シンプルなMVVMアプリケーションを作成する方法を紹介しました。SPREAD for WPFは、グリッドでありながら標準のDataGridやListBoxコントロールと異なり、ItemTemplateを持たないため、セルや行といった項目単位でバインディングすることができません。しかし、SPREADの持つ機能を利用して工夫することで、さまざまなコマンドを実装することも可能です。MVVMパターンの実装方法は多様で、実際の業務アプリケーションでは、もっと複雑な処理が必要となりますが、本記事での基本的な実装紹介が開発の一助となれば幸いです。

  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5