WPF GridViewColumn.CellTemplate DataTemplate ContentPresenter - wpf

I have a ListView which uses DataTemplates. If i use this in a ListView defined the columns over XAML it works how it schould. My DataTemplates are used in my view. But if i want to use the same DataTemplates in a second ListView, where i add new columns to the ListView it does not use my DataTemplate. What should i Do?
The code in XAML for the first ListView looks like this:
<GridViewColumn x:Name="lvSecondColumn" Header="Value" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
My Code i use for generating a column in second ListView is:
DataColumn dc = (DataColumn)colum;
GridViewColumn column = new GridViewColumn( );
column.DisplayMemberBinding = new Binding( dc.ColumnName ) );
column.Header = dc.ColumnName;
TestColumns.Columns.Add( column );
TestListView.ItemsSource = dt.DefaultView;
In WPFInspector i see there is no ContentPresenter in my dynamic Generated Column.
Picture from missing ContentPresenter from WPFInspector
How to add the ContentPresenter to my dynamic column???

You can't set both Binding and DataTemplate. According to the docs
https://msdn.microsoft.com/en-us/library/system.windows.controls.gridviewcolumn.displaymemberbinding(v=vs.110).aspx
The following properties are all used to define the content and style
of a column cell, and are listed here in their order of precedence,
from highest to lowest:
- DisplayMemberBinding
- CellTemplate
- CellTemplateSelector
If you use binding then it will generate a textbox with a ".ToString()" of the bound object. If you are aware of the structure of your items in the ListView you can just make DataTemplates with appropriate bindings in it. However when generating columns dynamically this is an issue.
You can dynamically generate a datatemplate for your column and integrate the binding in it:
public DataTemplate CreateColumnTemplate(string property)
{
StringReader stringReader = new StringReader(
#"<DataTemplate
xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"">
<ContentPresenter Content=""{Binding " + property + #"}""/>
</DataTemplate>");
XmlReader xmlReader = XmlReader.Create(stringReader);
return XamlReader.Load(xmlReader) as DataTemplate;
}
Then you can generate your columns like this:
GridViewColumn column = new GridViewColumn( );
column.CellTemplate = CreateColumnTemplate(dc.ColumnName);
column.Header = dc.ColumnName;
TestColumns.Columns.Add( column );
I didn't run the code there may be little mistakes.

Related

Performance issues with CellTemplate of GridViewColumn

I am using a ListView with it's View set to GridView to display items in dynamically generated columns. The ListView's virtualization is enabled. For this example I want to display up to 10 rows with 50 columns (but the count of columns is dynamic).
The first thing I do is to create the columns in code behind in a loop.
"Items" is just an ObservableCollection of strings in my view model for one row.
int index = 0;
foreach(Header header in Headers)
{
GridViewColumn column = new GridViewColumn();
column.Header = // the view model of the header;
column.HeaderTemplate = Resources["HeaderTemplate"] as DataTemplate;
column.DisplayMemberBinding = new Binding("Items" + "[" + index + "]");
m_MyGridView.Columns.Add(column);
index++;
}
Now, when I change the ItemsSource of my ListView, the data gets updated quiet fast.
Unfortunately, each string in every cell has to be formatted (font color, foreground etc). So I cannot use DisplayMemberBinding, but CellTemplate to assign a DataTemplate to every cell.
For this purpose I create a DataTemplate in xaml in my control that holds the ListView. This contains just a TextBlock with some bindings to properties of the cell's view model to define the string style of the TextBlock.
Now, Items is not just a collection of string but a collection of CellViewModels that not only contain the value to display but also information of how to display it. This information is then bound inside the DataTemplate.
The CellTemplate is set up in code and assigned to each GridViewColumn's CellTemplate:
foreach(Header header in Headers)
{
GridViewColumn column = new GridViewColumn();
column.Header = // the view model of the header;
column.HeaderTemplate = Resources["HeaderTemplate"] as DataTemplate;
column.CellTemplate = createCellTemplate(index);
m_MyGridView.Columns.Add(column);
index++;
}
DataTemplate getCellTemplate(int index)
{
FrameworkElementFactory elementFactory = new FrameworkElementFactory (typeof (ContentControl));
elementFactory.SetBinding(ContentControl.ContentProperty, new Binding("Items" + "[" + index + "]"));
DataTemplate dt = Resources["ValueTemplate"] as DataTemplate;
elementFactory.SetValue(ContentControl.ContentTemplateProperty, dt);
DataTemplate dataTemplate = new DataTemplate ();
dataTemplate.VisualTree = elementFactory;
return dataTemplate;
}
When I do this, things get really slow when I'm changing the ItemsSource of the ListView. It now takes nearly a second to render the new cells each time I change the ItemsSource to display different data.
So is there a way to speed things up? I thought about having the formatted string already in my CellViewModel by using a Paragraph, but unfortunately DisplayMemberBinding only supports string, so I have to use the CellTemplate.
Edit: so, here is the listview i'm using:
<ListView ItemsSource="{Binding ElementName=Control, Path=ItemViewModels}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" VirtualizingStackPanel.IsVirtualizing="True" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.View>
<GridView x:Name="m_MyGridView"/>
</ListView.View>
</ListView>
and the DataTemplate for the cells:
<DataTemplate x:Key="ValueTemplate">
<Border Background="{Binding Path=BackgroundColor">
<TextBlock Text="{Binding Path=Value}"
Foreground="{Binding Path=ForegroundColor}"
FontStyle="{Binding Path=FontStyle}"
FontWeight="{Binding Path=FontWeight}"
</TextBlock>
</Border>
</DataTemplate>
The function getCellTemplate is called only once when the headers are created. When the ItemsSource changes, it is not called, because they have been created already.

Set Binding for DataTemplate's elements programmatically

I have such a template
<Window.Resources>
<DataTemplate x:Key="MemberCoefDataTemplate">
<StackPanel>
<CheckBox Name="CheckBox"></CheckBox>
<TextBox Name="TextBox"></TextBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
And i use it in a grid
<DataGridTemplateColumn CellTemplate="{StaticResource MemberCoefDataTemplate}" />
I need to dynamically add columns to the grid. As Binding i use indexer property. So i need to set binding dynamically, because i don't know to which index bind to. When i just for test tried this
var column = new DataGridTemplateColumn();
column.CellTemplate = (DataTemplate)Application.Current.MainWindow.Resources["MemberCoefDataTemplate"];
TextBox tb = column.CellTemplate.FindName("TextBox", dg) as TextBox;
I got InvalidOperationException wit description:
this operation is valid only on elements that have this template applied
Usually to get the control during the setup time, you would have to call the method LoadContent();
I would therefore try column.CellTemplate.LoadContent().
You can find a full explanation about your problem (and a solution of course) written by Josh Smith here on his blog.
Indeed this is the point where the DataGridTemplateColumn applies the DataTemplate:
private FrameworkElement LoadTemplateContent(bool isEditing, object dataItem, DataGridCell cell)
{
DataTemplate dataTemplate;
DataTemplateSelector dataTemplateSelector;
this.ChooseCellTemplateAndSelector(isEditing, out dataTemplate, out dataTemplateSelector);
if (dataTemplate != null || dataTemplateSelector != null)
{
ContentPresenter contentPresenter = new ContentPresenter();
BindingOperations.SetBinding(contentPresenter, ContentPresenter.ContentProperty, new Binding());
contentPresenter.ContentTemplate = dataTemplate;
contentPresenter.ContentTemplateSelector = dataTemplateSelector;
return contentPresenter;
}
return null;
}
As you can see a ContentPresenter is used.

WPF data from source on a datagrid not showing up when using templated columns

At work we lost our team member who did all the wpf and I have picking up where he left off. This is the first time I have worked on wpf so I really don't know too much.
I have a two dimensional array of doubles that have a dynamic number of rows and columns. I want to display this as a datagrid. If I set autogeneratecolumns to true then everything works fine, except that I need the Columns to use a template I have made.
So I handle the autogeneratingcolumn event and make a new templated column with the template I want. The problem is that now my data will not show up in the grid.
This is my datagrid.
<controls:DataGridEx Grid.Row="11" Grid.ColumnSpan="9"
Name="_gridIntensityMap"
CanUserAddRows="False"
CanUserSortColumns="False"
AutoGenerateColumns="True"
AlternatingRowBackground="LightGray"
ItemsSource="{Binding Path=IntensityMap, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"
AutoGeneratedColumns="OnAutoGeneratedIntensityMap"
AutoGeneratingColumn ="IntensityGridAutoGeneratingColumn">
</controls:DataGridEx>
Here is how I handle the autogeneratingcolumn event:
private void IntensityGridAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
templateColumn = new DataGridTemplateColumn();
templateColumn.CellTemplate = (DataTemplate)Resources["IntensityNumberBoxTemplate"];
templateColumn.CellEditingTemplate = (DataTemplate)Resources["IntensityNumberBoxTemplate"];
// set each column to width = "1*" - causes them all to expand equally to fill space
templateColumn.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
//replace the generated column with our templated one.
e.Column = templateColumn;
}
And here is the template I am using:
<DataTemplate x:Key="IntensityNumberBoxTemplate" x:Name="IntensityNumberBoxTemplate">
<controls:NumberBox
Units = "CM2"
ToolTipService.IsEnabled="False"
IsReadOnly="False"
TextAlignment="Right" />
</DataTemplate>
As I said before the problem is that after using the template the data is now not showing up in the grid, the grid is blank.
Any help would be much appreciated.
I don't know much about that particular data grid control you're using, but inside that DataTemplate, you should have a binding set so the NumberBox knows where to retrieve a value from.
NumberBox probably has a Value property or something on it. Set it to Value="{Binding}" and see how that works.

