Conditional xaml layout based on class in UWP - c#

I have a data model with inheritance and I want to display the right fields for each subclass in my xaml markup.
public abstract class Model {
public int Id { set; get; }
}
public class ModelOne : Model {
public int Tasks { set; get; }
}
public class ModelTwo : Model {
public DateTime date { set; get; }
}
The data context of my xaml will be a field of type Model. Each model has different fields that i want to display, but the rest of the xaml will be the same, so I hope I can avoid creating two views. I could create a converter that converts class to visibility, but i don't think this would be the best solution. Is there any features in UWP-xaml that could help me achieve this?

There are a variety of ways to approach this. But for me, the simplest and most logical is to create DataTemplate resources as usual, but have the templates for the more-derived classes use the template for the base class.
For example, given model classes that look like this:
class MainModel
{
public Model BaseModel { get; set; }
public ModelOne ModelOne { get; set; }
public ModelTwo ModelTwo { get; set; }
}
class Model
{
public int BaseValue { get; set; }
}
class ModelOne : Model
{
public int OneValue { get; set; }
}
class ModelTwo : Model
{
public int TwoValue { get; set; }
}
You can write XAML that looks like this:
<Page
x:Class="TestSO40445037UwpTemplateInherit.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="using:TestSO40445037UwpTemplateInherit"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<l:MainModel>
<l:MainModel.BaseModel>
<l:Model BaseValue="17"/>
</l:MainModel.BaseModel>
<l:MainModel.ModelOne>
<l:ModelOne BaseValue="19" OneValue="29"/>
</l:MainModel.ModelOne>
<l:MainModel.ModelTwo>
<l:ModelTwo BaseValue="23" TwoValue="37"/>
</l:MainModel.ModelTwo>
</l:MainModel>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="baseModelTemplate" x:DataType="l:Model">
<TextBlock Text="{Binding BaseValue}"/>
</DataTemplate>
<DataTemplate x:Key="modelOneTemplate" x:DataType="l:ModelOne">
<StackPanel>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource baseModelTemplate}"/>
<TextBlock Text="{Binding OneValue}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="modelTwoTemplate" x:DataType="l:ModelTwo">
<StackPanel>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource baseModelTemplate}"/>
<TextBlock Text="{Binding TwoValue}"/>
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentControl Content="{Binding BaseModel}" Margin="5"
ContentTemplate="{StaticResource baseModelTemplate}"/>
<ContentControl Content="{Binding ModelOne}" Margin="5"
ContentTemplate="{StaticResource modelOneTemplate}"/>
<ContentControl Content="{Binding ModelTwo}" Margin="5"
ContentTemplate="{StaticResource modelTwoTemplate}"/>
</StackPanel>
</Grid>
</Page>
The above might be overkill for classes that look literally like the examples in your question. But for more complex view models, this works well. The derived classes can reuse the base class template, but have some control over how that template is presented (by virtue of being able to put the ContentControl wherever is needed in the template).
In addition to allowing reuse of the base class template in any derived class template, this also avoids the need for a single template that includes elements with bindings for all possible view models. Not only would such an approach result in over-weight visual trees at runtime, you'd get lots of binding errors as well, since the hidden elements will still be trying to bind to non-existing properties on the view model.
Reusing the base class template in derived class templates avoids all that, and to me fits better with the general architecture of the view model class inheritances.
Note that this is somewhat different from the way it would be done in WPF:
<DataTemplate DataType="{x:Type lm:Model}">
<!-- template definition here...for example: -->
<StackPanel>
<TextBlock Text="{Binding Id, StringFormat=Id: {0}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type lm:ModelOne}">
<!-- template for ModelOne here; a ContentControl as shown below should be placed
in the as needed for your desired visual appearance. For example,
here is a template using a StackPanel as the top-level element,
with the base class template shown as the first item in the panel -->
<StackPanel>
<ContentControl Content="{Binding}" Focusable="False">
<ContentControl.ContentTemplate>
<StaticResourceExtension>
<StaticResourceExtension.ResourceKey>
<DataTemplateKey DataType="{x:Type lm:Model}"/>
</StaticResourceExtension.ResourceKey>
</StaticResourceExtension>
</ContentControl.ContentTemplate>
</ContentControl>
<TextBlock Text="{Binding Tasks, StringFormat=Tasks: {0}}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type lm:ModelTwo}">
<!-- template for ModelTwo here; same as above -->
<StackPanel>
<ContentControl Content="{Binding}" Focusable="False">
<ContentControl.ContentTemplate>
<StaticResourceExtension>
<StaticResourceExtension.ResourceKey>
<DataTemplateKey DataType="{x:Type lm:Model}"/>
</StaticResourceExtension.ResourceKey>
</StaticResourceExtension>
</ContentControl.ContentTemplate>
</ContentControl>
<TextBlock Text="{Binding date, StringFormat=date: {0}}"/>
</StackPanel>
</DataTemplate>
(Where, of course, lm: is whatever your actual XML namespace for your model class types is.)
Unfortunately, it looks like along with many other useful WPF features, UWP (and previously WinRT, Phone, Silverlight, etc.) is missing automatic data template resource key definition and lookup. The WPF example takes advantage of this, even using the model type as the key for the base class data template resource reference.
In UWP, it appears that all data template resources must be given a key explicitly, and be referenced explicitly, either inline to a template property (e.g. ContentTemplate or ItemTemplate), or via a resource reference (e.g. {Static Resource ...}).
The documentation tantalizingly hints at the possibility of using automatic lookup [emphasis mine]:
All resources need to have a key. Usually that key is a string defined with x:Key=”myString”. However, there are a few other ways to specify a key:
Style and ControlTemplate require a TargetType, and will use the TargetType as the key if x:Key is not specified. In this case, the key is the actual Type object, not a string. (See examples below)
DataTemplate resources that have a TargetType will use the TargetType as the key if x:Key is not specified. In this case, the key is the actual Type object, not a string.
x:Name can be used instead of x:Key. However, x:Name also generates a code behind field for the resource. As a result, x:Name is less efficient than x:Key because that field needs to be initialized when the page is loaded.
But the XAML editor and compiler doesn't recognize a DataTemplate.TargetType property, there's no mention of it in the DataTemplate class documentation, x:DataType doesn't avoid the need to still define an x:Key property for the resource, and I don't see a way to use an actual Type reference as the resource key explicitly.
I can only surmise that the documentation page is in fact incorrect. Maybe some lazy tech writer just copy/pasted from the WPF? I don't know.
So the UWP example above goes with simple {StaticResource ...} references coded explicitly where needed.
Of course, another option in UWP is to use DataTemplateSelector, a WPF feature that appears to still be supported in UWP. Here is a related question that includes an example of one way to use a selector: UWP DataTemplates for multiple item types in ListView. Of course, there are many other reasonable ways to initialize and use a DataTemplateSelector. It's basically the fallback when the behaviors automatically supported in XAML don't suffice, and when implementing one, you can do it however makes the most sense to you.

