I'm using my own custom localization library that is based on this tutorial with extended features.
Works pretty nifty, but now I want to use it within a ItemControl with a Template for all available languages and get localized values depending on a property of the items.
ViewModel:
public IEnumerable<CultureInfo> AvailableLanguages
{
get
{
return TranslationManager.Instance.AvailableLanguages;
}
}
View:
<ItemsControl ItemsSource="{Binding AvailableLanguages}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type glo:CultureInfo}">
<StackPanel Orientation="Horizontal">
<Image Width="32" Height="24"
Source="{loc:Translate {Binding TwoLetterISOLanguageName}}"/>
<TextBlock Text="{Binding DisplayName}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Obviously using a Binding as Parameter won't work, because of some limitations of MarkupExtensions (you can see the basic implementation in the link above).
Is there a way to work around this issue? Will my only alternative be to do this whole stuff in the code behind?
Related
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.
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.
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}").
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.)
Can you tell me why this code isn't working?
I have a viewmodel with an observablecollection of searchresults which has an observablecollection of resultproperties. I cannot seem to display the nested collection of result properties like I want.
Here are the objects (abstracted for readability):
class ViewModel
{
public ObservableCollection<SearchResults<TDomain>> SearchResults { get; set; }
}
class SearchResults<TDomain>
{
public TDomain Result { get; set; }
public ObservableCollection<ResultProperty> ResultProperties { get; set; }
}
class ResultProperty
{
public string PropertyValue { get; set; }
}
Here is the xaml I cannot get to work. The DataContext is set to the ViewModel:
<StackPanel>
<ItemsControl ItemsSource={Binding SearchResults}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text={Binding Result.Id}/>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource={Binding ResultProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PropertyValue}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="PLACEHOLDER /////"/>
</StackPanel>
The outcome I am looking for is something like this:
[Stack panel to keep things orderly]
1
property1property2property3...
2
property1property2property3...
3
property1property2property3...
PLACEHOLDER /////
The results I am getting are these
[Stack panel to keep things orderly]
1
2
3
PLACEHOLDER /////
In otherwords, the binding isn't picking up the string. I have verified that the collections are populating like expected. But I can't get the xaml to work.
**ADDITION INFORMATION
Ok, so I tried some of the solutions, but they aren't working so I am going to add more details because maybe I am missing something about how the collections are getting updated.
The viewmodel has a button on it called "Search" which uses an ICommand that calls a the view model's TrySearch method which is below:
public void TrySearch()
{
var results = _model.GetAll();
foreach(var result in results)
this.SearchResults.Add(new SearchResults<TDomain>(result));
}
Could there be a reason this doesn't work because of the way the collection is updated? SearchResults is a dependency property (I know I know, it should be INPC, but its not, its dependency), but the other collections are not. Could this be a problem?
I would create the data templates as resources and refer to them elsewhere in the XAML. For example:
<Window ....>
<Window.Resources>
<DataTemplate x:Key="SearchResultTemplate" TargetType="SearchResults">
<TextBlock Text={Binding PropertyValue}"
</DataTemplate>
<DataTemplate x:Key="ViewModelTemplate" TartgetType="ViewModel">
<StackPanel>
<TextBlock Text={Binding Result.Id}/>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource={Binding ResultProperties} ItemTemplate="{StaticResource SearchResultTemplate}" />
</StackPanel>
</StackPanel
</DataTemplate>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource={Binding SearchResults} ItemTemplate="{StaticResource ViewModelTemplate}" />
</Window>
I'm not sure, but I think that the bindings you're using are telling the XAML parser to look for properties of the ViewModel class called ResultProperties and PropertyValue. The XAML parser doesn't see that they're properties of the object bound to that instance of the collection. Splitting it up like this should make it plain that the properties belong to the instance that the template is being applied to.
Your code is somehow correct but according to what you want it has a flow. The StackPanel has to be the ItemsPanel. Change it like this:
<StackPanel>
<ItemsControl ItemsSource="{Binding SearchResults}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Result.Id}"/>
<ItemsControl ItemsSource="{Binding ResultProperties}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PropertyValue}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="PLACEHOLDER /////"/>
</StackPanel>
The answer is that my SearchResults class was not hooked up properly. I made it a dependency object with dependency properties and it works fine. I am assuming that would translate similarly if it was INotifyPropertyChanged. Thank you for the responses.