How can I find a DataTemplate via code? - wpf

Knowing a HierarchicalDataTemplate is defined somewhere in the resource tree (i.e. it could be defined at the app level, in a style, in the window's resources or somewhere in the hierarchy of controls on that window), programmatically, how can you determine which HierarchicalDataTemplate will be applied for a particular data type relative to a specific control?
For instance, in the following example, given that we have an object of type Foo, how can we get the HierarchicalDataTemplate for it--which happens to be defined at the window level here--relative to MainTreeView?
<Window ... >
<Window.Resources>
<HierarchicalDataTemplate DataType="{x:Type Foo}"
ItemsSource="{Binding Children}">
<TextBlock Text={Binding Name}" />
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView x:Name="MainTreeView" />
</Window>
I've tried the following but it returns null:
var hdt = (HierarchicalDataTemplate)MainTreeView.FindResource(typeof(Foo));

Found it. DataTemplate objects don't use DataType for their default key like Style objects do. Instead, they use a DataTemplateKey object which you get as follows...
var dataTemplateKey = new DataTemplateKey(dataItem.GetType());
var hdt = (HierarchicalDataTemplate)MainTreeView.TryFindResource(dataTemplateKey);
That worked! :)

Related

WPF define ConverterCulture/Language in resources

I would like default culture/language to be defined as resource in order to provide consistent displaying of selected labels. It seems, however it is not possible to define neither Language(XMLLanguage) nor ConverterCulture(CultureInfor) resource, it does not seem to be possible to use a string resource neither:
<TextBlock Text="{Binding Source={x:Static sys:DateTime.Now},
Mode=OneWay, StringFormat={StaticResource DateFormat},
ConverterCulture={StaticResource DefaultCulture},
Language={StaticResource DefaultLang}/>
//....
<Grid.Resources>
<sys:String x:Key="DefaultCutureString">en-GB</sys:String>
<win:XmlLanguage xmlns:win="clr-namespace:System.Windows.Markup;assembly=PresentationFramework" x:Key="DefaultLang">en-GB</win:XmlLanguage>
<g:CultureInfo xmlns:g="clr-namespace:System.Globalization;assembly=mscorlib" x:Key="DefaultCuture">
<x:Arguments>
<sys:String>en-GB</sys:String>
</x:Arguments>
</g:CultureInfo>
</Grid.Resources>
How can I define and apply Culture/Language using Resource?
XmlLanguage and CultureInfo have no default parameterless constructors which means you cannot instantiate them in XAML.
But you could create the resources programmatically and then add them to Grid.Resources (just give the Grid and x:Name of "grid" or similar in the XAML markup to be able to identify it in the code):
grid.Resources["DefaultLang"] = XmlLanguage.GetLanguage("en-GB");
grid.Resources["DefaultCuture"] = new System.Globalization.CultureInfo("en-GB");

DataGridColumnHeader of a DataGrid defined in a DataTemplate for a ViewModel

