動的に入力する必要があるDatagridがあります。
Tablelayoutは次のようなものです。
id | image | name | Description | Name-1 | Name-N
最初の4列は静的で、他の4列は動的です。ユーザーは、必要な数のユーザーを追加できる必要があります。
複数のユーザーのデータを表に並べて比較します。
現在、動的に生成された列の名前と静的な列を埋めるメソッドを含むリストボックスがあります。各ユーザーのデータを読み込むこともできます。次に、それらを1つの大きなテーブルにマージする必要があります。
主な問題は、「ユーザーデータ」と静的コンテンツを1つのデータグリッドに配置する方法です。
これを行うには、少なくとも3つの方法があります。
CustomTypeDescriptorを使用する
*簡単にするために推奨
最初のアプローチ:コードビハインドを使用して、実行時にDataGridの列を生成します。これは簡単に実装できますが、MVVMを使用している場合は特に、少しハック感があります。したがって、固定列を持つDataGridがあります。
<DataGrid x:Name="grid">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding id}" Header="id" />
<DataGridTextColumn Binding="{Binding image}" Header="image" />
</DataGrid.Columns>
</DataGrid>
「名前」の準備ができたら、列を追加/削除してグリッドを変更します。例:
// add new columns to the data grid
void AddColumns(string[] newColumnNames)
{
foreach (string name in newColumnNames)
{
grid.Columns.Add(new DataGridTextColumn {
// bind to a dictionary property
Binding = new Binding("Custom[" + name + "]"),
Header = name
});
}
}
元のクラスを含むラッパークラスと、カスタムプロパティを含むディクショナリを作成する必要があります。あなたのメイン行クラスが「ユーザー」であるとしましょう、そしてあなたはこのようなラッパークラスが欲しいでしょう:
public class CustomUser : User
{
public Dictionary<string, object> Custom { get; set; }
public CustomUser() : base()
{
Custom = new Dictionary<string, object>();
}
}
ItemsSource
に、この新しい「CustomUser」クラスのコレクションを入力します。
void PopulateRows(User[] users, Dictionary<string, object>[] customProps)
{
var customUsers = users.Select((user, index) => new CustomUser {
Custom = customProps[index];
});
grid.ItemsSource = customUsers;
}
たとえば、それを一緒に結び付けます:
var newColumnNames = new string[] { "Name1", "Name2" };
var users = new User[] { new User { id="First User" } };
var newProps = new Dictionary<string, object>[] {
new Dictionary<string, object> {
"Name1", "First Name of First User",
"Name2", "Second Name of First User",
},
};
AddColumns(newColumnNames);
PopulateRows(users, newProps);
2番目のアプローチ:DataTable を使用します。これにより、内部でカスタムタイプのインフラストラクチャが使用されますが、使いやすくなります。 DataGridのItemsSource
をDataTable.DefaultView
プロパティにバインドするだけです:
<DataGrid ItemsSource="{Binding Data.DefaultView}" AutoGenerateColumns="True" />
次に、好きなように列を定義できます。例:
Data = new DataTable();
// create "fixed" columns
Data.Columns.Add("id");
Data.Columns.Add("image");
// create custom columns
Data.Columns.Add("Name1");
Data.Columns.Add("Name2");
// add one row as an object array
Data.Rows.Add(new object[] { 123, "image.png", "Foo", "Bar" });
3番目のアプローチ:.Netの型システムの拡張性を利用します。具体的には、 CustomTypeDescriptor
を使用します。これにより、実行時にカスタムタイプを作成できます。これにより、タイプにプロパティ「Name1」、「Name2」、...「NameN」、またはその他のプロパティがあることをDataGridに伝えることができます。このアプローチの簡単な例については、 here をご覧ください。
2番目のアプローチ:DataTableを使用します。これにより、内部でカスタムタイプのインフラストラクチャが使用されますが、使いやすいです。 DataGridのItemsSourceをDataTable.DefaultViewプロパティにバインドするだけです:
これはほとんど機能しましたが、DataTable.DefaultViewプロパティプロパティにバインドする代わりに、DataView型のプロパティを作成し、それにバインドしました。
<DataGrid ItemsSource="{Binding DataView, Mode=TwoWay}" AutoGenerateColumns="True" />
これにより、バインディングを双方向にすることができます。DataTable.DefaultViewへのバインディングをTwoWayバインディングにすることはできません。ビューモデルで
public DataView DataView
{
get { return _dataView; }
set
{
_dataView = value;
OnPropertyChanged("DataView");
}
}
このセットアップでは、ビューモデルの初期化時に列を動的に定義できるだけでなく、いつでもデータテーブルを動的に更新および変更できました。上記のMcGarnagleで定義されているアプローチを使用すると、DataTableが新しいデータソースで更新されたときにビュースキーマが更新されませんでした。
私は現在別のアプローチを使用していますが、このようにするのが正しいかどうかはわかりませんが、うまくいきます。小さなサンプルを作りました。
これが機能するためには、Datagridのすべてのエントリが同じ動的列を持つ必要があるため、少し柔軟性が低いことに注意してください。ただし、各エントリに異なる量の列を持つエントリがある場合、Datagridはおそらく間違ったアプローチです。
これらは私のクラスです
public class Person
{
public ObservableCollection<Activity> Hobbys { get; set; }
public string Name { get; set; }
}
public class Activity
{
public string Name { get; set; }
}
そして、これはコードビハインドです。
public MainWindow()
{
InitializeComponent();
DataContext = this;
ObservableCollection<Activity> hobbys = new ObservableCollection<Activity>();
hobbys.Add(new Activity() { Name = "Soccer" });
hobbys.Add(new Activity() { Name = "Basketball" });
Community = new ObservableCollection<Person>();
Community.Add(new Person() { Name = "James", Hobbys = hobbys });
Community.Add(new Person() { Name = "Carl", Hobbys = hobbys });
Community.Add(new Person() { Name = "Homer", Hobbys = hobbys });
MyGrid.Columns.Add(new DataGridTextColumn() { Header = "Name", Binding = new Binding("Name") }); //Static Column
int x = 0;
foreach (Activity act in Community[0].Hobbys) //Create the dynamic columns
{
MyGrid.Columns.Add(new DataGridTextColumn() { Header = "Activity", Binding = new Binding("Hobbys["+x+"].Name") });
x++;
}
}
そして、.XAMLで単純に:
<DataGrid Name="MyGrid" ItemsSource="{Binding Community}" AutoGenerateColumns="False"/>
それを単一の大きなDataGrid(table)に表示する必要がない場合は、DataGrid with id、image、name、Descriptionがあり、レコードの1つがそのDataGridで選択されている場合、その選択されたレコードに関連する画像の名前でListBoxを表示/更新します