Related

User controls for creating certain models

I'm new to WPF and I found that creating a custom component for my case would be the best, so please tell me if I'm wrong at first. The purpose of this idea is to reuse it in other scenarios as needed.
The Model:
public class FooModel
{
public string Whatever { get; set; }
}
The ViewModel:
public class FooViewModel
{
public FooModel Foo { get; set; }
public ICommand CreateCommand { get; set; } = new AnotherCommandImplementation<FooModel>(model =>
{
// model is null! :(
});
}
The UserControl:
<UserControl>
<UserControl.DataContext>
<local:FooViewModel />
</UserControl.DataContext>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Foo.Whatever}" Height="23" Width="120"/>
<Button CommandParameter="{Binding Foo}" Command="{Binding CreateCommand}" Width="80" Content="Create"/>
</StackPanel>
</UserControl>
Why is Foo null and how do I fix it?
UPDATE
As requested, here's the current DataTemplate technique attempt:
App.xaml:
<Application>
<Application.Resources>
<DataTemplate DataType="{x:Type vms:KeyboardActionViewModel}">
<ctrs:KeyboardActionControl />
</DataTemplate>
</Application.Resources>
</Application>
Window:
<Window>
<Window.DataContext>
<vms:ActionExecutorViewModel />
</Window.DataContext>
<StackPanel>
<CheckBox IsChecked="{Binding Enabled}" Content="Enabled" />
<UserControl Content="{Binding Action}" />
</StackPanel>
</Window>
ViewModel:
public class ActionExecutorViewModel : ViewModel<ActionExecutor>
{
private Boolean enabled;
private ActionViewModel action;
public ActionExecutorViewModel()
{
Action = new KeyboardActionViewModel(); // Test
}
public ActionViewModel Action
{
get => action;
set => AssignAndRaiseEventOnPropertyChange(ref action, value);
}
public Boolean Enabled
{
get => enabled;
set => AssignAndRaiseEventOnPropertyChange(ref enabled, value);
}
public override ActionExecutor BuildModel()
{
var executor = new ActionExecutor();
executor.Action = action.BuildModel();
return executor;
}
}
KeyboardActionControl:
<UserControl>
<Label Background="Aqua">Asadsadsad</Label>
</UserControl>
ActionViewModel is an abstract class where KeyboardActionViewModel inherits from it.
As Sereja notes, your proximal issue is that Foo is null. You never created it, so it's not there. It should probably be instantiated by FooViewModel, but it's possible that the creator of FooViewModel should create Foo as well. Without knowing the semantics, I can't be sure. The view should absolutely not be responsible for creating either one.
But there are bad assumptions built into what you're doing. Let's correct those and get you on the right track.
ViewModelBase implements INotifyPropertyChanged. Examples abound. The view XAML snippets below are intended to be partial: There are bits of UI which are not illustrated, as they shouldn't present any difficulties.
public class MainViewModel : ViewModelBase
{
public ActionExecutorCollectionViewModel ActionExecutors { /* INPC stuff */ }
// ViewModels create their own children.
= new ActionExecutorCollectionViewModel();
}
public class ActionExecutorCollectionViewModel : ViewModelBase
{
public ObservableCollection<ActionExecutor> Items { /* INPC stuff */ }
public ActionExecutor NewActionExecutor { /* INPC stuff */ }
// Create new ActionExecutor and assign to NewActionExecutor
public ICommand CreateActionExecutor { /* ... */ }
// Add NewActionExecutor to Items and set NewActionExecutor to null
public ICommand SaveActionExecutor { /* ... */ }
}
Write an implicit DataTemplate for each of the above. In MainViewModel's DataTemplate, there's something like this:
<ContentControl Content="{Binding ActionExecutors}" />
That displays the ActionExecutorsViewModel with its implicit DataTemplate, which contains something like this, among other things:
<Button
Command="{Binding CreateActionExecutor}"
Content="Create"
/>
<Button
Command="{Binding SaveActionExecutor}"
Content="Save"
/>
<ContentControl
Content="{Binding NewActionExecutor}"
/>
ActionExecutor needs some kind of crude class factory to create its own Action. You have two action types now. I would advise against going crazy at the moment trying to write a perfect architecture for adding new ones in the future. I would instead suggest giving ActionExecutor a public readonly collection of action type options, probably values from an enum: public ActionType { Mouse, Keyboard }, and a public ActionType ActionType property. When ActionType changes, create a new action of the new type and assign it to the Action property. ActionType’s setter should call a protected method which does that. There are other, more clever options for this, but the above design is reasonably maintainable and has served well in thousands of production applications.
In ActionExecutor's implicit DataTemplate, you would have a combobox which lets the user select a type of action from the ActionTypes collection. Its SelectedItem property is bound to ActionType. This is how actions are created.
ActionExecutor's DataTemplate contains something like this:
<CheckBox Content="Enabled" IsChecked="{Binding Enabled}" />
<ComboBox ItemsSource="{Binding ActionTypes}" SelectedItem="{Binding ActionType}" />
<ContentControl Content="{Binding Action}" />
All viewmodels below MainViewModel are created by their immediate parent viewmodels, never never never ever by a view. Think of the viewmodel "tree" as the skeleton or framework of the application. Views just display bits of it as needed. Viewmodels need to communicate with each other; views don’t. They just reflect and instigate state changes in their viewmodels. The window can create its viewmodel in its constructor, or in XAML as <Window.DataContext><local:MainViewModel /></Window.DataContext>. Either is fine, but doing it in the constructor allows you to call a constructor that has parameters.
Thus, with that one exception, a UserControl always gets its DataContext from context, never by creating it. This is a practical matter, not ideological: It makes writing and maintaining the application very much easier than the alternative. Many vexing issues are swept out of existence when you follow this rule. It's rare for a UserControl in a well-designed WPF application to define dependency properties. The purpose of a UserControl is to display a viewmodel. Other types of controls will define vast, lavish, glittering assortments of dependency properties. Not UserControls.
You can write UserControls and put them in DataTemplates, or just write DataTemplates. I believe that writing UserControls is a good idea. A DataTemplate containing a UserControl looks EXACTLY LIKE THIS:
<DataTemplate DataType="{x:Type ActionExecutor}">
<local:ActionExecutorUserControl />
</DataTemplate>
DataContext="{Binding SomeProperty}" is essentially always wrong. It’s a “code smell” which indicates that somebody doesn’t understand XAML very well yet.
If some part of the above doesn't make sense to you, I'll be happy to help you fill that gap in your knowledge. If you believe that some part of it conflicts with your requirements, you may very well be mistaken. However, it is your responsibility to fully understand and codify your own requirements, and to communicate those requirements clearly.
UPDATE
Implicit DataTemplates
An implicit datatemplate is 1) a datatemplate defined as a resource in an accessible ResourceDictionary, with 2) a DataType attribute specifying which one of your classes you want to display with it.
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type ActionExecutorCollectionViewModel}">
<local:ActionExecutorCollectionUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type ActionExecutor}">
<local:ActionExecutorUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type MouseAction}">
<local:MouseActionUserControl />
</DataTemplate>
<!-- And so on and so forth. -->
</Application.Resources>
MainWindow.xaml
MainWindow's DataContext is your MainViewModel, which I've partially defined above.
<Grid>
<!--
MainViewModel.ActionExecutors is of type ActionExecutorCollectionViewModel.
If you defined an implicit datatemplate for that class in some ResourceDictionary
that's in scope here (e.g., App.xaml), this UserControl will automatically
use that datatemplate.
-->
<UserControl Content="{Binding ActionExecutors}" />
</Grid>
ActionExecutorUserControl.xaml
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Interval</Label>
<TextBox Text="{Binding Interval}" />
</StackPanel>
<CheckBox IsChecked="{Binding Enabled}">Enabled</CheckBox>
<!--
If you have implicit datatemplates defined for all your action types,
the framework will automatically give this UserControl the correct template
for whatever actual type of action the Action property refers to.
This is where we begin to see the real value of implicit datatemplates.
-->
<UserControl Content="{Binding Action}" />
</StackPanel>
There is not constructor that initializes Foo with non-default values (which null is for reference types). That's the reason. At least, provide such a constructor, or - more WPFic way - create a DataContext="{Binding Foo}"; that's probably what you wanted, however your XAML is wrong then: you are creating new instance all the time rather than consuming the view model's Foo instance.
P.S. More than that, for UserControls it is command to expose a DependencyProperty to take the underlying model; so it would look like <UserControl Model="{Binding Foo}" ... />.

