DataGridのカラムを動的に作成する その2

 前回の続きで動的にDataGridカラムを作成する方法についてです。
 前回はC#コードでDataGridカラムを構築するためのコードを紹介しましたが、DataGridのカラム定義をMainWindowのコンストラクタで実行していたため、後からDataGridの構造を変更することはできませんでした。
 今回は読み込んだデータに応じてDataGridのカラム定義を再定義できるようにしてみます。

 前回はコードビハインドでDataGridを操作していましたが、今回はDataGridを継承した「DynamicDataGrid」というクラスを作成しています。
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace DynamicDataGrid2.Views
{
    public class DynamicDataGrid : DataGrid
    {
        public DynamicDataGrid()
        {
            AutoGenerateColumns = false;
        }

        #region ヘッダー
        public IList<string> Headers
        {
            get { return (IList<string>)GetValue(HeadersProperty); }
            set { SetValue(HeadersProperty, value); }
        }

        public static readonly DependencyProperty HeadersProperty =
            DependencyProperty.Register("Headers", typeof(IList<string>), typeof(DynamicDataGrid), new PropertyMetadata(null));
        #endregion

        /// <summary>
        /// DataGridを再定義する
        /// </summary>
        public void RedefineDataGrid()
        {
            Columns.Clear();    // 古いDataGrid定義を破棄

            for(var i = 0; i < Headers.Count; i++)
            {
                Columns.Add(new DataGridTextColumn()
                {
                    Header = Headers[i],
                    Binding = new Binding(string.Format("Values[{0}]", i))
                });
            }
        }
    }
}
 コンストラクタでDynamicDataGridの設定を行っています。
 ここではカラムの自動生成機能をOffにしています。
 DataGridのカラムを動的に定義する際にはデータバインディングするコレクション(Items)とは別にヘッダー情報が必要になります。
 DynamicDataGridでは依存関係プロパティでHeadersプロパティを定義し、ヘッダー情報をデータバインディングで指定できるようにしました。
 このHeadersプロパティのアイテム数が変更するタイミングでカラム定義ができればよかったのですが、Headersのプロパティ変更コールバックはstaticメソッドの為、肝心のDataGridにアクセスできません。
 そのためDataGridを更新(カラムの再定義)を行う為のメソッド(RedefineDataGrid)を公開しています。
 然るべきタイミングでRedefineDataGridメソッドを実行することでカラム定義を更新します。
 RedefineDataGridメソッドではDataGridのColumnsをクリアして古い定義を削除し、Headersを元にカラム定義を作り直しています。
 今回は動的にカラムを作り替えることが目的なので、style等は省略しました。(そのため、かなりシンプルな内容になっています)
 あとはViewModelとViewを用意してちゃんと動くか試してみます。
using Livet;
using Livet.Messaging;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace DynamicDataGrid2.ViewModels
{
    public class MainWindowViewModel : ViewModel
    {
        public MainWindowViewModel()
        {
            Headers = new ObservableCollection<string>();
            Items = new ObservableCollection<ItemViewModel>();
        }

        public ObservableCollection<string> Headers { get; private set; }

        public ObservableCollection<ItemViewModel> Items { get; private set; }

        // DataGrid用データ作成。メッセンジャーを使用して更新
        public void SetData1()
        {
            Headers.Clear();
            Items.Clear();

            Headers.Add("項目1");
            Headers.Add("項目2");

            var item1 = new ItemViewModel();
            item1.Values.AddRange(new[] {"値1", "値2"});
            Items.Add(item1);

            var item2 = new ItemViewModel();
            item2.Values.AddRange(new[] { "値3", "値4" });
            Items.Add(item2);

            // メッセンジャーを使ってDataGridを更新
            Messenger.Raise(new InteractionMessage("UpdateDataGrid"));
        }

        // DataGrid用のデータ作成。更新はView側で定義
        public void SetData2()
        {
            Headers.Clear();
            Items.Clear();

            Headers.Add("Item1");
            Headers.Add("Item2");
            Headers.Add("Item3");

            var item1 = new ItemViewModel();
            item1.Values.AddRange(new[] { "A", "B", "C" });
            Items.Add(item1);

            var item2 = new ItemViewModel();
            item2.Values.AddRange(new[] { "D", "E", "F" });
            Items.Add(item2);

            var item3 = new ItemViewModel();
            item3.Values.AddRange(new[] { "G", "H", "I" });
            Items.Add(item3);
        }
    }


    public class ItemViewModel : ViewModel
    {
        public ItemViewModel()
        {
            Values = new List<string>();
        }

        public List<string> Values { get; private set; }
    }
}
 構造がシンプルなのでModelを省略しています。
 MainWindowViewModelではDataGridのためのデータ(Items)とそのヘッダー情報(Headers)を持っています。
 SetData1メソッドとSetData2メソッドはHeadersとItemsの中身を変更します。
 SetData1メソッドではメッセンジャーを利用してDataGridの更新を行っています。
 SetData2メソッドはView側でDataGridの更新を行うようにしています。
<Window x:Class="DynamicDataGrid2.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:DynamicDataGrid2.Views"
        xmlns:vm="clr-namespace:DynamicDataGrid2.ViewModels"
        Title="MainWindow" Width="200" Height="200">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>

    <i:Interaction.Triggers>
        <l:InteractionMessageTrigger Messenger="{Binding Messenger}" MessageKey="UpdateDataGrid">
            <l:LivetCallMethodAction MethodTarget="{Binding ElementName=dataGrid}"
                                     MethodName="RedefineDataGrid"/>
        </l:InteractionMessageTrigger>
    </i:Interaction.Triggers>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        
        <Button Grid.Row="0" Grid.Column="0" Content="データ1を設定">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="SetData1"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
        
        <Button Grid.Row="0" Grid.Column="1" Content="データ2を設定">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="Click">
                    <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="SetData2"/>
                    <l:LivetCallMethodAction MethodTarget="{Binding ElementName=dataGrid}"
                                             MethodName="RedefineDataGrid"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
        
        <v:DynamicDataGrid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
                           x:Name="dataGrid"
                           Headers="{Binding Headers}"
                           ItemsSource="{Binding Items}"/>
    </Grid>
</Window>
 Viewではデータ変更用のボタンと動的DataGridを配置しています。
 「データ1を設定」ボタンでは、ボタンを押すとSetData1メソッドが実行され、メッセンジャーに「UpdateDataGrid」メッセージが送られます。
 InteractionMessageTrigger(Livetのメッセンジャー機能です)を使ってDynamicDataGridのRedefineDataGridメソッドを呼んでいます。
 「データ2を設定」ボタンでは、ボタンを押すとSetData1メソッドとDynamicDataGridのRedefineDataGridメソッドが実行されるように設定してあります。
 どちらの方法を使ってもDataGridは更新されます。
動的DataGrid
 DynamicDataGrid内でDataGridの更新を制御できればよかったのですが、方法が思いつかなかったので外部から更新用メソッドを呼ぶ形にしました。
 何か良い方法はないかなぁ。
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

カレンダー
05 | 2015/06 | 07
- 1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 - - - -
全記事表示リンク

全ての記事を表示する

カテゴリ
タグリスト

月別アーカイブ
08  07  06  05  04  03  02  01  12  11  10  09  08  07  06  04  03  02  01  12  11  10  09  08  07  06  05  04  03  02  01  12  11  10  09 
最新記事
リンク
最新コメント
検索フォーム
Amazon