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.
Related
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.
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.
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.
Scenario:
I have a view that has some DataTemplate resources
<DataTemplate x:Key="myDragCueTemplate">
<Border Background="Blue"
Opacity="0.5"
Width="250">
<TextBlock Text="{Binding}" HorizontalAlignment="Left"></TextBlock>
</Border>
</DataTemplate>
I have a custom control derived from ListBox. Inside the custom listbox on a certain event I want to fetch a datatemplate from the View's resources.
public class MyListBox : ListBox
{
public MyListBox()
{
this.DefaultStyleKey = typeof(MyListBox);
}
...
itemDragCue.ContentTemplate = this.Resources["myDragCueTemplate"] as DataTemplate;
...
I tried adding the datatemplates to a separate .xaml file and added a ResourceDictionary, but it still didn't pick it up.
How can I get the resource in the custom control's backend?
Thanks.
this.Resources will only give the resources declared in
<UserControl x:Class="MyListbox">
<UserControl.Resources>
I would recommend putting myDragCueTemplate in a ResourceDictionary. You will then have to read that ResourceDictionary in in your code behind, and extract the specific resource you want.
Try this
const string resourcesPath = "/AssemblyName;component/Resources.xaml";
Uri resourceUri = new Uri(resourcesPath, UriKind.Relative);
StreamResourceInfo sri = Application.GetResourceStream(resourceUri);
StreamReader sr = new StreamReader(sri.Stream);
ResourceDictionary dictionary = (ResourceDictionary) XamlReader.Load(sr.ReadToEnd());
itemDragCue.ContentTemplate = dictionary["myDragCueTemplate"] as DataTemplate;
I’m having the following issue with WPF ComboBox:
XAML:
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type this:Data}">
<ComboBox IsTextSearchEnabled="False" IsEditable="True"
Text="{Binding Value}" ItemsSource="{Binding Menu}"/>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding}"/>
<Button Click="ChangeData_Click">Change Data</Button>
</StackPanel>
Code behind:
public Window1()
{
InitializeComponent();
DataContext = new Data();
}
void ChangeData_Click(object sender, RoutedEventArgs e)
{
DataContext = new Data();
}
I open the window and get ComboBox, bounded to my data model, I select some item (e.g. 1), all is dandy.
I change the data context to a new data model – the selected item is (to my surprise) 1... Where I don't expect any selected item...
I suspect it has something to do with the combo box which search disabled and editable, but I’m not sure what was the problem.
I found a work around: call UpdateLayout() on the ContentControl bounded to the DataContext, but it’s ugly.
Is that WPF bug? Is it all my fault?
Please Help
I've submitted the same question to MSDN WPF Forum and it seems like a Microsoft bug.
There's a workaround I found, ugly, but it's working. Here's the modified code behind:
public Window1()
{
InitializeComponent();
DataContext = new Data();
DataContextChanged += delegate { contentControl.UpdateLayout(); };
}
void ChangeData_Click(object sender, RoutedEventArgs e)
{
DataContext = null;
DataContext = new Data();
}
Note that both setting the DataContext to null and calling UpdateLayout() on DataContextChanged are needed to solve this issue.