Why can't I use {x:Bind {RelativeSource Self}} in a data template?

If I use {x:Bind {RelativeSource Self}} in a data template, I get the following error while compiling:
Object reference not set to an instance of an object.
The idea is to pass the templated object to a property like a command parameter. Here is an example MainPage.xaml:
<Page
x:Class="XBindTest5.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:XBindTest5"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<ResourceDictionary>
<local:OpenItemCommand x:Key="OpenCommand"/>
</ResourceDictionary>
</Page.Resources>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ItemsControl ItemsSource="{x:Bind NewsItems, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:NewsItem">
<StackPanel>
<Button Command="{x:Bind {StaticResource OpenCommand}}" CommandParameter="{x:Bind {RelativeSource Self}}">
<TextBlock Text="{x:Bind Title}"/>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Page>
A simple model is defined in the code-behinde file MainPage.xaml.cs:
using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Windows.UI.Xaml.Controls;
namespace XBindTest5 {
public class NewsItem {
public string Title { get; set; }
}
/// <summary>
/// command to open the item
/// </summary>
public class OpenItemCommand : ICommand {
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) {
return true;
}
public void Execute(object parameter) {
// ... example ...
}
}
public sealed partial class MainPage : Page {
public ObservableCollection<NewsItem> NewsItems { get; set; }
= new ObservableCollection<NewsItem>(new[] {
new NewsItem() { Title = "Item 1" },
new NewsItem() { Title = "Item 2" } });
public MainPage() {
this.InitializeComponent();
}
}
}
Although it seems you have solved your problem, I still want to make some clarifications to avoid confusion and make it clearly for future readers.
As #Peter Duniho has mentioned, {x:Bind} can't work with DataContext property and {x:Bind} doesn't have a Source property, so you can't use StaticResource as data context in {x:Bind}, but you can use a property or a static path instead. While using {x:Bind}, it uses the background class as its data context. For example, when you set ItemsSource="{x:Bind NewsItems, Mode=OneWay}", it uses the XBindTest5.MainPage class as its data context and bind the NewsItems property of this class to ItemsSource. And while inside a DataTemplate, {x:Bind} uses the class declared in x:DataType as its data context. Please note following explanation in DataTemplate and x:DataType:
Inside a DataTemplate (whether used as an item template, a content template, or a header template), the value of Path is not interpreted in the context of the page, but in the context of the data object being templated. So that its bindings can be validated (and efficient code generated for them) at compile-time, a DataTemplate needs to declare the type of its data object using x:DataType.
In your case, you use the Command in DataTemplate, so you can add a OpenCommand property in NewsItem and bind this property to Command to use it.
In your code-behind:
public class NewsItem
{
public string Title { get; set; }
public OpenItemCommand OpenCommand { get; set; }
}
In the XAML:
<DataTemplate x:DataType="local:NewsItem">
<StackPanel>
<Button Command="{x:Bind OpenCommand}" CommandParameter="{x:Bind}">
<TextBlock Text="{x:Bind Title}" />
</Button>
</StackPanel>
</DataTemplate>
Also {x:Bind} doesn't support {RelativeSource}, usually you can name the element and use its name in Path as an alternative. For more information see {x:Bind} and {Binding} feature comparison.
But this can't be used in DataTemplate as all Path are supposed to be a property of NewsItem. And in your case, I think what you want to pass is the NewsItem not the Button, so you can use CommandParameter="{x:Bind}" to pass the NewsItem as the CommandParameter.
PS: There is a small bug in XAML designer, you may still get a Object reference not set to an instance of an object. error. You can add a space after Bind like {x:Bind } as a workaround.
Let me more specifically answer this. There is only one possible data context to x:bind and that is the underlying class. On a page, it is the page (or the code-behind). In a data template, it is the backing class specified in the targettype property of the data template. As an aside, in a control template, x:bind is not supported at all - though it's only a matter of time.
All that is to say that the data context of x:bind is fixed, and depending on where it is being used, I can tell you the data context without looking at your XAML. Why so rigid? In part to make the code generation around it simpler. Also, to make the implementation simpler, too. In either case, this is a fixed rule, and RelativeSource, ElementName, and Source and not supported in x:bind.
This does not mean you cannot reference the relativesource self, you just have to do it with a specified x:name. You would do something like this <Tag x:Name="Jerry" Tag="Nixon" Text="{x:Bind Jerry.Tag}" />.
Why does that particular sample fail? Unlike {binding}, {x:bind} requires matching types, which means setting Text's string can be down-cast and set to Tag's object, but Tag's object cannot be up-cast and set to Text's string value. The take-away for you is using x:bind means your types must match.
I hope this helps get you further along.
Best of luck.

