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.
Related
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.
I am working on a Treeview control. The items control should display a set of textbox and combobox dynamically depending on the value of the data structure.
The ArgumentTypeTemplateSelector 's convert code is executed. however, no Textbox and combo is display. Please would someone kindly help. thank you.
The tree View (xaml)
<ItemsControl x:Name="argumentTexts" ItemsSource="{Binding ArgumentDetailsCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type structures:ArgumentDetails}">
<ItemsControl x:Name="items" ItemsSource="{Binding DefaultValue}"
ItemTemplateSelector="{Binding DefaultValue, Converter={StaticResource ArgTypeTemplateSelector}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public class ArgumentTypeTemplateSelector : IValueConverter
{
private DataTemplate comboboxDataTemplate;
private DataTemplate textboxDataTemplate;
public DataTemplate ComboBoxDataTemplate
{
get
{
return this.comboboxDataTemplate;
}
set
{
this.comboboxDataTemplate = value;
}
}
public DataTemplate TextBoxDataTemplate
{
get
{
return this.textboxDataTemplate;
}
set
{
this.textboxDataTemplate = value;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string str = (string)value;
if (str.Contains("1"))
{
return this.ComboBoxDataTemplate;
}
return this.TextBoxDataTemplate;
}
In the resourcedictionary(xaml)
<DataTemplate x:Key="TextBoxDataTemplate">
<TextBox Text="{Binding DefaultValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"
Width="Auto"
Margin="5,0,0,0"
Padding="0"
Style="{StaticResource GridEditStyle}"
IsEnabled="True"/>
</DataTemplate>
<DataTemplate x:Key="ComboBoxDataTemplate">
<ComboBox HorizontalAlignment="Stretch" IsEnabled="True"/>
</DataTemplate>
<columnConfiguratorControls:ArgumentTypeTemplateSelector x:Key="ArgTypeTemplateSelector" ComboBoxDataTemplate="{StaticResource ComboBoxDataTemplate}" TextBoxDataTemplate="{StaticResource TextBoxDataTemplate}"/>
</ResourceDictionary>
DataTemplateSelectors work in a different way than converters. Your DataTemplateSelector will retrieve the item of your list as argument and depending on the criteria you define, you can choose a DataTemplate that should be returned.
Try the following:
In your xaml file define your DataTemplateSelector as static resource and set it in your ItemsControl:
<ItemsControl x:Name="argumentTexts" ItemsSource="{Binding ArgumentDetailsCollection}">
<ItemsControl.Resources>
<!-- define your template selector as static resource to reference it later -->
<ns:ArgumentTypeTemplateSelector x:Key="ArgumentTypeTemplateSelector"/>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Stretch" IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type structures:ArgumentDetails}">
<!-- use your template selector here -->
<ItemsControl x:Name="items" ItemsSource="{Binding DefaultValue}"
ItemTemplateSelector="{StaticResource ArgumentTypeTemplateSelector }"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In your code behind file, you need to implement your data template selector for example in the following way:
// derive from base class DataTemplateSelector
public class ArgumentTypeTemplateSelector : DataTemplateSelector
{
// override SelectTemplate method
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
// get access to the resources you need, for example
// by accessing the UI object where your selector is placed in
var frameworkElement = (FrameworkElement) container;
// return a data template depending on your custom logic,
// you can cast "item" here to the specific type and check your conditions
if(item has condition)
return (DataTemplate) frameworkElement.FindResource("YourDataTemplateKey");
else
// ...
return base.SelectTemplate(item, container);
}
}
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.
The Structure
The scenario is that I have a Pivot where each item is the menu of a certain day. Inside that PivotItem I need to display the dishes on the menu, grouped by category (e.g. soups, desserts, ...).
I have implemented this with the MVVM-model.
I thus have the folowing models:
public class Menu{
public string Date;
public List<DishList> Categories;
}
public class DishList
{
public string Category;
public List<Dish> Dishes;
}
public class Dish
{
public string Name;
public string Price;
}
Edit: these are simplified here, the actual structure for each field is like this:
private string _date;
//Get_Set
public string Date
{
get
{
return _date;
}
set
{
if (value != _date)
{
_date = value;
}
}
}
So the structure would be like this: a PivotElement containt a Menu-object. Inside it, I show Categories, a number of DishLists. Inside that Dishlist, I show the Category and the different Dishes, each with it's Name and Price. A mockup to make things a bit more clear (pdf-file on SkyDrive); http://sdrv.ms/12IKlWd
I have the following viewmodel
public class MenuViewModel : INotifyPropertyChanged
{
private ObservableCollection<Menu> _Menus;
}
The view that should implement (the structure) of the desired layout is as following:
<phone:Pivot
ItemsSource="{Binding Menus}">
<phone:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Date}" />
</DataTemplate>
</phone:Pivot.HeaderTemplate>
<phone:Pivot.ItemTemplate>
<DataTemplate>
<ItemsControl
ItemsSource="{Binding Categories}">
<TextBlock
Text="{Binding Category}"/>
<ItemsControl
x:Name="Dishes"
ItemsSource="{Binding Dishes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="1"
Text="{Binding Name}"/>
<TextBlock
Grid.Column="2"
Text="{Binding Price}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ItemsControl>
</DataTemplate>
</phone:Pivot.ItemTemplate>
</phone:Pivot>
As you can see, there's nested databinding to get to the data it needs.
The only code I then do on the page is
DataContext = App.ViewModel;
The Problem
When I launch it up, I get the correct Data displayed in the PivotItems header, and the correct number of PivotItems. Ufortunately, that's it; the content of the PivotItem stays empty. The first level of databinding thus works, but no further.
The way I was thinking
1st level: each PivotItem is linked to an object of type Menu, with header Menu.Date
2nd level: Inside that PivotItem I set the ItemsSource of a ItemsControl to that Menu.Categories
3rd level : The ItemsSource now is Menu.Categories.Dishes
I have already searched quite a bit and fiddled around with Datacontext and ItemsSource and different kinds of "{Binding ...}", but I can't get it to work. I would like to do the binding in xaml, and not in the code-behind.
Notes
The only function is to display data. The data is stationary and loaded once from a file.
That's why I chose ItemsControl instead of ListBox, there is no need to select anything.
This is a MVVM-approach to my previous question: Databinding + Dynamic Pivot
First off, you're trying to access a private field in the title so that won't work. These also have to be properties, not fields.
<TextBlock Text="{Binding Date}" />
private string Date;
Next you're binding to Category, which is also private and not a property.
<TextBlock Text="{Binding Category}"/>
private string Category;
And you're binding to the Categories field which needs to be a property.
<ItemsControl ItemsSource="{Binding Categories}">
public List<DishList> Categories;
Even if you correct these issues, you still won't get what you want because your outer ItemsControl doesn't have an ItemTemplate.
<ItemsControl ItemsSource="{Binding Categories}">
<TextBlock Text="{Binding Category}"/>
...
</ItemsControl>
Added: Your new error is because the DataTemplate can only have one child element so you need to use a container like a StackPanel.
<ItemsControl ItemsSource="{Binding Categories}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Category}"/>
<ItemsControl x:Name="Dishes" ItemsSource="{Binding Dishes}">
...
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have a XAML page that contains an ItemControl tag (the application uses the MVVM light framework):
<ItemsControl MinWidth="100" MinHeight="25" ItemsSource="{Binding Path=Options}" HorizontalAlignment="Left" d:LayoutOverrides="Height" Margin="10,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
This control has an item source that is a list of Option objects. The data template for this item control is as follows:
<DataTemplate DataType="{x:Type Sales:Option}">
<local:SalesOptionButton d:LayoutOverrides="Width, Height" DataContext="{Binding}"/>
</DataTemplate>
I have a view model that is associated with the SalesOptionButton control which is as follows:
public class SalesOptionButton
{
private string _name;
private Option _Option;
public ICommand SelectedOptionButtonCommand { get; set; }
public string Name
{
get { return _name; }
set { SetStructPropertyValue(ref _name, value); }
}
public Option Option
{
get { return _scriptOption; }
set { SetPropertyValue(ref _scriptOption, value); }
}
public SalesScriptOptionButton(ScriptOption option)
{
Option = option;
Name = option.OptionText;
}
protected override void RegisterForMessages()
{
SelectedOptionButtonCommand = new RelayCommand(OptionButtonSelected);
}
private void OptionButtonSelected()
{
MessengerService.Send(ScriptOptionSelectedMessage.Create(ScriptOption));
}
protected override void SetDesignTimeInfo(){}
}
Here is the XAML for the Option Control:
<UserControl [INCLUDES]>
<Button Height="25" Padding="1" MinWidth="100" Content="{Binding Name}" Command="{Binding SelectedOptionButtonCommand}"/>
</UserControl>
What this does is, for every option that is in the datasource, a button is created. These buttons should display the name of the option and, when the button is clicked, send a message to the main application that will process that click (set the chosen option).
The issue that I am seeing is that the buttons are being created but nothing else is being bound (There is no option name being displayed on the button and the button click is not working). Can anyone give me an idea as to why this isnt working like I think that it should be?
You aren't setting the data template as a property of your items control.
<ItemsControl ItemTemplate={StaticResource OptionTemplate} .../>
<DataTemplate x:Key="OptionTemplate" DataType="{x:Type Sales:Option}">
<local:SalesOptionButton d:LayoutOverrides="Width, Height" DataContext="{Binding}"/>
</DataTemplate>
It's difficult to decipher your post when bits and pieces of the code appear to be missing. You say:
This control has an item source that is a list of Option objects. The data template for this item control is as follows:
<DataTemplate DataType="{x:Type Sales:Option}">
<local:SalesOptionButton d:LayoutOverrides="Width, Height" DataContext="{Binding}"/>
</DataTemplate>
You haven't shown us your Option class - only your SalesOptionButton class. Presumably, your Option type has some property that yields the associated SalesOptionButton instance? If that's the case, then your data template is wrong here:
<local:SalesOptionButton d:LayoutOverrides="Width, Height" DataContext="{Binding}"/>
You're setting the DataContext of the SalesOptionButton to the Option instance, not to the SalesOptionButton instance. I'm guessing (and I have to) that you want something like this:
<local:SalesOptionButton d:LayoutOverrides="Width, Height" DataContext="{Binding SalesOptionButton}"/>