Applying DataTemplate based on type - c#

when i do the following and set the content of a Control to my ViewModel the Template gets applied automatically.
<DataTemplate DataType="{x:Type ViewModels:ViewModel}">
<StackPanel Orientation="Vertical">
<ControlX ....>
<ControlY ....>
</StackPanel>
</DataTemplate>
however i want to use FindResource to get the DataTemplate in the code behind so i had to add an x:key
<DataTemplate DataType="{x:Type ViewModels:ViewModel}" x:Key="{x:Type ViewModels:ViewModel}">
<DataTemplate DataType="{x:Type ViewModels:ViewModel}" x:Key="ViewModelTemplate">
But when i add an x:key the FindResource() works but the DataTemplate stops being applied automatically based on the type, any workaround available for this?

As a bad looking work around, you may try creating 2 DataTemplates that share the same content:
This ControlTemplate defines the shared content:
<ControlTemplate TargetType="{x:Type ContentControl}"
x:Key="MyControlTemplate">
<TextBlock Text="Some content" />
</ControlTemplate>
Then 2 DataTemplates as a workaround:
<DataTemplate x:Key="MyDataTemplate">
<ContentControl Template="{StaticResource MyControlTemplate}" />
</DataTemplate>
<DataTemplate DataType="{x:Type system:String}">
<ContentControl Template="{StaticResource MyControlTemplate}" />
</DataTemplate>
Edit:
I know that this answer came a year too late after I've provided a bad looking work around above, but it's better than never.
The implicit key set for an implicit data template is the data type itself wrapped in a DataTemplateKey.
You can either use:
FindResource(new DataTemplateKey(typeof (MainViewModel))
or
Resource[new DataTemplateKey(typeof (MainViewModel)]
to get your data template in code behind.

<DataTemplate DataType="{x:Type local:Task}">
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
This DataTemplate gets applied automatically to all Task objects. Note that in this case the x:Key is set implicitly. Therefore, if you assign this DataTemplate an x:Key value, you are overriding the implicit x:Key and the DataTemplate would not be applied automatically.
It works as documented. AFAIK you can either use Key or DataType not both, there may be workarounds which I wasn't aware of.

Related

Select different bindings in DataContext based on another binding

I have a fairly dynamic ObservableCollection of view models that is used by two different ListBox elements in XAML. Each view model contains properties for two different model objects of type Card called Primary and Secondary, as well as other properties. In one ListBox I'd like to display properties from Primary and in the other I'd like to display properties from Secondary. I'd like to use the same XAML UserControl file when displaying the ListBoxItems for both.
My first thought was to create an entry in UserControl.Resources that gives a name to the "right" card based on a RelativeSource reference from the parent view model which indicates Primary or Secondary, but I've not created an entry like that before. Is this the right approach? If so, what would the entry look like?
I've made up some XAML to help illustrate (may have typos). First, the Primary ListBox control:
<UserControl x:Class="Project.Cards.ListPrimary" d:DataContext="{Binding Main.Cards.Primary, Source={StaticResource Locator}}">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type vms:CardViewModel}">
<views:Card />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ListBox x:Name="CardListBox"
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}" />
</Grid>
</UserControl>
And the secondary:
<UserControl x:Class="Project.Cards.ListSecondary" d:DataContext="{Binding Main.Cards.Secondary, Source={StaticResource Locator}}">
... (same) ...
</UserControl>
And the card view (where I need to replace "Primary.Direction" with something that lets me select Primary/Secondary):
<UserControl x:Class="Project.Cards.Card">
<UserControl.Resources>
... perhaps something here ...
</UserControl.Resources>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
<TextBlock Text="{Binding Primary.Direction}" />
</StackPanel>
</UserControl>
If you want two instances of the same UserControl that differ in one respect, you figure out how to parameterize that. There are a couple of ways, but the simplest I thought of that fits your case was to just bind the differing value to a property of the View. This moves the specification of the different value to the owner.
We'll do that by defining a dependency property on the UserControl. It's a string, though it could be an object, and in the future you might want to make it one. Since we're using the view in a DataTemplate, we can bind a property of the DataContext to it there.
public partial class Card : UserControl
{
public Card()
{
InitializeComponent();
}
public String Direction
{
get { return (String)GetValue(DirectionProperty); }
set { SetValue(DirectionProperty, value); }
}
public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register("Direction",
typeof(String), typeof(Card), new PropertyMetadata(null));
}
...and we'll use that in the UserControl like this:
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
<TextBlock
Text="{Binding Direction, RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</StackPanel>
</Grid>
The RelativeSource stuff tells the Binding to look for that Direction property on the UserControl object itself, rather than on the DataContext as it would otherwise do by default.
If Card.Direction were object instead of string, you'd make that TextBox a ContentControl and bind to its Content property. Then you could put anything in there -- XAML, a whole other viewmodel, literally anything that XAML can figure out how to display.
And here's how it looks in the wild:
<DataTemplate DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Primary.Direction}" />
</DataTemplate>
And here's my whole mainwindow content from my test code. I didn't bother creating user controls for the listboxes; the above template is an exact match for the way you're doing it.
<Window.Resources>
<DataTemplate x:Key="PrimaryItemTemplate" DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Primary.Direction}" />
</DataTemplate>
<DataTemplate x:Key="SecondaryItemTemplate" DataType="{x:Type vms:CardViewModel}">
<views:Card Direction="{Binding Secondary.Direction}" />
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}"
ItemTemplate="{StaticResource PrimaryItemTemplate}"
/>
<ListBox
SelectedItem="{Binding SelectedCard}"
ItemsSource="{Binding Cards}"
ItemTemplate="{StaticResource SecondaryItemTemplate}"
/>
</StackPanel>
</Grid>
I originally thought of a more elaborate scheme where you give the view a DataTemplate instead, and it worked, but this is simpler. On the other hand, that was more powerful. I actually used that in the first version of the answer, before I came to my senses; it's in the edit history.
Thanks for a fun little projectlet.

