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}" ... />.
Related
I am pretty new to C# so I am just starting to learn the basics. Right now I have a ContentControl inside a Window like this:
<ContentControl Content="{Binding}" x:Name="SubView"/>
And I configured my resources of the Windows like this:
<Window.Resources>
<DataTemplate x:Name="StammdatenViewTemplate" DataType="{x:Type viewmodels:StammdatenViewModel}">
<views:StammdatenView DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="AdministrationViewTemplate" DataType="{x:Type viewmodels:AdministrationViewModel}">
<views:AdministrationView DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
In my Window class I am setting the DataContext like this:
DataContext = new StammdatenViewModel();
Here is the thing I would like to do. I want to disable all the TextBoxes inside the ContentControl. I thought about adding a function to my StammdatenView.xaml.cs class (which is the class of my subview), then firing the event from the Window somehow. Though I would need access to the function inside the subview. Is that somehow possible and if yes how? Or would anyone suggest a different approach?
Thanks in advance.
Or would anyone suggest a different approach?
Yes. You should bind the IsEnabled property of each TextBox in the StammdatenView to a boolean property of the StammdatenViewModel.
You can then disable the TextBoxes by setting the source property in the view model class. This is one of the key aspects of the MVVM design pattern, i.e. that you handle your application logic in the view model.
Make sure that the view model class implements the INotifyPropertyChanged interface and provide change notifications as explained on MSDN.
You can create a INotifyPropertyChanged event on your view model, then bind it to the 'IsEnabled={Binding IsTextBoxEnabled}' attribute in your view template for the textbox.
public class ViewModel : BaseViewModel
{
private bool _isTextBoxEnabled;
public bool IsTextBoxEnabled
{
get { return _isTextBoxEnabled; }
set
{
if (value != _isTextBoxEnabled)
_isTextBoxEnabled = value;
this.RaisePropertyChanged("IsTextBoxEnabled");
}
}
}
XAML
<DataTemplate x:Key="template">
<StackPanel Orientation="Horizontal" DataContext="{Binding}">
<TextBox IsEnabled="{Binding IsTextBoxEnabled}" />
</StackPanel>
</DataTemplate>
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.
I'm trying to get my head around MVVM, i'm currently stuck on how to handle navigation.
Currently I have a page and within that page is a frame, that frame is responsible for framing in various other pages. Navigation was previously handled with a drop down box and on selection changed it would navigate that way.
I'm not sure how I could do this without touching the frame from the model view which would end up breaking mvvm.
In the end what I am trying to accomplish is, clicking on the combobox, selecting an item and then having then frame below navigate to the correct view.
I'm not using Prism or any other framework with MVVM, just trying to do it all manually.
The ComboBox would display an ObservableCollection of frame items exposed by your main viewmodel, and the viewmodel will have another property for the selected item.
Your main viewmodel and the frame item viewmodels all inherit from a ViewModelBase class which implements INotifyPropertyChanged, and maybe some other stuff.
So, C#:
public ObservableCollection<ViewModelBase> FrameItems { get; protected set; }
private ViewModelBase _selectedFrameItem;
public ViewModelBase SelectedFrameItem {
get { return _selectedFrameItem; }
set {
value = _selectedFrameItem;
// Defined in ViewModelBase
OnPropertyChanged();
}
}
Your main viewmodel will populate FrameItems in its constructor:
public MainViewModel()
{
FrameItems = new ObservableCollection<ViewModelbase> {
new IceCreamMenu(),
new SmurfOptions(),
new MagicSparklePonyFourierTransformConfiguration()
};
}
Every frame item is a subclass of ViewModelBase. It exposes properties with notifications, including ObservableCollections of any set of child things it may have. And we'll display it by writing a datatemplate for it in just a bit.
Let's assume that you've given your ViewModelBase class a String Title { get; set; } property. Or maybe you'll want to write a subclass of ViewModelBase that introduces Title; your call. For now let's put it in ViewModelBase for simplicity.
XAML -- this leaves out all the layout, but you don't need that here.
<ComboBox
ItemsSource="{Binding FrameItems}"
SelectedItem="{Binding SelectedFrameItem}"
DisplayMemberPath="Title"
/>
<Frame Content={Binding SelectedFrameItem}" />
OK, but how on earth does it know what to do with SelectedFrameItem?!
Easy! Write a resource dictionary called, say, ViewModelDataTemplates.xaml, and merge it into App.xaml so its contents are "visible" in any XAML in your application.
App.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Source is a relative path from project root directory -->
<ResourceDictionary Source="ViewModelDataTemplates.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
...plus whatever theme stuff or whatever.
In ViewModelDataTemplates.xaml, define data templates for your frame item classes.
Say you've got an IceCreamMenu viewmodel, with a collection of Flavors
public ObservableCollection<IceCreamFlavor> Flavors { get; protected set; }
...and a SelectedFlavor. You'd define the namespace vm appropriately with an xmlns:vm attribute on the resource dictionary.
ViewModelDataTemplates.xaml
<DataTemplate DataType="{x:Type vm:IceCreamMenu}">
<Grid>
<ListBox
ItemsSource="{Binding Flavors}"
SelectedItem="{Binding SelectedFlavor}"
/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:IceCreamFlavor}">
<StackPanel Orientation="Horizontal">
<Border
Height="20"
Width="20"
Margin="4"
Background={Binding Color, Converter={StaticResource ColorToBrushConverter}}"
/>
<Label Content="Name" />
</StackPanel>
</DataTemplate>
If you've got existing UserControls that you want to use via datatemplates, that's easy: Say you've got a NotesTabView UserControl that's a view for your NotesTabViewModel, you could define a DataTemplate like this:
<DataTemplate DataType="{x:Type vm:NotesTabViewModel}">
<vw:NotesTabView />
</DataTemplate>
#EdPlunkett: As an alternative to DataTemplate for each view, you can bind your frame to selected page viewmodel using ViewModelToViewConverter like I did here: https://stackoverflow.com/a/31721236/475727
Implicit DataTemplates and DataTemplateSelectors are unique to WPF and XAML, so people think it's recommended solution, but I think it's not suitable for navigation. It feels hackish and it smells with violation of DRY principle.
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>.
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.