WPF datatemplateselector not getting called - c#

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.

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>

TabControl not finding data templates for TabControl items

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.

Adding buttons to WPF from ObservableCollection using MVVM pattern

I'm trying to add buttons to UserControl. I have to follow MVVM pattern. I have created a class, DeviceButton, with different set/gets and a constructor. In a viewmodel, used as datacontext, is the ObservableCollection and a get-method for the collection. I have bound the collection to a ItemsControl source and tried to add a template. I guess I'm missing something 'cause the buttons won't load.
The UserControl is added into a tab (using dragablz) which as well is a part of a ObservableCollection, also added at run time (this is working just fine). The idea is, that the tab has a list of buttons that has to be created at run time, where the list is fetched from a web service - so the buttons has to be added dynamically/programatically. Overview is just the first tab - a template for each tab (reflecting the fetched items) is being implemented when the buttons work. For now, I'm just adding a test button to the collection, but as stated, this won't show. What am I missing?
I have a Overview.xaml file:
<UserControl // attributes omitted to save spaces... ask if needed>
<StackPanel>
<Border>
<TextBlock FontSize="16" FontWeight="Bold" TextWrapping="WrapWithOverflow"
TextAlignment="Center" HorizontalAlignment="Center"
Foreground="{DynamicResource AccentColorBrush}">
Welcome to Greenhouse App
</TextBlock>
</Border>
<ItemsControl ItemsSource="{Binding DeviceButtons}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vmodel:DeviceButton}">
<Button Width="50" Height="50" Margin="10 10 0 0" Command="{Binding OpenTab}"
CommandParameter="{Binding DeviceType}" HorizontalAlignment="Left"
VerticalAlignment="Top" Style="{DynamicResource SquareButtonStyle}">
<StackPanel>
<Image Source="{Binding ImageUrl}" />
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
// Manually added test button...
<Button x:Name="windMill" HorizontalAlignment="Left" Margin="10,0,0,0"
VerticalAlignment="Top" Width="50" Height="50"
Command="{Binding OpenTab}" FontFamily="Segoe Ui"
Style="{DynamicResource SquareButtonStyle}">
<StackPanel>
<Image Source="/Greenhouse;component/Icons/Windmill.png" />
</StackPanel>
</Button
</StackPanel>
I'm trying to add an ObservableCollection of type DeviceButton, the _deviceButtons collection (Binded as ItemsSource to the ItemsControl)
The tabList items are working just find, and I can manually add more if needed (these will later be added through the OpenNewTab-command, which should be bound to the buttons)
The DeviceButton file:
public class DeviceButton
{
private readonly string _content;
private readonly string _deviceType;
private readonly string _imageUrl;
public DeviceButton(string content, string deviceType, string imageUrl)
{
_content = content;
_deviceType = deviceType;
_imageUrl = imageUrl;
}
public string Content
{
get { return _content; }
}
public string DeviceType
{
get { return _deviceType; }
}
public string ImageUrl
{
get { return _imageUrl; }
}
}
The collection is located in a viewmodel file, MainWindowViewModel.cs:
public class MainWindowViewModel : ViewModelBase, INotifyPropertyChanged
{
public ICommand OpenTab { get; set; }
private string tabControl { get; set; } = "0";
private IInterTabClient _interTabClient;
private ObservableCollection<TabContent> _tabContents = new ObservableCollection<TabContent>();
private ObservableCollection<DeviceButton> _deviceButtons = new ObservableCollection<DeviceButton>();
public MainWindowViewModel()
{
_interTabClient = new MyInterTabClient();
DeviceButtons.Add(new DeviceButton("Windturbine", "windturbine", "/Greenhouse;component/Icons/Windmill.png"));
TabContents.Add(new TabContent("Overview", new Overview()));
OpenTab = new RelayCommand<object>(OpenNewTab);
}
private void OpenNewTab(object obj)
{
MessageBox.Show("Yo"); // Not yet implemented
RaisePropertyChanged(() => tabControl);
}
public ObservableCollection<TabContent> TabContents
{
get { return _tabContents; }
}
public ObservableCollection<DeviceButton> DeviceButtons
{
get { return _deviceButtons; }
}
public IInterTabClient InterTabClient
{
get { return _interTabClient; }
}
}
The buttons aren't loaded when I start the program (only the "test" button added manually in the WPF).
The UserControl, Overview, is considered a tab in another Controls.Metrowindow, MainWindow.xaml:
<Window.Resources>
<Style TargetType="{x:Type dragablz:TabablzControl}">
<Setter Property="CustomHeaderItemTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type viewmodel:TabContent}">
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type viewmodel:TabContent}">
<ContentPresenter Margin="4" Content="{Binding Content}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<dragablz:TabablzControl SelectedIndex="{Binding tabControl}" ItemsSource="{Binding TabContents}" x:Name="InitialTabablzControl" Margin="4 0 4 4">
<dragablz:TabablzControl.InterTabController>
<dragablz:InterTabController InterTabClient="{Binding MyInterTabClient}" />
</dragablz:TabablzControl.InterTabController>
</dragablz:TabablzControl>
I guess it's an issue with the resources/binding in the Overview.xaml, but I've exhausted all suggested solutions I could find.
Issue was in binding
ItemsSource="{Binding Path=DataContext.TabContents,
RelativeSource={RelativeSource AncestorLevel=1,
AncestorType={x:Type Window}, Mode=FindAncestor}}"
I suspect the DataContext is being set to a TabContent object, which does not have a DeviceButtons property
I would suggest using a tool like Snoop to verify your DataContext is what you expect.

Binding to TextBlock in ListView

I don't know if my understanding of binding is just poor or if I am not seeing the problem, but I hope someone can help me out here. I have a ListView with a template of an image and a TextBlock, I need the TextBlock to be bound to the ItemsSource of the ListView. However when I run this I get nothing shown, I don't even see my image that I have set.
XAML:
<UserControl.Resources>
<FontFamily x:Key="FontFamily">MS Reference Sans Serif</FontFamily>
</UserControl.Resources>
<Grid>
<ListView BorderThickness="0" ItemsSource="{Binding Facies}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="../Images/Shale.png"/>
<TextBlock Text="{Binding FaciesName}" Width="75" Margin="5"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
C#:
public partial class FaciesControl : UserControl
{
public FaciesControl()
{
InitializeComponent();
}
public List<string> Facies {get; set;}
public void Bind(string[] data)
{
Facies = new List<string>();
Facies.AddRange(data);
}
}
First set DataContext like this:
public FaciesControl()
{
InitializeComponent();
string[] str = { "Name1", "Name2", "Name3" };
Bind(str); // Make sure you have called the Bind method
DataContext = Facies;
}
Second change your XAML like this:
<ListView BorderThickness="0" ItemsSource="{Binding}">
....
....
<TextBlock Text="{Binding}" Width="75" Margin="5"/>

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