I have successfully used ProxyElement to pass Data Context of my Data Grid to DataGridColumnHeaders. However, I am trying out something new and I just can't figure out what I am doing wrong over here.
Here is what I am trying to do: I am creating a UserControl and associating it to my ViewModel in my Resources file (see Resources.xaml code snippet below).
Resources.xaml:
<ResourceDictionary
xmlns:myVm="clr-namespace:..."
xmlns:myUserControl="clr-namespace:...">
<DataTemplate DataType={x:Type myVm:DummyModel}">
<myUserControl:DummyUserControl />
</DataTemplate>
</ResourceDictionary>
Now in my UserControl, I have a DataGrid with a DataGridComboBoxColumn. I am trying to access my data context to set its item source and in the past I was able to do it using proxy element. This time however I am not able to do so. (See DummyUserControl.xaml code snippet below)
DummyUserControl.xaml:
<UserControl x:Class="Client.MyControl.DummyUserControl"
...>
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" x:Name="ProxyElement"
DataContext="{Binding}" />
</UserControl.Resources>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGridComboBoxColumn
Header="Company"
ItemsSource="{Binding Path=DataContext.ProductCompanies,
Source={StaticResource ProxyElement}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValueBinding="{Binding CompanyId}" />
</DataGrid>
</UserControl>
When I do this, my binding fails with the following message:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext.
BindingExpression:(no path); DataItem=null; target element is 'FrameworkElement'
(Name='ProxyElement'); target property is 'DataContext' (type 'Object')
I have no idea what to do here. I remember reading that the datacontext for a datatemplate is automatically set, so I have no idea why the data context is null in this case. To prove it is null, I also tried setting the binding in the code-behind file and added a breakpoint to check its value (which was null).
Can anyone suggest what to do here?
Edit 1
I have also tried the following approaches:
Remove ProxyElement altogether and see if it can detect DataContext. To no surprise, this failed.
Tried binding to the templated parent. Fail.
Tried binding to the UserControl itself. Fail.
I also tried referencing the data context of the parent item where this view model is going to be displayed, which is in a TabItem of a TabControl.
All of the alternate bindings gave me same error as the error above.
Here is a (working but not preferred) solution to this problem. You will realize why it is not preferred by the end of it.
The key to this problem is understanding what and how data context of a data template works. Whenever you define a Data Template for a View Model, the data context for the view that follows, whether it is a user control or just xaml itself, is the View Model! This shouldn't be a surprise to anyone.
But this will surprise people: if you specify a User Control, the Data Context of the User Control is not set during construction of the User Control! In other words, in the constructor of User Control, Data Context is going to be null. Furthermore, any XAML code that relies on the Data Context at construction time, which in this case was my FrameworkElement resource called ProxyElement got its DataContext set to null because it gets constructed at construction time of the User Control!
So when does the DataContext get set? Simply after the User Control is created. In pseudo code, this following describes the logic behind drawing a ViewModel:
Draw ViewModel x;
DataTemplate in ResourceDictionary says ViewModel x can be drawn using UserControl abc
Let's create a new instance of UserControl abc
Let's now assign the DataContext of abc to the ViewModel itself.
Let's return the newly created instance of UserControl abc
So what do we do to solve the problem in this question?
UserControl.xaml:
<UserControl ...
DataContextChanged="DaCoHasChanged">
<UserControl.Resources>
<FrameworkElement x:Key="ProxyElement" /> <!--Remove DataContext="{Binding}"-->
</UserControl.Resources>
<DataGrid ...>
<DataGridComboBoxColumn
ItemsSource="{Binding Path=DataContext.ProductCompanies,
Source={StaticSource ProxyElement}}"
... />
</DataGrid>
</UserControl>
UserControl.xaml.cs:
private void DaCoHasChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var proxyElement = Resources["ProxyElement"] as FrameworkElement;
proxyElement.DataContext = e.NewValue; // instead of e.NewValue, you could
// also say this.DataContext
}
I am trying to figure out a way of getting rid of the code in the code-behind file. But till then, if someone else hits this problem, then they might be able to get inspired from this solution.
Credit to the concept behind this solution goes to: How to set the DataContext for a View created in DataTemplate from ViewModel
Try this
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
<DataGridTemplateColumn Header="Company">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path="{Binding DataContext.ProductCompanies,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValuePath="Id"
SelectedValueBinding="{Binding CompanyId}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Now as I got your problem I think the problem is in DataGridComboBoxColumn I dont know why it not Binding using RelativeResource . Try it with DataGridTemplateColumn and you would not require any ProxyElement I hope this will help.

connect View to ViewModel with DataTemplate

