TabControl not finding data templates for TabControl items - c#

I am binding a collection of TabViewModel items to a TabControl. Each of these has a header string property, and a content property of my own custom type BaseTabContentViewModel, an abstract class which each actual tab data viewmodel implements. Eg ValuationTabViewModel which is a sub-class of BaseTabContentViewModel.
I add the new TabViewModel to the Observable<TabViewModel> for the TabControl to pick up and it shows in the UI. I have overridden style templates for the layout of the tab control and header which work fine. The only trouble is the content doesn't find the template in my resource dictionary based on its type, it just displays the full qualified class name of the viewmodel, showing that it is not finding a default template for this class.
Why isn't the ValuationTabViewModel that is being displayed, finding the datatemplate for this type below?
My main view model.
public ObservableCollection<TabViewModel> DetailTabs { get; }
var valuationTab = new TabViewModel(DetailTabConstants.ValuationTab, new ValuationTabViewModel(_eventAggregator, _errorNotifier, _windsorContainer));
DetailTabs = new ObservableCollection<TabViewModel> { valuationTab };
Main XAML
<TabControl Margin="0,-2,0,0" x:Name="SelectionTabs" Style="{StaticResource DetailTabControl}" ItemsSource="{Binding DetailTabs}"
SelectedValue="{Binding SelectedTab, Mode=TwoWay}" ItemContainerStyle="{StaticResource DetailTabItem}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The content style template I want it to use
<DataTemplate x:Key="ValuationTabTemplate" DataType="{x:Type detailTabs1:ValuationTabViewModel}" >
<detailTabs:ValuationTab Margin="0,10,0,10" />
</DataTemplate>
And my Tab item ViewModel class
public class TabViewModel : ViewModelBase
{
private string _header;
private BaseTabContentViewModel _content;
public string Header
{
get => _header;
set
{
_header = value;
RaisePropertyChanged(nameof(Header));
}
}
public BaseTabContentViewModel Content
{
get => _content;
set
{
_content = value;
RaisePropertyChanged(nameof(Content));
}
}
public TabViewModel(string header, BaseTabContentViewModel viewModel)
{
Header = header;
Content = viewModel;
}
}

Remove the <TabControl.ContentTemplate> element and define an implicit DataTemplate (without an x:Key) for each type:
<TabControl Margin="0,-2,0,0" x:Name="SelectionTabs" Style="{StaticResource DetailTabControl}" ItemsSource="{Binding DetailTabs}"
SelectedValue="{Binding SelectedTab, Mode=TwoWay}" ItemContainerStyle="{StaticResource DetailTabItem}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type detailTabs1:ValuationTabViewModel}">
<detailTabs:ValuationTab Margin="0,10,0,10" />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>

I think your custom DataTemplate isn't being used because you have specified both a Key and a DataType for it, and the Key takes precedence.
As per the Microsoft docs:
... if you assign this DataTemplate an x:Key value, you are overriding the implicit x:Key and the DataTemplate would not be applied automatically
I would suggest removing the Key property and just using DataType:
<DataTemplate DataType="{x:Type detailTabs1:ValuationTabViewModel}">
...
</DataTemplate>
Also, as #mm8 implied, you are explicitly setting the ContentTemplate of your TabControl. You need to remove that from the XAML.

Related

wpf bind different datatemplates to different types of objects in contentcontrol

