Avoiding ItemTemplate Duplication With WPF Controls? - c#

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}").

Related

How to create a template with parameters in WPF?

I have 3 List views. They are very similar. The only difference is that their ItemsSource binds to different variables. Is there a way to create a template list view with unknown ItemsSource, and I can pass a parameter to fill that ItemsSource?
My code is something like this:
<ListView Name="View1" ItemsSource={"Binding Student1"}>
<TextBlock Text={"Binding Name"}/>
</ListView>
<ListView Name="View2" ItemsSource={"Binding Student2"}>
<TextBlock Text={"Binding Name"}/>
</ListView>
<ListView Name="View3" ItemsSource={"Binding Student3"}>
<TextBlock Text={"Binding Name"}/>
</ListView>
Edit:
I might have expressed my question in a wrong way. I would like to have a separate user control view called "StudentView":
<ListView ItemsSource=Parameter1>
<TextBlock Text={"Binding Name"}/>
</ListView>
So that in my main window, I can do something like this:
<local:StudentView Parameter1={"Binding Student1"}/>
You are on the right track with thinking about templating
What you are looking for is something called a ControlTemplate.
Your ControlTemplate would then target the ListView control and use the key word TemplateBinding to pass through the ItemsSource binding from your ListView
You would look to add this as a window resource as shown below.
<Window.Resources>
<ControlTemplate x:Key="ListViewTemplate" TargetType="ListView">
<ListView ItemsSource="{TemplateBinding ItemsSource}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ControlTemplate>
</Window.Resources>
This would enable you to use this template on your ListView controls as shown below
<ListView Template="{StaticResource ListViewTemplate}" ItemsSource="{Binding PersonList}"/>
<ListView Template="{StaticResource ListViewTemplate}" ItemsSource="{Binding PersonList1}"/>
<ListView Template="{StaticResource ListViewTemplate}" ItemsSource="{Binding PersonList2}"/>
Hope this gives you what you were looking for

Navigation between page

I'm writing a small WPF app with only 3 pages (for now). I'm using DataTemplate and ContentControl in the main window to display and switch between my pages. (See code sample below). It's working but I have a few concerns:
The DataTemplate use parameterless constructor only. If I add one then It can't find the constructor.
The 'registration' is done in the xaml and I cannot use Dependency Injection to link Views with ViewModels.
Questions:
Is there a way to change that without using third party tool?
If the only good option is to use a tool, which ones should I consider?
<Window.Resources>
<DataTemplate DataType="{x:Type pageViewModels:HomePageViewModel}">
<pageViews:HomePageView />
</DataTemplate>
<DataTemplate DataType="{x:Type pageViewModels:GamePageViewModel}">
<pageViews:GamePageView />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
Edit
To clarify, I want to inject a class in the constructor of my viewsModels, but if I do that then the navigation within my application is broken because the dataTemplate is looking for the parameterless constructor.
It looks like my problem has been very well explained in this post.
In short, I need to implement a ViewModelLocator pattern in order to fix all my concerns.

What's an idiomatic XAML TreeView with CollectionViewGroup groupings?

