Using datatemplates for converting item source - c#

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. :)

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

Conditional xaml layout based on class in UWP

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.

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>.

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.

Confusion about WPF binding

I am trying to bind a 2D array of buttons arranged in stackpanels to a 2D ObservableCollection...
Yet, I'm afraid I don't understand something very elementary about binding.
My XAML:
<Window.Resources>
<DataTemplate x:Key="ItemsAsButtons">
<Button Content="{Binding}" Height="100" Width="100"/>
</DataTemplate>
<DataTemplate x:Key="PanelOfPanels">
<ItemsControl ItemsSource="{Binding Path=DayNumbers}" ItemTemplate=" {DynamicResource ItemsAsButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
...
<ItemsControl x:Name="DaysPanel" Grid.ColumnSpan="7" Grid.Row="2"
ItemTemplate="{DynamicResource PanelOfPanels}"/>
My C# code:
The backend:
/// <summary>
/// Window BE for Calendar.xaml
/// </summary>
public partial class Calendar : Window
{
private CalendarViewModel _vm;
public Calendar()
{
InitializeComponent();
_vm = new CalendarViewModel();
this.DataContext = _vm;
}
}
The ViewModel:
class CalendarViewModel
{
CalendarMonth _displayedMonth;
EventCalendar _calendar;
public CalendarViewModel()
{
_displayedMonth = new CalendarMonth();
}
public ObservableCollection<ObservableCollection<int>> DayNumbers
{
get
{
return _displayedMonth.DayNumbers;
}
}
}
I'm trying to populate the buttons with values from CalendarViewModel.DayNumbers - yet the buttons do not appear. I'm clearly doing something wrong with my binding.
Change all your DynamicResource to StaticResource. This shouldn't stop it working, but might be inefficient at runtime. Have a look this page for WPF resources overview.
Also your ItemsControl is not bound to DayNumbers. Add a binding like so:
<ItemsControl x:Name="DaysPanel" Grid.ColumnSpan="7" Grid.Row="2"
ItemTemplate="{StaticResource PanelOfPanels}"
ItemsSource={Binding DayNumbers}/>
When you set the DataContext on Calendar window you set which object will be the default binding source for the whole window. You didn't specify which property of your ViewModel is bound to the ItemsControl. This is what the code above does.
EDIT Because you are overriding the item template for the ItemsControl and provide a collection container there, you need to provide the ItemsSource for it as well. The syntax {Binding} simply means bind to each member or enumeration, in this case ObservableCollection<int>.
Just to reiterate, the template is exactly that - a template for displaying data. It should be reusable, you should be able to bind it to whatever model you want. A rule of thumb - the data binding to actual data should happen on the control, not the template.
Like Igor said, you need specify ItemsSource={Binding DayNumbers} in outer-most ItemsControl, otherwise, it binds to the DataContext, which is CalendarViewModel and it is not IEnumerable.
Once you do that, it will apply <DataTemplate x:Key="PanelOfPanels"> for each item inside DayNumbers. Note that the DataContext of the DataTemplate in each element in DayNumbers, which is of type ObservableCollection<int>. Here you cannot specify ItemsSource="{Binding Path=DayNumbers}" as DayNumbers is not a valid property in ObservableCollection<int>. Instead, since ObservableCollection<int> is already a IEnumerable, it should be fine not specifying ItemsSource since it will by default bind to DataContext.
Finally, it goes to your inner-most <DataTemplate x:Key="ItemsAsButtons">, and you can put button there as what you did.
Hope it clarifies a little bit. Sorry I don't have the environment to test it out and give you the solution.
Debugging WPF bindings is not straightforward. One tip is you can use dummy converter and set breakpoint in the Convert method to see what it binds.
public class DebugConverter1 : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
#endregion
}
{Binding Converter={StaticResource debugConverter1}}

Categories

Resources