Dynamically change a static resource

In my App.xaml I have two resources
<x:Double x:Key="MasterGridSize">150</x:Double>
<DataTemplate x:Key="MasterGridItemTemplate">
<Grid Width="{StaticResource MasterGridSize}" Height="{StaticResource MasterGridSize}">
</Grid>
</DataTemplate>
When I change the value of MasterGridSize it does not change the grid size - as expected. How can I achieve this?
I tried ThemeResource, then it's at least changing, when the theme changes.
DynamicResource is not available.
I don't want to add MasterGridSize to the ViewModel, since this would lead to some dependencies between ViewModels and some update code.
Any other ideas?
Edit: It might also be reasonable to edit the DataTemplate directly - how would I achieve this?
You need to use a value holder, like the BindableValueHolder from the UWP Community Toolkit.
<helpers:BindableValueHolder x:Key="MasterGridSize">
<helpers:BindableValueHolder.Value>
<x:Double>150</x:Double>
</helpers:BindableValueHolder.Value>
</helpers:BindableValueHolder>
<DataTemplate x:Key="MasterGridItemTemplate">
<Grid Width="{Binding Value, Source={StaticResource MasterGridSize}}" Height="{Binding Value, Source={StaticResource MasterGridSize}}" />
</DataTemplate>
Source code available here!
Have you declared your resources very well? If yes, try GridLength as follows:
<Page.Resources>
<GridLength x:Key="FirstSize">5*</GridLength>
<GridLength x:Key="SecondSize">4*</GridLength>
</Page.Resources>
<DataTemplate x:Key="MasterGridItemTemplate">
<Grid Width="{StaticResource FirstSize}" Height="{StaticResource SecondSize}">
</Grid>
</DataTemplate>

Avoiding ItemTemplate Duplication With WPF Controls?