I have a TreeView that looks like this:
<TreeView Grid.Row="1" x:Name="InspectionResultsTreeView"
ItemsSource="{Binding Source={StaticResource InspectionTypeGroupViewSource}, Path=Groups}"
ItemTemplate="{StaticResource InspectionTypeGroupsTemplate}">
</TreeView>
The ItemsSource is a keyed resource that goes by the name of InspectionTypeGroupViewSource:
<CollectionViewSource x:Key="InspectionTypeGroupViewSource" Source="{Binding Results}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Inspection.InspectionType" />
<PropertyGroupDescription PropertyName="Inspection" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
The role of this little thing is to take the ViewModel's Results property:
private ObservableCollection<ICodeInspectionResult> _results;
public ObservableCollection<ICodeInspectionResult> Results
{
get { return _results; }
set { _results = value; OnPropertyChanged(); }
}
...and group it on two levels - first by InspectionType, then by Inspection - the result is a 3-level hierarchy with inspection types, inspections, and then individual inspection results. At this point a screenshot might help visualizing I guess:
So, the ItemTemplate of the InspectionResultsTreeView is another keyed resource, by the name of InspectionTypeGroupsTemplate - that's the bold "inspection type" items:
<HierarchicalDataTemplate x:Key="InspectionTypeGroupsTemplate"
DataType="{x:Type CollectionViewGroup}"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource InspectionGroupsTemplate}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{Binding Name}"
FontWeight="Bold"
TextWrapping="NoWrap"/>
<TextBlock Margin="4,0,4,0"
VerticalAlignment="Center"
Text="{Binding ItemCount, StringFormat=({0})}"
TextWrapping="NoWrap"/>
</StackPanel>
</HierarchicalDataTemplate>
And the ItemTemplate of that template is an InspectionGroupsTemplate - that's the individual inspections, with the "severity" icons:
<HierarchicalDataTemplate x:Key="InspectionGroupsTemplate"
DataType="{x:Type CollectionViewGroup}"
ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource InspectionResultTemplate}">
<StackPanel Orientation="Horizontal">
<Image Style="{StaticResource IconStyle}"
Source="{Binding Name, Converter={StaticResource InspectionIconConverter}}"
VerticalAlignment="Center" />
<TextBlock Margin="4"
VerticalAlignment="Center"
Text="{Binding Name, Converter={StaticResource InspectionDescriptionConverter}}"
TextWrapping="NoWrap"/>
<TextBlock Margin="0,4,0,4"
VerticalAlignment="Center"
Text="{Binding ItemCount, StringFormat=({0})}"
TextWrapping="NoWrap"/>
</StackPanel>
</HierarchicalDataTemplate>
Lastly, the ItemTemplate of this grouping is an InspectionResultTemplate, which is for each individual inspection result:
<DataTemplate x:Key="InspectionResultTemplate"
DataType="{x:Type inspections:ICodeInspectionResult}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Margin="4"
Text="{Binding Name}"
TextWrapping="NoWrap"/>
</StackPanel>
</DataTemplate>
The ICodeInspectionResult interface has a string Name property that I'm using here; this Name is different from the Name that's used in the grouping levels, where it's an object CollectionViewGroup.Name - the underlying type of that Name is that of the grouping, so level 1 is an InspectionType, and level 2 is an Inspection.
The problem is that I'm using more converters than I believe I'd need to, to convert this object Name and access the members I need to access and display... but then, I need to display the number of items in each grouping so the DataType ought to be a CollectionViewGroup... right?
How can I do this without resorting to a converter for everything that needs to be displayed? How is this supposed to be done? Every TreeView / CollectionViewGroup tutorial I could find was a trivial implementation.
You've encountered the iconic problem with XAML: it's almost too structured.
The most idiomatic solution is writing a custom WPF User Control. (How and what you include in it is up you.) The goal of the WPF User Control is to eliminate the duplicate XAML markup and logic. You can include your Converter in the User Control, and eliminate the converters from your main control.
There are plenty of tutorials on creating UserControl objects in WPF, so I'll not go into detail here.
As far as the Converter issue: this is almost the most idiomatic way. Each converter is reusable, and focuses only on one source type. There's not much else you can do about it, except consider merging converters that support the same source type together. (There's a reason the converter has a Type targetType parameter, and an object parameter.)

Applying DataTemplate based on type

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.

WPF Multiple ItemSources?

Is it possible to have multiple ItemSources for a single control?
Given the code below:
<ComboBox Margin="137,101,169,183" ItemsSource="{Binding collection}" SnapsToDevicePixels="True"
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding CheckCommand}" IsChecked="{Binding IsChecked}" Content="{Binding Name}"/>
<TextBlock Text="" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The TextBlock within the ComboBox DataTemplate requires data from another property within the VM than that of the ComboBox. How can this be achieved?
Thanks.
You can use RelativeSource-FindAncestor to reach up the visual tree and grab a different DataContext.
For example (assuming the command is what you want):
Command=”{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}, Path=CheckCommand}”
This should also serve as a good resource.
Edit: Typo and resources.
If i remember correctly, DataTemplates run within their own scope and cannot directly use ElementNames defined outside the DataTemplate. You could however get around it by using StaticResource and referring to that directly from TextBlock inside the template.
I haven't tried Ragepotatos's approach to go outside DataTemplate scope but would love to know if that works out for you too.

Categories

Resources