I have a container view that looks something like this
<UserControl x:Class="Views.ContainerView">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type viewmodels:AViewModel}">
<views:MyView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:BViewModel}">
<views:MyView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:CViewModel}">
<views:MyView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:DViewModel}">
<views:MyView />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=AvailableViewModels}"
SelectedItem="{Binding Path=CurrentViewModel}"
IsSynchronizedWithCurrentItem="True" />
<ContentControl Content="{Binding Path=CurrentViewModel}" />
</Grid>
</UserControl>
All my viewmodels inherit BaseViewModel so I turned my view into this
<UserControl x:Class="Views.ContainerView">
<UserControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type viewmodels:BaseViewModel}">
<views:MyView />
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Path=AvailableViewModels}"
SelectedItem="{Binding Path=CurrentViewModel}"
IsSynchronizedWithCurrentItem="True" />
<ContentControl Content="{Binding Path=CurrentViewModel}" />
</StackPanel>
</UserControl>
thinking it would instantiate just a single MyView and just rebind the viewmodel when ListBox.SelectedItem changes. Am I understanding this behavior correctly? Is this a preferred practice? How can I verify that I'm not churning memory as I switch between views?
To expand on Pavel's answer and clarify what happens when to the views when you change view-models is that a new view will be generated for the new view-model and the old view will hopefully be garbage collected in time.
The problem with this is sometimes we will have views that registers to some event in code behind (non-weak events) and this will prevent the view from being collected and you will have memory leaks.
Two approaches.
Any event subscription in view code-behind should be weaken (EventAggregator in PRISM) to allow garbage collection.
Register an instance of the view with the unity container and resolve it when you need to reuse it. Before you inject it into the region, just update the DataContext.
Hope this helps.
It will instantiate a new MyView for each view model you use. If you want to reuse your user controls, you can set the DataContext property on each user control.
Related
This is originally a question about Teleriks TabbedWindow control, but its really a generic.
Question. In a ItemTemplate, how to I bind to both the view and properties of the viewmodel
Below, my datasource is a list of Views (ie UserControls). I want to have the View presented in the ContentControl and some properties of the viewmodel presented in the header.
<telerik:RadTabbedWindow x:Class="Porter.Application.Views.MainWindow"
...
ItemsSource="{Binding Tabs2}">
<telerik:RadTabbedWindow.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.TabHeader}" />
</DataTemplate>
</telerik:RadTabbedWindow.ItemTemplate>
<telerik:RadTabbedWindow.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</telerik:RadTabbedWindow.ContentTemplate>
</telerik:RadTabbedWindow>
UPDATED RESULT AFTER ANSWER FROM mm8
<telerik:RadTabbedWindow
ItemsSource="{Binding Tabs2}" <!--list of ViewModels (lets say ViewModelBase.cs)-->
...>
<telerik:RadTabbedWindow.Resources>
<DataTemplate DataType="{x:Type acc:SearchAccountsViewModel}">
<acc:SearchAccountsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type hello:HelloWorldViewModel}">
<hello:HelloWorldView/>
</DataTemplate>
</telerik:RadTabbedWindow.Resources>
<telerik:RadTabbedWindow.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabHeader}" />
</DataTemplate>
</telerik:RadTabbedWindow.ItemTemplate>
<telerik:RadTabbedWindow.ContentTemplate>
<DataTemplate >
<ContentControl Content="{Binding}" />
</DataTemplate>
</telerik:RadTabbedWindow.ContentTemplate>
</telerik:RadTabbedWindow>
The Tab2 property should return an IEnumerable<T> where the type T has some public properties that you bind to in the XAML markup.
It may for example have a TabHeader property that you bind the header of the tab to in the ItemTemplate like this:
<telerik:RadTabbedWindow x:Class="Porter.Application.Views.MainWindow"
...
ItemsSource="{Binding Tabs2}">
<telerik:RadTabbedWindow.ItemTemplate>
<DataTemplate>
<TextBlock Text = "{Binding TabHeader}" />
</ DataTemplate >
</ telerik:RadTabbedWindow.ItemTemplate>
</telerik:RadTabbedWindow>
The ContentTemplate should be resolved automatically provided that you have defined a DataTemplate for the type T in scope of the RadTabbedWindow, for example in your App.xaml. It's in this template that you add your UserControl:
<DataTemplate DataType="{x:Type local:YourClass}">
<local:UserControl1 />
</DataTemplate>
You should not create a UserControl in the view model and add it to Tabs2. This breaks what the MVVM pattern is all about, i.e. separation of concerns. A view model doesn't create views.
If you don't have/want an implicit DataTemplate in App.xaml, you may of course also define the ContentTemplate inline:
<telerik:RadTabbedWindow x:Class="Porter.Application.Views.MainWindow"
...
ItemsSource="{Binding Tabs2}">
<telerik:RadTabbedWindow.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabHeader}" />
</DataTemplate>
</telerik:RadTabbedWindow.ItemTemplate>
<telerik:RadTabbedWindow.ContentTemplate>
<DataTemplate>
<local:UserControl1 />
</DataTemplate>
</telerik:RadTabbedWindow.ContentTemplate>
</telerik:RadTabbedWindow>
The key point is that you bind to properties of T in both templates and that T is a POCO and not a control of some kind.
in mvvm pattern we encapsulate viewmodel in to view and exposing it as DataContext and doing binding for controls
Is there another way than this type of binding (without calling the DataContext)
pls let me clear regarding this
Thanks in advance
You don't have to set any DataContext properties to connect views to view models. You can just set up a number of DataTemplates instead:
<DataTemplate DataType="{x:Type ViewModels:FirstViewModel}">
<Views:FirstView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:LastViewModel}">
<Views:LastView />
</DataTemplate>
If you declare these in the App.xaml file, they will be available throughout your application.
I want to achieve the following:
My ViewModel exposes a property named 'Categories' which is a collection of CategoryViewModel objects
Each CategoryViewModel object exposes a property called 'Items' which is a collection of strings*.
On my View, I want a TabControl with a TabItem for each object in the 'Categories' collection.
The Content of each TabItem should be a xceed DataGrid control displaying the contents of the selected tab's Items collection.
<TabControl ItemsSource="{Binding Categories}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CategoryName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding Items}"
AutoCreateColumns="True">
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
This works ok when I bind directly to the ItemsSource property of the DataGridControl. However, in order to utilize all of the functionality of the DataGridControl, I need to bind the ItemsSource property of the DataGridControl to a DataGridCollectionViewSource object that is bound to my Items collection. I do this when the grid ISN'T nested in another control by creating a DataGridCollectionViewSource object in the Resources section of the UserControl and binding to that.
<UserControl>
<UserControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</UserControl.Resources>
<Grid>
<xcdg:DataGridControl
ItemsSource="{Binding Source={StaticResource GridData}}"
AutoCreateColumns="True">
</xcdg:DataGridControl>
</Grid>
</UserControl>
How do I need to structure the XAML so that when the TabControl is being bound, a DataGridCollectionViewSource object is created for each TabItem so that the DataGridControl that is generated within the content of the TabItem can be bound to it?
Clear as mud, right? :)
Thanks!
Notes:
*In the real solution the collection contains objects of a class that is more complex than a simple string, but a string was used to make the example more simple.
OK, this is a bit of a long-shot, but could you use the DataGrid.Tag ...
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding RelativeSource={RelativeSource Self}, Path=Tag}"
AutoCreateColumns="True">
<xcdg:DataGridControl.Tag>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</xcdg:DataGridControl.Tag>
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
Or ... resources can be defined on any FrameworkElement, so you could try:
<TabControl.ContentTemplate>
<DataTemplate>
<xcdg:DataGridControl
ItemsSource="{Binding Source={StaticResource GridData}}"
AutoCreateColumns="True">
<xcdg:DataGridControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
Source="{Binding Items}" />
</xcdg:DataGridControl.Resources>
</xcdg:DataGridControl>
</DataTemplate>
</TabControl.ContentTemplate>
I don't use the eXceed Grid so cannot test whether these work - just a couple of ideas to try!
Colin E.
You can use x:Shared="True" attribute on a resource. That means a new instance is created for every use of that resource.
Example:
<UserControl.Resources>
<xcdg:DataGridCollectionViewSource x:Key="GridData"
x:Shared="False"
Source="{Binding Items}" />
</UserControl.Resources>
I've got a set of ViewModels that I'm binding to the ItemsSource property of a TabControl. Let's call those ViewModels AViewModel, BViewModel, and CViewModel. Each one of those needs to have a different ItemTemplate (for the header; because they each need to show a different icon) and a different ContentTemplate (because they have very different interaction models).
What I'd like is something like this:
Defined in Resource.xaml files somewhere:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type CViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate x:Key="ContentTemplate" DataType="{x:Type CViewModel}">
...
</DataTemplate>
Defined separately:
<TabControl ItemTemplate="[ Some way to select "ItemTemplate" based on the type ]"
ContentTemplate="[ Some way to select "ContentTemplate" based on the type ]"/>
Now, I know that realistically, each time I define a DataTemplate with the same key the system is just going to complain. But, is there something I can do that's similar to this that will let me put a DataTemplate into a TabControl based on a name and a DataType?
The easiest way would be to use the automatic template system, by including the DataTemplates in the resources of a ContentControl. The scope of the templates are limited to the element they reside within!
<TabControl ItemsSource="{Binding TabViewModels}">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type CViewModel}">
...
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="{x:Type AViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type BViewModel}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type CViewModel}">
...
</DataTemplate>
</TabControl.Resources>
</TabControl>
You can remove the x:Key :) This will automatically apply the template when the given type is encountered (probably one of the most powerful and underused features of WPF, imo.
This Dr. WPF article goes over DataTemplates pretty well. The section you'll want to pay attention to is "Defining a Default Template for a Given CLR Data Type".
http://www.drwpf.com/blog/Home/tabid/36/EntryID/24/Default.aspx
If this doesn't help your situation, you might be able to do something close to what you are looking for using a Style (ItemContainerStyle) and setting the content and header based on the type using a data trigger.
The sample below hinges on your ViewModel having a property called "Type" defined pretty much like this (easily put in a base ViewModel if you have one):
public Type Type
{
get { return this.GetType(); }
}
So as long as you have that, this should allow you to do anything you want. Note I have "A Header!" in a textblock here, but that could easily be anything (icon, etc).
I've got it in here two ways... one style applies templates (if you have a significant investment in these already) and the other just uses setters to move the content to the right places.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:WpfApplication1">
<Window.Resources>
<CompositeCollection x:Key="MyCollection">
<local:AViewModel Header="A Viewmodel" Content="A Content" />
<local:BViewModel Header="B ViewModel" Content="B Content" />
</CompositeCollection>
<DataTemplate x:Key="ATypeHeader" DataType="{x:Type local:AViewModel}">
<WrapPanel>
<TextBlock>A Header!</TextBlock>
<TextBlock Text="{Binding Header}" />
</WrapPanel>
</DataTemplate>
<DataTemplate x:Key="ATypeContent" DataType="{x:Type local:AViewModel}">
<StackPanel>
<TextBlock>Begin "A" Content</TextBlock>
<TextBlock Text="{Binding Content}" />
</StackPanel>
</DataTemplate>
<Style x:Key="TabItemStyle" TargetType="TabItem">
<Style.Triggers>
<!-- Template Application Approach-->
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:AViewModel}">
<Setter Property="HeaderTemplate" Value="{StaticResource ATypeHeader}" />
<Setter Property="ContentTemplate" Value="{StaticResource ATypeContent}" />
</DataTrigger>
<!-- Just Use Setters Approach -->
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Type local:BViewModel}">
<Setter Property="Header">
<Setter.Value>
<WrapPanel>
<TextBlock Text="B Header!"></TextBlock>
<TextBlock Text="{Binding Header}" />
</WrapPanel>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="{Binding Content}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemContainerStyle="{StaticResource TabItemStyle}" ItemsSource="{StaticResource MyCollection}" />
</Grid>
HTH, Anderson
One way would be to use DataTemplateSelectors and have each one resolve the resource from a separate ResourceDictionary.
In this example I use DataTemplates in the resources section of my TabControl for each view model I want to display in the tab items.
In this case I map ViewModelType1 to View1 and ViewModelType2 to View2.
The view models will be set as DataContext object of the views automatically.
For displaying the tab item header, I use an ItemTemplate.
The view models I bind to are of different types, but derive from a common base class ChildViewModel that has a Title property. So I can set up a binding to pick up the title to display it in the tab item header.
In addition I display a "Close" Button in the tab item header. If you do not need that, just remove the button from the example code so you just have the header text.
The contents of the tab items are rendered with a simple ItemTemplate which displays the view in a content control with Content="{Binding}".
<UserControl ...>
<UserControl.DataContext>
<ContainerViewModel></ContainerViewModel>
</UserControl.DataContext>
<TabControl ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding SelectedViewModel}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type ViewModelType1}">
<View1/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelType2}">
<View2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock Text="{Binding Title}" />
<Button DockPanel.Dock="Right" Margin="5,0,0,0"
Visibility="{Binding RemoveButtonVisibility}"
Command="{Binding DataContext.CloseItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TypeOfContainingView}}}"
>
<Image Source="/Common/Images/ActiveClose.gif"></Image>
</Button>
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</UserControl>
The user control which contains the tab control has a container view model of type ContainerViewModel as DataContext. Here I have a collection of all the view models displayed in the tab control. I also have a property for the currently selected view model (tab item).
This is a shortened version of my container view model (I skipped the change notification part).
public class ContainerViewModel
{
/// <summary>
/// The child view models.
/// </summary>
public ObservableCollection<ChildViewModel> ViewModels {get; set;}
/// <summary>
/// The currently selected child view model.
/// </summary>
public ChildViewModel SelectedViewModel {get; set;}
}
Josh Smith uses exactly this technique (of driving a tab control with a view model collection) in his excellent article and sample project WPF Apps With The Model-View-ViewModel Design Pattern. In this approach, because each item in the VM collection has a corresponding DataTemplate linking the View to the VM Type (by omitting the x:Key as Anderson Imes correctly notes), each tab can have a completely different UI. See the full article and source code for details.
The key parts of the XAML are:
<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
<vw:CustomerView />
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
There is one downside - driving a WPF TabControl from an ItemsSource has performance issues if the UI in the tabs is big/complex and therefore slow to draw (e.g., datagrids with lots of data). For more on this issue, search SO for "WPF VirtualizingStackPanel for increased performance".
I have a listbox and I want to iterate over a collection of Bars in my Foo-object.
<ListBox DataContext="{Binding Path=Foo.Bars}" >
<ListBox.Items>
<ListBoxItem>
<ContentControl DataContext="{Binding Path=.}" />
</ListBoxItem>
</ListBox.Items>
</ListBox>
This is the datatemplate I want to use.
<DataTemplate DataType="{x:Type Bar}">
<Label Content="hello stackoverflow" />
</DataTemplate>
If I snoop (--> examine by using the tool Snoop) my application, I notice that the entire collection of Bars is bound to the ContentControl, in stead of just 1.
How can I properly bind so the iteration over the collection goes fine?
You can just set the DataTemplate, and WPF does all the work. Set the ItemsSource to a list of Bar items, and then define a DataTemplate for Bar items.
<ListBox ItemsSource="{Binding Path=Foo.Bars}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type Bar}">
<Label Content="hello stackoverflow" />
</DataTemplate>
</ListBox.Resources>
</ListBox>
You could also set the ItemsTemplate directly by using <ListBox.ItemTemplate> instead of <ListBox.Resources>
See Data Binding Overview at MSDN.
First add your namespace to the Window element (Intellisense) :
xmlns:local="clr-namespace:yourenamespace"
Then the following XAML ( in Window.Resources is a clean way to do it ) :
<Window.Resources>
<ObjectDataProvider x:Key="DataProvider" ObjectType="{x:Type local:Foo}"/>
<DataTemplate x:Key="Template" >
<TextBlock Text="{Binding Bar}"/>
</DataTemplate>
</Window.Resources>
Place the Listbox :
<ListBox DataContext="{Binding Source={StaticResource DataProvider}}" ItemsSource="{Binding Bars}" ItemTemplate="DynamicResource Template" />
But, it depends on your code-behind object, you have to set a constructor to initialise public properties within your object which are ObservableCollection<> preferably (There is some restriction rules with object instance in XAML).