ListViewのGridViewモードを使用して、プログラムが外部ソースから受信するデータのセットを表示したいと思います。データは2つの配列で構成され、1つは列名、もう1つは文字列値でコントロールに入力されます。
ListViewのアイテムとして使用できる適切なクラスを作成する方法がわかりません。アイテムを設定する唯一の方法は、列を表すプロパティを持つクラスに設定することですが、実行前に列についての知識がありません。
次のように、ItemTemplateを動的に作成できます。 実行時に動的にWPF ItemTemplateを作成する それでも、実際のデータをどのように記述するかについて迷っています。
ありがたいことにどんな助けも受けました。
次のような方法を使用して、最初の配列を指定して、GridViewColumnsをGridViewに動的に追加できます。
private void AddColumns(GridView gv, string[] columnNames)
{
for (int i = 0; i < columnNames.Length; i++)
{
gv.Columns.Add(new GridViewColumn
{
Header = columnNames[i],
DisplayMemberBinding = new Binding(String.Format("[{0}]", i))
});
}
}
値を含む2番目の配列はROWS * COLUMNSの長さであると想定しています。その場合、アイテムは長さCOLUMNSの文字列配列にすることができます。 Array.CopyまたはLINQを使用して、配列を分割できます。原理はここに示されています:
<Grid>
<Grid.Resources>
<x:Array x:Key="data" Type="{x:Type sys:String[]}">
<x:Array Type="{x:Type sys:String}">
<sys:String>a</sys:String>
<sys:String>b</sys:String>
<sys:String>c</sys:String>
</x:Array>
<x:Array Type="{x:Type sys:String}">
<sys:String>do</sys:String>
<sys:String>re</sys:String>
<sys:String>mi</sys:String>
</x:Array>
</x:Array>
</Grid.Resources>
<ListView ItemsSource="{StaticResource data}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=[0]}" Header="column1"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=[1]}" Header="column2"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=[2]}" Header="column3"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
おかげで、それは非常に役に立ちます。
次のように動的バージョンを作成するために使用しました。あなたが提案したように、私は列見出しを作成しました:
private void AddColumns(List<String> myColumns)
{
GridView viewLayout = new GridView();
for (int i = 0; i < myColumns.Count; i++)
{
viewLayout.Columns.Add(new GridViewColumn
{
Header = myColumns[i],
DisplayMemberBinding = new Binding(String.Format("[{0}]", i))
});
}
myListview.View = viewLayout;
}
XAMLでListViewを非常に簡単に設定します。
<ListView Name="myListview" DockPanel.Dock="Left"/>
データを保持するためにObservableCollectionのラッパークラスを作成しました。
public class MyCollection : ObservableCollection<List<String>>
{
public MyCollection()
: base()
{
}
}
そして、私のListViewをそれにバインドしました:
results = new MyCollection();
Binding binding = new Binding();
binding.Source = results;
myListview.SetBinding(ListView.ItemsSourceProperty, binding);
次に、データを入力するために、古いデータをクリアして新しいデータを追加する場合でした。
results.Clear();
List<String> details = new List<string>();
for (int ii=0; ii < externalDataCollection.Length; ii++)
{
details.Add(externalDataCollection[ii]);
}
results.Add(details);
おそらくそれを行うためのより良い方法がありますが、これは私のアプリケーションにとって非常に便利です。再度、感謝します。
CodeProjectに関するこの記事では、動的リストビューを作成する方法を正確に説明しています。データが実行時にのみ認識される場合です。 http://www.codeproject.com/KB/WPF/WPF_DynamicListView.aspx
それがまだ関連しているかどうかはわかりませんが、セルテンプレートセレクターを使用して個々のセルのスタイルを設定する方法を見つけました。セルの適切なDataContextを取得するためにContentPresenterのコンテンツをいじくり回す必要があるため(セルテンプレート内の実際のセルアイテムにバインドできるように)、少しハッキーです。
public class DataMatrixCellTemplateSelectorWrapper : DataTemplateSelector
{
private readonly DataTemplateSelector _ActualSelector;
private readonly string _ColumnName;
private Dictionary<string, object> _OriginalRow;
public DataMatrixCellTemplateSelectorWrapper(DataTemplateSelector actualSelector, string columnName)
{
_ActualSelector = actualSelector;
_ColumnName = columnName;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
// The item is basically the Content of the ContentPresenter.
// In the DataMatrix binding case that is the dictionary containing the cell objects.
// In order to be able to select a template based on the actual cell object and also
// be able to bind to that object within the template we need to set the DataContext
// of the template to the actual cell object. However after the template is selected
// the ContentPresenter will set the DataContext of the template to the presenters
// content.
// So in order to achieve what we want, we remember the original DataContext and then
// change the ContentPresenter content to the actual cell object.
// Therefor we need to remember the orginal DataContext otherwise in subsequent calls
// we would get the first cell object.
// remember old data context
if (item is Dictionary<string, object>)
{
_OriginalRow = item as Dictionary<string, object>;
}
if (_OriginalRow == null)
return null;
// get the actual cell object
var obj = _OriginalRow[_ColumnName];
// select the template based on the cell object
var template = _ActualSelector.SelectTemplate(obj, container);
// find the presenter and change the content to the cell object so that it will become
// the data context of the template
var presenter = WpfUtils.GetFirstParentForChild<ContentPresenter>(container);
if (presenter != null)
{
presenter.Content = obj;
}
return template;
}
}
注:行が辞書になるように、CodeProjectの記事からDataMatrixを変更しました(ColumnName-> Cell Object)。
このソリューションが何かを壊したり、将来の.Netリリースで壊れたりしないことを保証することはできません。これは、ContentPresenterがテンプレートを選択した後にDataContextを独自のContentに設定するという事実に依存しています。 (これらの場合、リフレクターは大いに役立ちます:))
GridColumnsを作成するとき、私は次のようなことをします。
var column = new GridViewColumn
{
Header = col.Name,
HeaderTemplate = gridView.ColumnHeaderTemplate
};
if (listView.CellTemplateSelector != null)
{
column.CellTemplateSelector = new DataMatrixCellTemplateSelectorWrapper(listView.CellTemplateSelector, col.Name);
}
else
{
column.DisplayMemberBinding = new Binding(string.Format("[{0}]", col.Name));
}
gridView.Columns.Add(column);
注:ListViewを拡張して、xamlでバインドできるCellTemplateSelectorプロパティを追加しました
@Edit 15/03/2011:小さなデモプロジェクトが添付された小さな記事を書きました: http://codesilence.wordpress.com/2011/03/15/listview-with-dynamic-columns/
完全にprogmaticバージョン:
var view = grid.View as GridView;
view.Columns.Clear();
int count=0;
foreach (var column in ViewModel.GridData.Columns)
{
//Create Column
var nc = new GridViewColumn();
nc.Header = column.Field;
nc.Width = column.Width;
//Create template
nc.CellTemplate = new DataTemplate();
var factory = new FrameworkElementFactory(typeof(System.Windows.Controls.Border));
var tbf = new FrameworkElementFactory(typeof(System.Windows.Controls.TextBlock));
factory.AppendChild(tbf);
factory.SetValue(System.Windows.Controls.Border.BorderThicknessProperty, new Thickness(0,0,1,1));
factory.SetValue(System.Windows.Controls.Border.MarginProperty, new Thickness(-7,0,-7,0));
factory.SetValue(System.Windows.Controls.Border.BorderBrushProperty, Brushes.LightGray);
tbf.SetValue(System.Windows.Controls.TextBlock.MarginProperty, new Thickness(6,2,6,2));
tbf.SetValue(System.Windows.Controls.TextBlock.HorizontalAlignmentProperty, column.Alignment);
//Bind field
tbf.SetBinding(System.Windows.Controls.TextBlock.TextProperty, new Binding(){Converter = new GridCellConverter(), ConverterParameter=column.BindingField});
nc.CellTemplate.VisualTree = factory;
view.Columns.Add(nc);
count++;
}
MVVMアプリケーションが列(およびおそらくいくつかの追加のメタデータ)を指定できるGridViewにAttachedPropertyを追加することでこれを実行します。その後、動作コードはGridViewオブジェクトと直接動的に連携して列を作成できます。このようにして、MVVMに準拠し、ViewModelはその場で列を指定できます。