I'm trying to understand. When I'm connecting View to ViewModel like this:
<DataTemplate DataType="{x:Type local:MyViewModel}">
<local:MyView />
</DataTemplate>
What does it mean?
It looks like the View is set to be the DataTemplate of the ViewModel. BUT the ViewModel doesn't have a Property of DataTemplate. So what exactly is going on in there?
A demonstration of the question - How do I do that by code (Connecting the View and ViewModel this specific way. I can't write ViewModel.DataTemplate = View)?
Thank you.
It means "To whatever control whose Content data is MyViewModel place MyView there". You are not setting DataTemplate of viewmodel (That does not mean anything) you are setting the DataTemplate for the control whose Data is MyViewModel.
Take for example an ItemsControl with an Items Source of
ObservableCollection<Employee> Employees
where each Employee is represented by a DataTemplate for Example :
<DataTemplate TargetType="{x:Type local:Employee}">
<StackPanel>
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
So in the same manner a MyViewModel.cs such as Employee.cs as a visual representation based on a DataTemplate .
and represented for example as such :
<ContentControl Content="{Binding MyViewModelProperty}" />
The way how it works is very simple. Your definition of DataTemplate is something like a definition of how a data will look like. In your example the data that you want to represent visually are of type:
DataType="{x:Type local:MyViewModel}"
By defining DataTemplate in control, window or other resources, e.g.
<UserControl.Resources> ...your template... <UserControl.Resources>,
you say "Hey, I want that all my data of type local:MyViewModel will look like this...". Inside the template you define a root control, that will be put in all places where your local:MyViewModel have been used. Normally when you place local:MyViewModel in Grid, ContentControl or other containers, you will see its string representation like "xxxx.xxxxx.MyViewModel" instead of visual.
To to see a graphical representation you must define a DataTemplate. It will replace the string "xxxx.xxxxx.MyViewModel" - representing your data and put there a visual control you defined in the template. Then when it is done, this representation - control from your DataTemplate will get DataContext property set to your View Model, here it will be local:MyViewModel instance.
That will give you a possibility to use binding in your DataTemplate, to bind in you DataTemplate directly to properties from you ViewModel.
Is that more clear now?

Is it Possible to apply a DataTemplate to a Page?

I'm trying to follow the MVVM pattern laid out here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090097 I have this in my MainWindowResources.xaml file:
<DataTemplate DataType="{x:Type vm:VendorsViewModel}">
<vw:Vendors/> <--- I get a "Can't put a page in a style" error in blend with this
</DataTemplate>
and I've got this in my MainWindow.xaml file
<Window.Resources>
<ResourceDictionary Source="MainWindowResources.xaml"/>
</Window.Resources>
The mainWindow.xaml file contains a menu on the left and page holder on the right. Can I apply a dataTemplate to a <Page>? Or does it have to be a <UserControl>? As it stands, nothing is being data bound, here's what I have on the page that I want to have the viewmodel applied to:
<Custom:DataGrid Margin="0,30,0,0" d:LayoutOverrides="Width" ItemsSource="{Binding Path=AllVendors, Mode=Default}" >
<Custom:DataGrid.Columns>
<Custom:DataGridTextColumn Header="Company Name" Binding="{Binding Path=Name}" />
</Custom:DataGrid.Columns>
</Custom:DataGrid>
DataTemplates are applied to Content, which in most cases is either the Content property of a ContentControl or the Items/ItemsSource property of an ItemsControl. Page is not derived from ContentControl (UserControl is) so a DataTemplate can't be applied to its Content.
From what you're doing here it doesn't sound like that's what you're trying to do though. It looks like you're trying to use a Page in a DataTemplate which is what the error is telling you. Page is treated like Window in that it is a root container that is intended to have visual Content defined in a xaml file. UserControl has a similar purpose but can be inserted anywhere into a layout. If you change vw:Vendors to be a UserControl that should get rid of this specific error but you should also consider whether you're gaining anything from having the UserControl instead of just putting its content directly into the DataTemplate - this can help discourage code-behind and force you to use your ViewModel correctly.

x:Name attribute does not always create member or field variable

Adding an x:Name attribute to a XAML element normally results in a member variable being added to the backing class that can then be accessed using normal code. When the element in question is part of the DataTemplate, the field does not get created.
I can sort of understand that the DataTemplate is making this a special case but can anyone explain the underlying principle to me? Also what are the options for getting access to the object within .NET Code?
<dataControls:DataForm x:Name="CompanyDetail" CurrentItem="{Binding CurrentItem}" AutoGenerateFields="False">
<dataControls:DataForm.EditTemplate>
<DataTemplate>
<StackPanel dataControls:DataField.IsFieldGroup="True">
<dataControls:DataField Label="About">
<Border Height="150" Style="{StaticResource HtmlPlaceHolderBorderStyle}" Width="298" VerticalAlignment="Top">
<telerik:RadHtmlPlaceholder x:Name="uxAboutHtml" x:FieldModifier="Public" HtmlSource="{Binding About, Mode=TwoWay}"/>
</Border>
</dataControls:DataField>
</StackPanel>
</DataTemplate>
</dataControls:DataForm.EditTemplate>
</dataControls:DataForm>
You can use the FrameworkElement.FindName("objectName") method on the parent of the DataTemplate e.g. var uxAboutHtml = CompanyDetail.FindName("uxAboutHtml"); to get a reference to the object. The downside to this is that paramater passed to FindName does not end up being strongly typed with the XAML x:Name"objectName" attribute.
I have changed tack on this and am now referencing the underlying object that the control is being bound to, which is probably a better way to go.
var htmlContent = (CompanyViewModel)CompanyDetail.CurrentItem;

Resources