Best way to bind a set of same-type ViewModels to a TabControl in MVVM / WPF

I have an existing ViewModel and View in an MVVM project. Effectively this View presents a collection of items in a particular, styled way. I'll call this existing ViewModel "CollectionPresenter".
Up to now, this has been presented as as follows in XAML:
<Grid>
<ns:CollectionPresenter />
</Grid>
Now, I want to have a dynamic collection of these "CollectionPresenter" view models made available ideally in a tab view.
My approach has been to define an observable collection of these "CollectionPresenters", creating them first on construction of the parent view model. The XAML above then changed to look something like this:
<TabControl ItemsSource="{TemplateBinding CollectionPresenters}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CollectionPresenterTitle}">
</DataTemplate>
<TabControl.ItemTemplate>
<TabControl.ContentTemplate>
... this is where things get confusing
</TabControl.ContentTemplate>
<TabControl>
You can see above my problem is the ContentTemplate.
When I load this up, I get a tab control and it has as many tabs as my observable collection of "CollectionPresenter" objects.
However, the content of the tab control is always empty.
Is this approach correct - and is there a better way regardless?
EDIT: ADDING SOME EXTRA THINGS TO MAKE IT CLEARER
I've tried the below, but it doesn't work. The XAML with the Tab Control (the binding to "Things" works fine):
<TabControl ItemsSource="{TemplateBinding Things}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:Thing}">
<TextBlock Text="{Binding ThingName}" Width="200" Background="Blue" Foreground="White"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:Thing}">
<TextBlock Text="{Binding ThingName}" Width="500" Height="500" Background="Blue" Foreground="White"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The definition for the "Things" observable collection (which is inside the templated parent (ParentObject) of the XAML with the tab control):
public static readonly DependencyProperty ThingsProperty =
DependencyProperty.Register("Things", typeof(ObservableCollection<Thing>), typeof(ParentObject), new PropertyMetadata(null));
public ObservableCollection<Thing> Things
{
get { return (ObservableCollection<Thing>)GetValue(ThingsProperty); }
set { SetValue(ThingsProperty, value); }
}
Stripped down version of the "Thing" view model:
public class Thing : ViewModelBase
{
public Thing()
{
}
public void Initialise(ObservableCollection<Thing> things, string thingName)
{
Things = things;
ThingName = thingName;
}
public static readonly DependencyProperty ThingNameProperty =
DependencyProperty.Register("ThingName", typeof(string), typeof(Thing), new PropertyMetadata(null));
public string ThingName
{
get { return (string)GetValue(ThingNameProperty); }
set { SetValue(ThingNameProperty, value); }
}
}
Looking at my answer to the WPF MVVM navigate views question, you can see this:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use an instance from one of these types in our application, these DataTemplates will tell the framework to display the related view instead.
Therefore, your solution is to simply not hard-code one single DataTemplate to the TabControl.ItemTemplate property, but to leave that blank instead. If you use multiple DataTemplates without providing x:Key values, then they will implicitly be applied when each data object is to be rendered in the TabControl.
UPDATE >>>
Using these DataTemplates should leave your TabControl looking like this:
<TabControl ItemsSource="{TemplateBinding Things}" />
I'm not sure why you're using a TemplateBinding there though as you don't need to define any new templates to get this working... therefore, you should be using a plain old Binding instead.
One other thing that you need to do is to use different data types for each item in the collection that you want to display differently. You could derive custom classes from your Thing class and so the collection could still be of type ObservableCollection<Thing>.