I am new to WPF and MVVM. What I am trying to do is to bind two different DataTemplates to two different kinds of objects in one ContentControl. Each kind of object corresponds to one DataTemplate.
The two kinds of objects are called Unit and Component respectively. They contain different properties. For example a Unit has 3 properties: Id, Name and Manufacture. A Component has 3 properties Id, Type and Materials. The example code is as below:
public class Unit : INotifyPropertyChanged
{
private int _id;
private string _name;
private string _manufacture;
public int Id
{
get {return this._id}
set
{
this._id = value;
OnPropertyChanged("Id")
}
{
public string Name
{
get {return this._name}
set
{
this._id = value;
OnPropertyChanged("Name")
}
{
public string Manufacture
{
get {return this._manufacture}
set
{
this._id = value;
OnPropertyChanged("Manufacture")
}
{
public event PropertyChangedEventHandler PropertyChanged;
...
}
The Component class has the similar structure.
In the MainWindow, I have a ListBox listing names of objects (I will change it to a TreeView in the future) on the left, and a ContentControl on the right. I want that when I select the name of an object, the details of the object will be shown on the right. The code of the MainWindow is as below:
<Windows.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=UnitItems}"
x:Key="UnitDataView">
</CollectionViewSource>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=ComponentItems}"
x:Key="ComponentDataView">
</CollectionViewSource>
<CompositeCollection x:Key="AllDataView
<CollectionContainer Collection="{Binding Source={StaticResource UnitDataView}}" />
<CollectionContainer Collection="{Binding Source={StaticResource ComponentDataView}}" />
</CompositeCollection>
<local: PartDataTemplateSelector x:Key="MyDataTemplateSelector"
UnitTemplate="{StaticResource unitTemplate}"
ComponentTemplate="{StaticResource componentTemplate}" />
</Windows.Resources>
<Grid>
<Grid.ColumnDefinition>
<ColumnDefinition>
<ColumnDefinition>
</Grid.ColumnDefinition>
<ListBox x:Name="ComponentListView" Grid.Column="0"
ItemsSource="{Binding Source={StaticResource AllDataView}}" />
<TabControl Grid.Column="1"
<TabItem Header="Basic Info">
<ContentControl x:Name="BasicInfoContent"
ContentTemplateSelector="{StaticResource MyDataTemplateSelector}"
Content="{Binding Source={StaticResource AllDataView}}">
</ContentControl>
</TabItem>
</TabControl>
</Grid>
The UnitItems and ComponentItems are two ObservableCollection<T> objects defined in App.xaml.cs. And I have defined some DataTemplates in App.xaml. The example code is as below:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="..."
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type src:Unit}">
<!-- This template is to show the name of a unit object in the ListBox -->
</DataTemplate>
<DataTemplate DataType="{x:Type src:Component}">
<!-- This template is to show the name of a component object in the ListBox -->
</DataTemplate>
<DataTemplate x:Key="unitTemplate" DataType="{x:Type src:Unit}">
<!-- This template is to show the details of a unit object in the ContentControl -->
</DataTemplate>
<DataTemplate x:Key="componentTemplate" DataType="{x:Type src:Component}">
<!-- This template is to show the details of a component object in the ContentControl -->
</DataTemplate>
</Application.Resources>
And my custom DataTemplateSelector is as below:
class MyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate UnitTemplate { get; set; }
public DataTemplate ComponentTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
swith (item)
{
case Unit _:
return UnitTemplate;
case Component _:
return ComponentTemplate;
}
return null;
}
}
I have read this article ContentTemplateSelector and tried the ContentTemplateSelector, but since I use a CompositeCollection and CollectionContainer to bind these two kinds of objects in the ContentControl, the item object in my DataTemplateSelector class receives the CompositeCollection type, not a Unit type nor a Component type, so there is no proper template being returned. Also I tried the method mentioned in this article DataType Property, which is to set a DataType property for each of the DataTemplate and set the Path to "/". Maybe I misunderstood it, but it did not work either, where I think it has the same issue with the ContentTemplateSelector one. So anybody can help me on this problem?
It is my very first time to ask a question on Stack Overflow. I know some of my description and codes are trivial to this question, but I just don't want to miss any details that may be related to my problem. I apologise for that. Also if there are any problem with my coding style and data structure, please feel free to point it out. I really appreciate it. Thank you for your reading and help!
You do not need a DataTemplateSelector. Just make sure that the detail DataTemplates can be automatically selected, by not assining a key to them.
It also seems that you don't need two collections for your objects. You might as well derive both Unit and Component from a common base class and have a single collection of base class references.
Finally there should be a view model, which besides the objects collection also has a property for the currently selected object.
Take this simplified example view model:
public class Base
{
public int Id { get; set; }
}
public class Unit : Base
{
public string UnitData { get; set; }
}
public class Component : Base
{
public string ComponentData { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Base> Objects { get; }
= new ObservableCollection<Base>();
private Base selectedObject;
public Base SelectedObject
{
get { return selectedObject; }
set
{
selectedObject = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedObject)));
}
}
}
An instance of it should be assigned to the window's DataContext:
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.Objects.Add(new Unit { Id = 1, UnitData = "Unit Data" });
vm.Objects.Add(new Component { Id = 2, ComponentData = "Component Data" });
DataContext = vm;
}
Finally, the XAML would be this:
<ListBox ItemsSource="{Binding Objects}"
SelectedItem="{Binding SelectedObject}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:Unit}">
<TextBlock>
<Run Text="Unit, Id:"/>
<Run Text="{Binding Id}"/>
</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Component}">
<TextBlock>
<Run Text="Component, Id:"/>
<Run Text="{Binding Id}"/>
</TextBlock>
</DataTemplate>
</ListBox.Resources>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedObject}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Unit}">
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding UnitData}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Component}">
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<TextBlock Text="{Binding ComponentData}"/>
</StackPanel>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>

