template - wpf listview grouping
WPF GridView con una definición dinámica (6)
Puede agregar GridViewColumns a GridView dinámicamente dado el primer conjunto utilizando un método como este:
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))
});
}
}
Supongo que la segunda matriz que contiene los valores será de longitud ROWS * COLUMNS. En ese caso, sus artículos pueden ser matrices de cuerdas de longitud COLUMNAS. Puede usar Array.Copy o LINQ para dividir la matriz. El principio se demuestra aquí:
<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>
Quiero usar el modo GridView de un ListView para mostrar un conjunto de datos que mi programa recibirá de una fuente externa. Los datos consistirán en dos matrices, uno de los nombres de columna y uno de cadenas para completar el control.
No veo cómo crear una clase adecuada que pueda usar como elemento en un ListView. La única forma que conozco de rellenar los Elementos es configurarlo en una clase con propiedades que representen las columnas, pero no tengo conocimiento de las columnas antes del tiempo de ejecución.
Podría crear un ItemTemplate dinámicamente como se describe en: Crear WPF ItemTemplate DYNAMICALLY en tiempo de ejecución, pero aún no sé cómo describir los datos reales.
Cualquier ayuda recibida con gratitud
Gracias, eso es muy útil.
Lo usé para crear una versión dinámica de la siguiente manera. Creé los títulos de las columnas como sugirió:
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;
}
Configure el ListView muy simplemente en XAML:
<ListView Name="myListview" DockPanel.Dock="Left"/>
Creé una clase contenedora para ObservableCollection para contener mis datos:
public class MyCollection : ObservableCollection<List<String>>
{
public MyCollection()
: base()
{
}
}
Y atado mi ListView a él:
results = new MyCollection();
Binding binding = new Binding();
binding.Source = results;
myListview.SetBinding(ListView.ItemsSourceProperty, binding);
Luego, para llenarlo, fue solo un caso de borrar cualquier dato viejo y agregar el nuevo:
results.Clear();
List<String> details = new List<string>();
for (int ii=0; ii < externalDataCollection.Length; ii++)
{
details.Add(externalDataCollection[ii]);
}
results.Add(details);
Probablemente haya formas más claras de hacerlo, pero esto es muy útil para mi aplicación. Gracias de nuevo.
Este artículo en CodeProject explica exactamente cómo crear un ListView dinámico, cuando los datos solo se conocen en tiempo de ejecución. http://www.codeproject.com/KB/WPF/WPF_DynamicListView.aspx
No estoy seguro de si todavía es relevante, pero encontré una forma de diseñar las celdas individuales con un selector de plantilla de celda. Es un poco hacky porque tienes que masticar el Contenido del ContentPresenter para obtener el DataContext adecuado para la celda (para que puedas enlazar al elemento de la celda real en la plantilla de la celda):
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;
}
}
Nota: Cambié DataMatrix del artículo CodeProject para que las filas sean Dictionaries (ColumnName -> Cell Object).
No puedo garantizar que esta solución no rompa algo o que no se rompa en el futuro. Lanzamiento de .Net. Se basa en el hecho de que ContentPresenter establece el DataContext después de que seleccionó la plantilla para su propio Contenido. (Reflector ayuda mucho en estos casos :))
Al crear GridColumns, hago algo como eso:
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);
Nota: He extendido ListView para que tenga una propiedad CellTemplateSelector a la que pueda vincularse en xaml
@Editar 15/03/2011: Escribí un pequeño artículo que tiene un pequeño proyecto de demostración adjunto: http://codesilence.wordpress.com/2011/03/15/listview-with-dynamic-columns/
Versión totalmente progmática:
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++;
}
Haría esto añadiendo AttachedProperty a GridView, donde mi aplicación MVVM puede especificar las columnas (y tal vez algunos metadatos adicionales). El código de comportamiento puede funcionar dinámicamente directamente con el objeto GridView para crear las columnas. De esta forma, se adhiere a MVVM y ViewModel puede especificar las columnas sobre la marcha.