Using datatemplates for converting item source

Lets say I have a ItemsControlwhich is used to render buttons for a list of viewModels
<ItemsControl ItemsSource="{Binding PageViewModelTypes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
CommandParameter="{Binding }" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The PageViewModelTypesare the view models which are available (For example OtherViewModel). For each of the types there is a DataTemplate setup with the according views.
<dx:DXWindow.Resources>
<DataTemplate DataType="{x:Type generalDataViewModel:GeneralViewModel}">
<generalDataViewModel:GeneralView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:OtherViewModel}">
<other:OtherView />
</DataTemplate>
</dx:DXWindow.Resources>
Is there any way of replacing the PageViewModelTypes with the corresponding template types for the ItemsControl within the view?
Bind the button content to the item content and your templates will be resolved to the actual types:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"
CommandParameter="{Binding }" />
</DataTemplate>
</ItemsControl.ItemTemplate>
Unfortunately, your question is not at all clear. The most common scenario that could fit the vague description you've provided is to have each item in the ItemsControl displayed using a DataTemplate that corresponds to that type.
Let's call that Option A.
But the statement:
replacing the PageViewModelTypes with the corresponding template types for the ItemsControl within the view
…could be construed as meaning you want an entirely different data source for the control. I.e. you want to selectively choose a different value for the ItemsSource property.
Let's call that Option B.
Then later, in the comments, you were asked:
do you want to show the template when the user clicks the relevant button?
…and you responded "yes"! Even though that's a completely different behavior than either of the above two.
Let's call that Option C.
Maybe we can encourage you to provide much-needed clarification. But to do that, it seems most fruitful to start with the simplest, most common scenario. Here is an example of code that implements Option A:
XAML:
<Window x:Class="TestSO28429768ButtonTemplate.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestSO28429768ButtonTemplate"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:ColorToBrushConverter x:Key="colorToBrushConverter1"/>
<local:BaseViewModelCollection x:Key="itemsCollection">
<local:StringViewModel Text="Foo"/>
<local:StringViewModel Text="Bar"/>
<local:ColorViewModel Color="Yellow"/>
<local:ColorViewModel Color="LightBlue"/>
</local:BaseViewModelCollection>
<DataTemplate DataType="{x:Type local:StringViewModel}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ColorViewModel}">
<Rectangle Width="50" Height="25"
Fill="{Binding Path=Color, Converter={StaticResource colorToBrushConverter1}}" />
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{StaticResource itemsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
C#:
class BaseViewModelCollection : List<BaseViewModel> { }
class BaseViewModel { }
class StringViewModel : BaseViewModel
{
public string Text { get; set; }
}
class ColorViewModel : BaseViewModel
{
public Color Color { get; set; }
}
class ColorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new SolidColorBrush((Color)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
As you'll see, the ItemsControl displays the Button instances using its default panel, StackPanel. The Content of each Button is bound to the respective item in the ItemsSource collection, a list containing two each of the StringViewModel class and the ColorViewModel class.
Through defined templates in the window's resources, the content presenter of the button uses the DataTemplate associated with each type of view model. Items corresponding to a StringViewModel get the template for that type, i.e. a TextBlock displaying the text of the view model. Likewise, items corresponding to a ColorViewModel instance get the template that displays a rectangle filled with the color from the view model.
If the above does not exactly address your question (and it may well not), please edit your question to clarify what you are asking:
If the above is close, but not precisely what you wanted, please use the above as a reference and explain how what you want to do is different.
If the above has nothing to do with what you wanted, then ignore it. But do be specific about what you actually want, and use precise terminology. For example, if you really want to replace the ItemsSource with a different collection, then saying you want to replace the PageViewModelTypes collection makes sense. But if not, don't use a phrase that seems to say exactly that!
Of course, if either Option B or Option C more closely match what you are trying to do, go ahead and use those as references for your clarifications.
Finally, please check out the very helpful pages How do I ask a good question? and How to create a Minimal, Complete, and Verifiable example. They have lots of great information about how you can express yourself in a way that will allow others to easily understand what you mean. :)

Don't create new view each time with DataTemplate/DataType

I have something like this:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<!-- View 1 Here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<!-- View 2 here -->
</DataTemplate>
<Window.Resources>
<ContentPresenter Content="{Binding}"/>
</Window>
This will automatically swap out the view as I bind different viewmodels, which is very handy.
However, I have one view with a tabcontrol and many subviews. Each subview has several visual parts that are configured by a custom xml file (complex business case). Each time this view is created, the xml file is parsed which causes a small (1-2 second) delay. It's enough of a delay to be annoying and make the UI feel sluggish.
Is there a way to use the DataTemplate pattern without destroying and recreating the view each time a viewmodel is bound? I'd rather not change the viewmodel if possible.
For this case the easiest solution is to have the two views always there and change which one is visible. You can use a converter to change the visibility based on the type of the data context
<View1 Visibility="{Binding Converter={StaticResource TypeToVisibilityConverter, ConverterParameter=VM1}" />
<View2 Visibility="{Binding Converter={StaticResource TypeToVisibilityConverter, ConverterParameter=VM2}" />
And the converter will check if the type matches with the parameter to return Visible, or Collapsed otherwise.
You could wrap your VM into an additional class. Your DataTemplates will decide on the type of the Wrapper class but the real implementation will be exposer through a property of this Wrapper. When this property will change the DataTemplate wont be reloaded but all the bindings will be refreshed.
Wrapper class:
public class WrapperVM1:INotifyPropertyChanged
{
public Content VM1 { get{...} set{...} }
}
public class WrapperVM2:INotifyPropertyChanged
{
public Content VM2 { get{...} set{...} }
}
Now your data templates will describe wrapper class representations:
<DataTemplate DataType="{x:Type local:WrapperVM1}">
<TextBlock Text={Binding Content.SomPropertyInVM1}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:WrapperVM2}">
<TextBlock Text={Binding Content.SomPropertyInVM2}"/>
</DataTemplate>
As you can see if you substitute the Content property of the wrapper with a new instance of VM this won't recreate the view but all bindings will update. However if you need to switch to other type of VM you will have to substitute the Wrapper class by the appropriate Wrapper.

Categories

Resources