Set Binding for DataTemplate's elements programmatically - wpf

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.

Related

WPF GridViewColumn.CellTemplate DataTemplate ContentPresenter

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.

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.

Binding dynamically generated textblock WPF MVVM Light

I am using MVVMM Light WPF and I want to do the following: Generate textboxes dynamically and bind them to a property of a class.
I already have the following but it doesn't show up in my view when running the application.
This is my collection:
private ObservableCollection<Border> _controllekes;
public ObservableCollection<Border> Controllekes
{
get { return _controllekes; }
set
{
_controllekes = value;
RaisePropertyChanged("Controllekes");
}
}
This it my xaml:
<ItemsControl ItemsSource="{Binding Path=Controllekes}">
</ItemsControl>
This is a part where I fill the itemsource "Controllekes":
Controllekes = new ObservableCollection<Border>();
Border border = new Border();
border.BorderThickness = new System.Windows.Thickness(5);
border.BorderBrush = Brushes.AliceBlue;
border.Padding = new System.Windows.Thickness(5);
TextBlock tb = new TextBlock();
tb.Background = Brushes.Red;
Binding nameTextBinding = new Binding("Controllekes");
nameTextBinding.Path = new System.Windows.PropertyPath(this.Dossier.Omschrijving);
nameTextBinding.Mode = BindingMode.OneWay;
//nameTextBinding.Source = this.Dossier.Omschrijving;
tb.SetBinding(TextBlock.TextProperty, nameTextBinding);
border.Child = tb;
this.Controllekes.Add(border);
What it does it creates a border with in this border a textblock where the binding should happen. I whish to bind the property this.Dossier.Omschrijving (Dossier is the class). If I just enter a string in the textbox it works.
In runtime the border gets generated but the textblock remains empty. The object Dossier.Omschrijving contains information.
What do I do wrong?
EDIT:
safe put me in the right direction and the answer of ItemsControl with multiple DataTemplates for a viewmodel made me finish the job :)
through all that and use ItemTemplate
<ItemsControl x:Name="ic">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding yourboolProperty}"/>
<TextBlock Background="Red" Text="{Binding yourStringProperty}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and set ic's ItemsSource to List

Problems binding to a the content of a WPF DataGridCell in XAML

