Reputation: 80
I'm trying to change the template of a cell inside a DataGrid
depending on a List<List<int>>
which holds the type of the cell (I.e 1=bool, 2=int, 3=string, 4=custom, etc...). The custom types (types because they can be more than 1) have to be represented by a ComboBox
. For numbers and strings, I need a normal TextBox
and for boolean I need a CheckBox
. The DataGrid
is binded to a DataTable
which I can resize and edit at runtime. Here it is some code:
<Grid>
<DataGrid ItemsSource="{Binding Path=DataTable}" Name="Grid" AutoGenerateColumns="True"
CanUserResizeRows="True" CanUserDeleteRows="False"
CanUserAddRows="False" AreRowDetailsFrozen="False"
SelectionUnit="Cell" LoadingRow="Grid_LoadingRow">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="AlternatingRowBackground" Value="LightYellow"/>
</Style>
</DataGrid.Style>
</DataGrid>
</Grid>
public partial class TableEditorWindow : Window
{
public string[] DebugNames = { "Bob", "Dan", "Pierre", "Mark", "Gary" };
// Stores the values of the Table
public ds_grid Table { get; set; }
// Stores the types of each cell in the Table
public ds_grid ValueTypesTable { get; set; }
// Used as wrapper between the Table variable and the DataGrid
public DataTable DataTable { get; set; }
public TableEditorWindow()
{
InitializeComponent();
Table = new ds_grid(5, 5);
// Fills the Table with 1s
for (int i = 0; i < 5; ++i)
{
for (int j = 0; j < Table.Width; ++j)
{
Table.Set(i, j, 1d);
}
}
DataTable = new DataTable();
// Add the columns
for (int i = 0; i < 5; ++i)
{
DataTable.Columns.Add(DebugNames[i]);
}
// Add the rows
for (int i = 0; i < Table.Height; ++i)
{
DataRow _row = DataTable.NewRow();
for (int j = 0; j < Table.Width; ++j)
{
_row[j] = Table.Get(j, i);
}
DataTable.Rows.Add(_row);
}
Grid.DataContext = this;
Grid.RowHeaderWidth = 50;
Grid.ColumnWidth = 100;
}
// Gives to each row the correct name
private void Grid_LoadingRow(object sender, DataGridRowEventArgs e)
{
int _id = e.Row.GetIndex();
e.Row.Header = DebugNames[_id];
}
}
ds_grid
is basically a List<List<object>>
with some utility methods around it.
I saw that there are some solutions, such as using DataTrigger, but I think that in that case I'd need to write in in the DataGrid
in the XAML file, but I can't because AutoGenerateColumns
is True
. There is also the possibility to change the Type of each column of the DataTable
but I don't want that every cell of that column is of that type, I want that only a single cell becomes of that type, at runtime.
Maybe there are better solutions, such as not using a DataGrid
, or not using a DataTable
, or there is a way to set AutoGenerateColumns
to False
and manually generating every column when needed, by code. Any suggestion is really appreciated.
Thanks in advance.
Upvotes: 1
Views: 1665
Reputation: 6766
This is different enough from my original answer that I'm submitting it separately.
I also want to point out that is a very unconventional use of a DataGrid
. Common data structure is that each column has a single type and I've hardly ever needed otherwise. My previous answer works if you stick with that convention. That being said, what you're asking for can be done.
If you really want to disregard common data structure and customize things on the cell level, you'll need a custom DataGridColumn
:
public class DataTableBoundColumn : DataGridBoundColumn
{
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
if (dataItem == CollectionView.NewItemPlaceholder) { return null; }
DataRowView dataRow = (dataItem as DataRowView);
if (dataRow == null) { throw new ArgumentException(); }
object cellData = dataRow[cell.Column.DisplayIndex];
var contentHost = new ContentControl() { Content = cellData };
//Do some tests on cellData to determine the type and pick a DataTemplate
//Alternatively, you could build the actual content here in code-behind, but a DataTemplate would probably be cleaner
contentHost.ContentTemplate = (DataTemplate)SomeResourceDictionary["SomeResourceKey"];
return contentHost;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
return GenerateElement(cell, dataItem);
}
}
The above is based off the example from this article. Using this column type, your AutoGeneratingColumn
handler would be as follows:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataTableBoundColumn col = new DataTableBoundColumn();
e.Column = col;
e.Column.Header = "Whatever you want";
}
Upvotes: 2
Reputation: 6766
If you want to customize the auto-generated columns you will have to use the DataGrid.AutoGeneratingColumn
event. I understand that you don't want the entity of a column to be the same, but you'll still need to use this event- you just need to use it a bit differently.
You were on the right track with thinking of Style
s and DataTrigger
s to dynamically change the cell template. This would usually be done by declaring the columns in XAML, but you could get around that by using DataGrid.AutoGeneratingColumn
and declaring your column as a resource.
Take something like this:
<DataGrid>
<DataGrid.Resources>
<DataGridTemplateColumn x:Key="TemplateColumn" x:Shared="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<!--Use DataTriggers to set the content to whatever you need-->
<DataTrigger>
<!--...-->
</DataTrigger>
<DataTrigger>
<!--...-->
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Resources>
</DataGrid>
The above is a DataTemplate
that uses a ContentControl
with DataTrigger
s to dynamically set the ContentTemplate
. I defined it as resource of the DataGrid
with an x:Key
. x:Shared="False"
means that WPF will create a new instance of this column whenever the resource is requested, instead of creating one and giving out references to that single instance so users can "share" it. This will let you add multiple instances of the column to the DataGrid
.
Your AutoGeneratingColumn
would be something like this:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
//Add an if statement if you only want to replace some of the columns with the dynamic template one
var DG = (DataGrid)sender;
DataGridTemplateColumn col = (DataGridTemplateColumn)DG.Resources["TemplateColumn"];
e.Column = col;
e.Column.Header = "Whatever you want";
}
This replaces your column with an insance of the TemplateColumn
resource.
Upvotes: 0