I have comboboxes that all need to use a converter:
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentPresenter
Content="{Binding Converter={StaticResource TimespanConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I'm currently pasting this everywhere I need it, but I am wondering if there is a way to avoid this duplication: to be able to do something like:
<TimeSpanComboBox ...></...> or something similar?
You can define an implicit DataTemplate somewhere in your Application.Resources. i.e.
<DataTemplate DataType="{x:Type sys:TimeSpan}">
<ContentPresenter
Content="{Binding Converter={StaticResource TimespanConverter}}"/>
</DataTemplate>
You can of course also define a key and re-use it explicitly where you need it (e.g. ItemTemplate="{StaticResource TimeSpanTemplate}").

Use Style in TreeView HierarchicalDataTemplate

I'm new to this and can't quit get the correct syntax. This works correctly to capture the Left Mouse click on the textbox within the treeview:
<HierarchicalDataTemplate
DataType="{x:Type r:NetworkViewModel}"
ItemsSource="{Binding Children}"
>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NetworkIP}" Width="110" >
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding DataContext.SelectItem, RelativeSource={RelativeSource FindAncestor, AncestorType=TreeView}}"
CommandParameter="{Binding}" />
</TextBlock.InputBindings>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
How can this be done using a Style block in the Resources?
The goal being to use the same style for all TextBoxes in the TreeView. Something that would sit in the Usercontrol.Resources and be refrenced by the HierarchicalDataTemplate.
If I understand you correctly, you could define a template in the controls or windows resources with a target type (opposed to key x:Key=...) to have it automatically applied to all items in the tree view.
Here is a small example with a definition of a template in the window resources, which contains the InputBindings definition. This template will be automatically applied to all objects of type ItemViewModel if no other template is explicitly defined by the ItemsControl or TreeView. In this example, the items are displayed in a simple ItemsControl but it works for the TreeView just the same.
Note that for this to work, all items in the TreeView need to be of the same type. It is not sufficient if they are derived from the same base type. The template will only be applied, if the type defined in Template.DataType is exactly the same as the type of the ViewModel. If your TreeViews ItemsScources contain mixed type, you would need to specify the template for every type separately.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:ItemViewModel}">
<TextBlock Text="{Binding Name}" Width="110" >
<TextBlock.InputBindings>
<MouseBinding
MouseAction="LeftClick"
Command="{Binding SelectItem}" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}" />
</Grid>
</Window>

Silverlight DataGrid styling

I have a DataGrid whose ItemsSource is bound to a changing Observable Collection. Inside of this collection is a Business Object. Based on some of the values of the Business Object's properties, I would like to be able to modify the color of the text for each item displayed in my DataGrid once the ItemsSource is created.
Has anyone done this before or ran across something similar? Thanks in advance.
<DataTemplate x:Key="MyTemplate">
<Grid x:Name="LayoutRoot">
<TextBlock Text="{Binding MyText}"
Foreground="{Binding MyStatus, Converter={StaticResource colorConverter}}" />
</Grid>
</DataTemplate>
I added the above code and inserted the TemplateColumn to the grid as below:
<data:DataGridTemplateColumn Header="Testing"
CellTemplate="{StaticResource MyTemplate}"/>
The code works fine and pulls out the correct text but the converter never fires and the Binding of the foreground is never called from the get on it.
Any ideas?
Yes. Use a Value Converter when databinding.
<UserControl.Resources>
<myconverters:BackColor x:Key="BackColor" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="{Binding SomeValue, Converter={StaticResource BackColor}" >
</Grid>
Then have your converter class implement IValueConverter and return a Brush object of some kind. You usually don't have to implement ConvertBack()
Adding to BC's answer:
You can make a DataGridTemplateColumn and specify a data template for cells in a column. In the data template you can bind the text colour.
<swcd:DataGrid ... >
<swcd:DataGrid.Columns>
<swcd:DataGridTemplateColumn Header="MyColumn" CellTemplate="{StaticResource MyColumnDataGridCellTemplate}"/>
...
in resources:
<DataTemplate x:Key="MyColumnDataGridCellTemplate">
<Grid>
<TextBlock Text="{Binding someproperty}" Foreground="{Binding someotherproperty, Converter={StaticResource MyConverter}}"/>
...

Categories

Resources