I used the following post to implement a datagrid bound to a list of dynamic objects
Binding DynamicObject to a DataGrid with automatic column generation?
The ITypedList method GetItemProperties works fine, a grid is displayed with all the columns I described.
I use a custom PropertyDescriptor and override the GetValue and SetValue methods as described in the above post, I also implement the TryGetMember and TrySetMember methods in the dynamic objects.
so basically I have a ComplexObject:DynamicCobject with a field Dictionary and a ComplexObjectCollection implementing ITypedList and IList.
This all works fine except when I bind the itemsSource of the DataGrid to the collection, the cells will show the SimpleObject type name and I actually want to implement a template to show the property Value of the SimpleObject in a text block.
I've used all sorts of methods to try and get the underlying SimpleObject but nothing works and I always get the ComplexObject for the row. I am using autogenerated columns and this always seems to produce a text column, this may be the problem but why cant I still get the underlying SimpleObject from somewhere in the cell properties?
Below would be my ideal solution but this does not work.
<Grid>
<Grid.Resources>
<DataTemplate x:Key="DefaultNodeTempate">
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Content}">
<ContentControl.Resources>
<DataTemplate DataType="local:SimpleObjectType">
<TextBlock Text="{Binding Value}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</Grid.Resources>
<DataGrid ItemsSource="{Binding ElementName=mainWin, Path=DynamicObjects}">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultNodeTempate}" />
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
Any suggestions would be much appreciated.
Thanks
Kieran
So I found the solution was to do some work in the code behind.
In the AutoGeneratingColumn event create a DataTemplate with a content control and a custom template selector (I create the selector in Xaml and found it as a resource).
Create a binding for the ContentProperty of the ContentControl with e.PropertyName as the path
Create a new DataGridTemplateColumn and set the new columns CellTemplate to your new DataTemplate
replace e.Column with your new column and hey presto the cells datacontext bind with the dynamic property for that column.
If anyone has any refinement to this please feel free to share your thoughts.
Thanks
EDIT: As requested some sample code for my solution
Custom template selector:
public class CustomDataTemplateSelector : DataTemplateSelector
{
public List<DataTemplate> Templates { get; set; }
public CustomDataTemplateSelector()
: base()
{
this.Templates = new List<DataTemplate>();
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate template = null;
if (item != null)
{
template = this.Templates.FirstOrDefault(t => t.DataType is Type ? (t.DataType as Type) == item.GetType() : t.DataType.ToString() == item.GetType().ToString());
}
if (template == null)
{
template = base.SelectTemplate(item, container);
}
return template;
}
}
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="ParentControl">
<Grid.Resources>
<local:CustomDataTemplateSelector x:Key="MyTemplateSelector" >
<local:CustomDataTemplateSelector.Templates>
<DataTemplate DataType="{x:Type local:MyCellObject}" >
<TextBox Text="{Binding MyStringValue}" IsReadOnly="{Binding IsReadOnly}" />
</DataTemplate>
</local:CustomDataTemplateSelector.Templates>
</local:CustomDataTemplateSelector>
</Grid.Resources>
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="True" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" >
</DataGrid>
</Grid>
</Window>
Code behind:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
// Get template selector
CustomDataTemplateSelector selector = ParentControl.FindResource("MyTemplateSelector") as CustomDataTemplateSelector;
// Create wrapping content control
FrameworkElementFactory view = new FrameworkElementFactory(typeof(ContentControl));
// set template selector
view.SetValue(ContentControl.ContentTemplateSelectorProperty, selector);
// bind to the property name
view.SetBinding(ContentControl.ContentProperty, new Binding(e.PropertyName));
// create the datatemplate
DataTemplate template = new DataTemplate { VisualTree = view };
// create the new column
DataGridTemplateColumn newColumn = new DataGridTemplateColumn { CellTemplate = template };
// set the columns and hey presto we have bound data
e.Column = newColumn;
}
There may be a better way to create the data template, I have read recently that Microsoft suggest using a XamlReader but this is how I did it back then. Also I haven't tested this on a dynamic class but I'm sure it should work either way.

WPF DataTemplateColumn access DataTemplate and set ItemsSource

I know its strange what I am doing but I want this to work. I am going wrong somehwere I feel.
I have a DataTemplate defined in my resources as follows :
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../ParameterEditorResourceDictionary.xaml"></ResourceDictionary>
<ResourceDictionary>
<DataTemplate x:Key="ParameterDefault">
<StackPanel Orientation="Horizontal">
<TextBlock Text="("></TextBlock>
<ItemsControl ItemsSource="{//I need to set from code}">
//some code here
</ItemsControl>
<TextBlock Text=")"></TextBlock>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
I have a DataGrid defined in my xaml which has a loaded event.
<cc:PEDataGrid AutoGenerateColumns="False"
Loaded="CommonPEGrid_Loaded">
</cc:PEDataGrid>
In my event handler code I want to set the ItemsSource of ItemsControl defined in my DataTemplate. My code behind looks like this :
private void CommonPEGrid_Loaded(object sender, RoutedEventArgs e)
{
int i = 0;
DataGrid dg = sender as DataGrid;
DataGridTemplateColumn column = null;
//ParametersAllLoops is a ObservableCollection
foreach (ParameterLoop obj in ParametersAllLoops)
{
column = new DataGridTemplateColumn();
column.Header = "Loop ( " + i.ToString() + " )";
DataTemplate dt = null;
//Here I want to write code
//I want to access the DataTemplate defined in resources
//and set the ItemsSource of ItemsControl to something like this
// xxx.ItemsSource = obj; and then assign the DataTemplate to
//the CellTemplate of column.
//**Note :: ParameterLoop object has the IList Parameters**
column.CellTemplate = dt;
dg.Columns.Add(column);
i++;
}
}
You can find the resource by using method FindResource() and cast it to DataTemplate but to assign it ItemSource you will need string manipulation.
It seems you want to have dynamic columns on your datagrid, I would suggest that you have that generate the datatemplate in code behind so that you can resolve your binding paths and source names there and then attach it as cell template or cell edit template.

Resources