TemplateSelector doesn't work

I have Items Control, but I want improve this code for working with different types of input data.
<Grid>
<ItemsControl x:Name="control"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ItemsSource="{x:Bind ItemsSource, Mode=OneWay}"
ItemTemplate="{x:Bind CellTemplate, Mode=OneWay, Converter={StaticResource SimpleSelector}}">
<!--I want make like this-->
<ContentControl VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
ContentTemplate="{Binding SelectedCollageTemplate, Converter={StaticResource CollageTemplateSelector}}" />
<!-- -->
<!--now I have this-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:SimplePanel SelectedCollage="{Binding SelectedCollage, Mode=TwoWay}"
SelectedCollagePattern="{Binding SelectedCollagePattern}">
<controls:SimplePanel.Background>
<ImageBrush Stretch="Fill"
ImageSource="ms-appx:///Images/Background/5.jpg" />
</controls:SimplePanel.Background>
</controls:SimplePanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- -->
</ItemsControl>
</Grid>
As you can see I want change hardcode to more flexible way and use Template selector
I create selector:
<templateSelector:CollageTemplateSelector x:Key="CollageTemplateSelector"
SimpleTemplate="{StaticResource SimpleTemplate}"
ShapeTemplate="{StaticResource ShapeTemplate}"/>
And added DataTemplate:
<DataTemplate x:Key="SimpleTemplate">
<controls:SimplePanel
SelectedCollage="{Binding SelectedCollage, Mode=TwoWay}"
SelectedCollagePattern="{Binding SelectedCollagePattern}">
<controls:SimplePanel.Background>
<ImageBrush Stretch="Fill"
ImageSource="ms-appx:///Images/Background/5.jpg" />
</controls:SimplePanel.Background>
</controls:SimplePanel>
My converter returns Simple Panel. But when I lauch it my SimplePanel doesnt start(I have break point on constructor) and part of code doesnt work. What is my problem?
You're setting the ContentTemplate of your ContentControl to your selector; you should set the ContentTemplateSelector property instead.
In your ItemsControl you're setting ItemsTemplate to something that looks like a template selector; you should set the ItemsTemplateSelector property instead.
You shouldn't bind to template selectors, but access them as StaticResources.
I don't fully understand the details of what you're trying to do, so here's an example of a DataTemplateSelector that works.
To start with, I'm using the following ItemsSource, with the intent of making the string "Three" show in red:
public string[] ItemsSource => new[]
{
"One", "Two", "Three",
};
The template selector has two DataTemplate properties that will be set from XAML -- one for "Three" strings; another for all other strings:
public sealed class ItemTemplateSelector : DataTemplateSelector
{
/// <summary>
/// This property is set in XAML.
/// </summary>
public DataTemplate NormalTemplate { get; set; }
/// <summary>
/// This property is set in XAML.
/// </summary>
public DataTemplate ThreeTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if ("Three".Equals(item))
{
return ThreeTemplate;
}
return NormalTemplate;
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return SelectTemplateCore(item);
}
}
The data template selector has two versions of SelectTemplateCore. This page discusses which one to use under what circumstances:
If your ItemsControl.ItemsPanel is an ItemsStackPanel or ItemsWrapGrid, provide an override for the SelectTemplateCore(Object) method. If the ItemsPanel is a different panel, such as VirtualizingStackPanel or WrapGrid, provide an override for the SelectTemplateCore(Object, DependencyObject) method.
The XAML (which assigns data templates to the selector's two properties) looks like this:
<Grid>
<Grid.Resources>
<local:ItemTemplateSelector x:Key="ItemTemplateSelector">
<local:ItemTemplateSelector.NormalTemplate>
<DataTemplate>
<TextBlock Foreground="Blue" Text="{Binding}" />
</DataTemplate>
</local:ItemTemplateSelector.NormalTemplate>
<local:ItemTemplateSelector.ThreeTemplate>
<DataTemplate>
<TextBlock Foreground="Red" Text="{Binding}" />
</DataTemplate>
</local:ItemTemplateSelector.ThreeTemplate>
</local:ItemTemplateSelector>
</Grid.Resources>
<ItemsControl
VerticalAlignment="Center"
HorizontalAlignment="Center"
ItemsSource="{x:Bind ItemsSource, Mode=OneWay}"
ItemTemplateSelector="{StaticResource ItemTemplateSelector}">
</ItemsControl>
</Grid>
The result looks like this, with the string "Three" shown in red:
I hope this is sufficient to put you on the right track.

How to correctly databind collection to TabControl?

I've looked at several qustions/answers on SO and for some reason I'm not getting anything to work for binding a collection to the TabControl. I am trying to do this so I don't have to assign the DataContext in the code-behind.
Here is the view model:
public class DocumentsCollectionViewModel : IEnumerable<DocumentViewModel> {
private readonly ObservableCollection<DocumentViewModel> mDocsCollection = new ObservableCollection<DocumentViewModel>();
public ObservableCollection<DocumentViewModel> Documents {
get { return mDocsCollection; }
}
// initially excluded from question as I thought it was understood :)
public IEnumerator<DocumentViewModel> GetEnumerator() {
return mDocsCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return mDocsCollection.GetEnumerator();
}
}
...for completeness sake, the DocumentViewModel:
public class DocumentViewModel {
private readonly Document mDocument;
public string Name {
get { return mDocument.Name; }
}
}
In the XAML, I am a little confused about where to tell the tab control to use the Documents property in DocumentsCollectionViewModel:
<TabControl Name="DocumentsTab"
ItemsSource="{Binding localmodels:DocumentsCollectionViewModel}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Have you set the DataContext of your Window/UserControl which has this TabControl to the instance of your DocumentsCollectionViewModel?
Try doing this in the constructor of your Window containing the TabControl
public void MainWindow()
{
InitializeComponents();
this.DataContext = new DocumentsCollectionViewModel();
//Initialize the collection inside your VM
}
OR you can set DataContext in xaml like
<Window>
<Window.DataContext>
<localmodels:DocumentsCollectionViewModel/>
</Window.DataContext>
</Window>
then in your xaml just directly bind to Documents property
<TabControl Name="DocumentsTab"
ItemsSource="{Binding Documents}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type localmodels:DocumentViewModel}">
<Label Style="{StaticResource DefaultFont}"
Content="{Binding Name}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

WPF datatemplateselector not getting called

Hello I'm trying to dynamically change datatemplate but my method SelectTemplate in class TreeViewItemTemplateSelector never getting called (I've checked it by debugger) :( please help me :)
Code from xaml MainWindow:
Code in code behind:
Your problem seems to be that your TreeViewCustomItem is inheriting from TreeViewItem. (As seen in http://pastebin.com/jnP2nWMF)
Removing that inheritance (and the dependency property) causes the template selector to fire fine. What were/are you trying to achieve with the node item?
Looking at the OutputWindow, I get this message:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='TreeViewCustomItem'
You don't have to have items inherit from the TreeViewItem in order to bind them to a TreeView, TreeViewItem is something that the TreeView uses to hold the data, and then the DataTemplate is used to present the data.
Move DataTemplates from TreeView.Resources to Window.Resources
<Window.Resources><DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate><DataTemplate x:Key="Regulation">
<TextBlock Text="{Binding Path=Header}" FontWeight="Bold"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="Article">
<TextBlock Text="{Binding Path=Header}" Foreground="Green"></TextBlock>
</DataTemplate>
<local:TreeViewItemTemplateSelector x:Key="TemplateSelector" DefaultTemplate="{StaticResource DefaultTemplate}" ArticleTemplate="{StaticResource Article}" RegulationTemplate="{StaticResource Regulation}" />
and make change
<TreeView ItemTemplateSelector="{StaticResource TemplateSelector}" Height="409" HorizontalAlignment="Left" Margin="10,10,0,0" Name="treeView1" VerticalAlignment="Top" Width="277" ItemsSource="{Binding}"/>
Update code and we will see. I put similar code into VS and it works so we need to take a closer look. So i checked this and changed
public class TreeViewCustomItem
{
public string Header { get; set; }
}
and this
listmy = new ObservableCollection<TreeViewCustomItem> { new TreeViewCustomItem { Header = "xD" }, new TreeViewCustomItem { Header = "xxD" } };
//treeView1.ItemsSource = listmy;
this.DataContext = listmy;
public class selector : DataTemplateSelector
{
public DataTemplate RegulationTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
TreeViewCustomItem treeViewItem = item as TreeViewCustomItem;
if (treeViewItem.Header == "xD")
{
return RegulationTemplate;
}
else
{
return DefaultTemplate;
}
}
}
and in XAML looks like this
xmlns:local="clr-namespace:WpfApplication1.Views">
<Window.Resources>
<DataTemplate x:Key="DefaultTemplate">
<TextBlock Text="{Binding Header}"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="Regulation">
<TextBlock Text="{Binding Path=Header}" FontWeight="Bold"></TextBlock>
</DataTemplate>
<local:selector x:Key="selector_" DefaultTemplate="{StaticResource DefaultTemplate}" RegulationTemplate="{StaticResource Regulation}"/>
</Window.Resources>
<Grid>
<TreeView Height="409" HorizontalAlignment="Left" Margin="10,10,0,0" Name="treeView1" VerticalAlignment="Top" Width="277"
ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource selector_}"/>
</Grid>
And it works so my presumption is that problem is inside TreeViewCustomItem.