WPF Dynamically Setting CellTemplate and Binding

I have a GridView control and I am adding GridViewColumns. I was using DisplayMemberBinding property of GridViewColumn but now I want to use the CellTemplate. I am binding to a dictionary.
The following code worked with DisplayMemberBinding:
var column = new GridViewColumn
{
Header = current.Key,
DisplayMemberBinding = new Binding("[" + current.Key + ]")
};
Now, I need to do the same with CellTemplate but for some reason I am not sure why it is not displaying the items.
var column = new GridViewColumn
{
Header = current.Key,
CellTemplate = (DataTemplate)FindResource("GridViewTextBlockDataTemplate"),
};
And here is the DataTemplate defined in Window.Resources:
<DataTemplate x:Key="GridViewTextBlockDataTemplate" x:Name="GridViewTextBlockDataTemplate">
<TextBlock Text="{Binding Path=[Key]}"></TextBlock>
</DataTemplate>
Thanks,
Azam
Your solution is correct except that to get the value in the textblock you just need to have Keyword Binding and no need to provide path as you are binding the datatemplate, it automatically assigns the value.
If that does not work try to use datatemplate selector for the datagrid and For a detailed explanation on datatemplate selector please have a look a these link:
WPF DataTemplate Selector Tutorial

Programmatically create WPF DataGridTemplateColumn for DataGrid

