I have an ItemsControl that contains a collection of items that are shown at a page.
The ItemsControl has an ItemTemplate property which is set to certain DataTemplate resource.
<DataTemplate x:Key="SimpleTemplate">
<!-- .... -->
</DataTemplate>
<DataTemplate x:Key="ComplexTemplate">
<!-- .... -->
</DataTemplate>
...............................
<ItemsControl
x:Name="MainCanvas"
DataContext="{StaticResource mainItems}"
ItemsSource="{Binding Path=Buttons}"
ItemTemplate="{StaticResource SimpleTemplate}"
>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="4000" Height="4000" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Is it possible to change DataTemplate for a one specific item in my ItemsControl programmatically?
Sound like you are looking for ItemTemplateSelector
You can create a TemplateSelector and decide which template to apply based on a given item:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate SimpleTemplate { get; set; }
public DataTemplate ComplexTemplate { get; set; }
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
// Logic to decide which template to apply goes here
return // Either SimpleTemplate or ComplexTemplate
}
}
In xaml, add your template selector as a resource
<local:MyTemplateSelector x:Key="itemTemplateSelector">
<local:MyTemplateSelector.SimpleTemplate>
<DataTemplate>
<!-- Implementation goes here -->
</DataTemplate>
</local:MyTemplateSelector.SimpleTemplate>
<local:MyTemplateSelector.ComplexTemplate>
<DataTemplate>
<!-- Implementation goes here -->
</DataTemplate>
</local:MyTemplateSelector.ComplexTemplate>
</local:MyTemplateSelector>
And use it in your ItemsControl
<ItemsControl
x:Name="MainCanvas"
DataContext="{StaticResource mainItems}"
ItemsSource="{Binding Path=Buttons}"
ItemTemplateSelector="{StaticResource itemTemplateSelector}">
Hope this helps
Related
I've got a ObservableCollection<IContainers> Containers which defines the property ObservableCollection<object> Content.
public interface IContainers
{
public double Height {get; set;}
public double Width {get; set;}
public ObservableCollection<object> Content {get; set;}
}
public class SetupStep
{
public ObservableCollection<IContainer> Containers {get; set;}
}
The types of items in the Content Property can vary. These types define their own properties to which I want to bind.
This is my .xaml code:
<c:ScatterView
ItemsSource="{Binding Containers}">
<c:ScatterView.ItemTemplate>
<DataTemplate>
<ListBox
ItemsSource="{Binding Content}">
<!--A way to determine my types in Contents?!?!-->
<ListBox.Resources>
<DataTemplate x:Key="{x:Type myObjects:Picture}">
<Image Source="{Binding Picture.FullFileName}"/>
</DataTemplate>
<DataTemplate x:Key="{x:Type myObjects:Parameter}">
<myControl:ParameterControl Id="{Binding Parameter.Id}"/>
</DataTemplate>
</ListBox.Resources>
<!--A way to determine my types in Contents?!?!-->
</ListBox>
</DataTemplate>
</c:ScatterView.ItemTemplate>
</c:ScatterView>
I found no working solution to bind to the properties of myObjects like Picture or Parameter.
I hope for some ideas :)
Thanks, Alex
Data Templates and Binding source properties are resolved by reflection. So if the Content collection contains a Picture, a DataTemplate for Picture can be applied automatically.
The only thing you need to do is to set the DataType property of the DataTemplate:
<ListBox.Resources>
<DataTemplate DataType="{x:Type myObjects:Picture}">
<Image Source="{Binding Picture.FullFileName}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type myObjects:Parameter}">
<myControl:ParameterControl Id="{Binding Parameter.Id}"/>
</DataTemplate>
</ListBox.Resources>
Everything else should work out of the box.
While you could also use a DataTemplateSelector, the above approach is far simpler. Using a DataTemplateSelector isn't necessary unless you want to have different DataTemplates for different items of the same type, e.g. depending on the value of some property of the item class.
You might want to take a look at the DataTemplateSelector class. This allows you to switch DataTemplate depending on different criteria - in your case, this criteria could be the list item type:
public class CustomDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is MainWindow.Picture)
return PictureTemplate;
if (item is MainWindow.Parameter)
return ParameterTemplate;
// return some default template as fall-back
}
public DataTemplate PictureTemplate { get; set; }
public DataTemplate ParameterTemplate { get; set; }
// ...add other template references here...
}
You can now define all templates as XAML resources, and simply reference the TemplateSelector within the ListBox:
<Window.Resources>
<DataTemplate x:Key="PictureTemplate">
<Image Source="{Binding FullFileName}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterTemplate">
<myControl:ParameterControl Id="{Binding Id}"/>
</DataTemplate>
...add other templates here...
<local:CustomDataTemplateSelector x:Key="CustomDataTemplateSelector"
PictureTemplate="{StaticResource PictureTemplate}"
ParameterTemplate="{StaticResource ParameterTemplate}"/>
</Window.Resources>
<ListBox
ItemsSource="{Binding Content}"
ItemTemplateSelector="{StaticResource CustomDataTemplateSelector}">
</ListBox>
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.
I am trying to implement the following scanarios
APPROACH SO FAR
Tried to implement it with an ItemsControl (with WrapPanel) and a TextBox wrapped inside a WrapPanel, but it does not have a desired output as there are two WrapPanels wrapping separately
<toolkit:WrapPanel Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding someThing}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Border>
<TextBlock Text="somesomething" />
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<TextBox/>
</toolkit:WrapPanel>
I am thinking if I can add the TextBox at the END of the ItemsControl, but failed to do so. Please specify if there is any other workaround/ solution to any of my approaches
You need to use DataTemplateSelector for the ItemsControl and specify different templates for different list items.
public class BlockItem
{
// TODO
}
public class BoxItem
{
// TODO
}
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate BlockTemplate { get; set; }
public DataTemplate BoxTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if (item is BlockItem) return BlockTemplate;
else if (item is BoxItem) return BoxTemplate;
return base.SelectTemplateCore(item);
}
}
XAML:
<ItemsControl ItemsSource="{Binding someObject}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
<local:MyTemplateSelector>
<local:MyTemplateSelector.BlockTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="something"/>
</Grid>
</DataTemplate>
</local:MyTemplateSelector.BlockTemplate>
<local:MyTemplateSelector.BoxTemplate>
<DataTemplate>
<Grid>
<TextBox Text="something"/>
</Grid>
</DataTemplate>
</local:MyTemplateSelector.BoxTemplate>
</local:MyTemplateSelector>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
And you then add different types of objects to your items source:
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BlockItem());
someObject.Add(new BoxItem());
If you want the TextBox to be the last element, then you need it to be the last item in your ItemsSource list.
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);
}
}
I have an ItemsControl that contains product categories and I have another ItemsControl that contains a list of all the articles catégoie currently selected, I need to related the current selection of the category with the binding of the ItemsControl articles
<ItemsControl ItemsSource="{Binding Path=Categories}">
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=CategorieCaption}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
...
</ItemsControl>
<ItemsControl ItemsSource="{Binding Path=SelectedCategories.Articles}">
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=ArticleCaption}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
...
</ItemsControl>
You just need to update your data bound Article collection each time the Category is changed. If you add a property to data bind with the ListBox.SelectedItem, then you can do that from the property setter:
<ListBox ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory}" ... />
..
public Category SelectedCategory
{
get { return selectedCategory; }
set
{
if (selectedCategory != value)
{
selectedCategory = value;
NotifyPropertyChanged("SelectedCategory");
// Selected Category was changed, so update Articles collection
Articles = UpdateArticlesByCategory(selectedCategory);
}
}
}