ItemsControl inside my UserControl is not updating

I'm having trouble with my UserControl with following source code:
[ContentProperty("SetContent")]
public partial class HeaderContainer : UserControl
{
// Header region
public FrameworkElement SetContent
{
get { return (FrameworkElement)GetValue(SetContentProperty); }
set { SetValue(SetContentProperty, value); }
}
public static readonly DependencyProperty SetContentProperty =
DependencyProperty.Register("SetContent", typeof(FrameworkElement),
typeof(HeaderContainer), new PropertyMetadata(null));
public HeaderContainer()
{
InitializeComponent();
DataContext = this;
}
}
This is my XAML
<StackPanel>
<Border>
<TextBlock Text="{Binding Header}" />
</Border>
<ContentPresenter Content="{Binding SetContent}"/>
</StackPanel>
And here is my Problem:
<c:HeaderContainer Header="List">
<ItemsControl ItemsSource="{Binding ObjectList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding DisplayName}"/>
<TextBlock Text="{Binding SecondLine}" Foreground="Gray" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</c:HeaderContainer>
The Itemscontrol itself is working, also the UserControl, when I add content in XAML. The problem is, when I add Items to ObjectList while it's in the HeaderContainer - nothing happens. Where am I thinking wrong?
It is hard to determine the exact issue here because your code is incomplete. You have a SetContent dependency property, however your example usage doe not use it. I think your approach should be to sub-class ContentControl, adding your Header property to this.
Or ... just use the Silverlight Toolkit HeaderedContentControl which I think does exactly what you are trying to achieve!

Categories