I would like to be able to programmatically create DataGridTemplateColumns based on my data source. For example, if my source has a date in a particular column I would like to be able to utilize a Datepicker control. I know this is easily accomplished with xaml and a DataGridTemplateColumn at design-time, however, how would I accomplish this at run-time?
Is my best option xamlreader.load or a more traditional route like:
Dim TempCol As Microsoft.Windows.Controls.DataGridTemplateColumn
I have not had any success with the latter.
Thanks.
-Paul
Edit:
This is the code I attempted to use:
Dim TempCol As New Microsoft.Windows.Controls.DataGridTemplateColumn
TempCol.CellEditingTemplate = DataTemplate.Equals(DatePicker)
I receive DatePicker is a type and cannot be used as an expression.
I am basiing this on the WPF Toolkit demo.
http://windowsclient.net/wpf/wpf35/wpf-35sp1-toolkit-datagrid-feature-walkthrough.aspx
<dg:DataGridTemplateColumn Header="Date" MinWidth="100">
<dg:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<dg:DatePicker SelectedDate="{Binding Date}" SelectedDateFormat="Short" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellEditingTemplate>
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date, StringFormat=d}" />
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
Thanks!
The reason that your code does not work is because you are setting the value of the CellEditingTemplate column to a bool (the result of calling DataTemplate.Equals(), rather than creating an instance of the template in code.
You can create a template in code using something like this (equivalent to the XAML code snippet you provided):
DataGridTemplateColumn col = new DataGridTemplateColumn();
col.Header = "Date";
// Create a factory. This will create the controls in each cell of this
// column as needed.
FrameworkElementFactory factory =
new FrameworkElementFactory(typeof(DatePicker));
// Bind the value of this cell to the value of the Date property of the
// DataContext of this row. The StringFormat "d" will be used to display
// the value.
Binding b = new Binding("Date");
b.StringFormat = "d";
factory.SetValue(DatePicker.SelectedDateProperty, b);
// Create the template itself, and add the factory to it.
DataTemplate cellEditingTemplate = new DataTemplate();
cellEditingTemplate.VisualTree = factory;
col.CellEditingTemplate = cellEditingTemplate;
I'm not sure if this approach would work better than loading the XAML yourself. Maybe try both approaches and see which one works best for you, and works faster?

Resources