ItemsControl
Composant qui représente une collection d'éléments. Ces éléments peuvent être hétérogène.
ListBox, ComboBox et TreeView héritent d'ItemsControl.
|
<ItemsControl Margin="10" ItemsSource="{Binding Path=MyTodoList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Ellipse Fill="Silver"/>
<TextBlock Text="{Binding Path=Priority}"
HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Border BorderBrush="Aqua" BorderThickness="1" CornerRadius="15">
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</Border>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Width" Value="100"/>
<Setter Property="Control.Margin" Value="5"/>
<Setter Property="Control.HorizontalContentAlignment"
Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Grid Background="{TemplateBinding Background}">
<ToggleButton IsChecked="{TemplateBinding IsSelected}"
Content="{TemplateBinding Content}"
Focusable="False"
IsHitTestVisible="False" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
|
Accès au DataContext de l'ItemsControl depuis un DataTemplate
|
<ItemsControl ItemsSource="{Binding Path=Property00}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ItemsControl}"
Path="DataContext.Property01" />
<Binding Path="Property1" />
</MultiBinding>
</TextBlock.Text>
|
Grid et ItemsControl
|
<ItemsControl ItemsSource="{Binding Path=MyTodoList}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="GridGroup1" />
<ColumnDefinition SharedSizeGroup="GridGroup2"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0">A</Label>
<Label Grid.Column="1">B</Label>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<ScrollViewer>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="GridGroup1" />
<ColumnDefinition SharedSizeGroup="GridGroup2" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="Column1"
Style="{StaticResource ColumnHeaderStyle}" />
<TextBlock Grid.Column="1"
Text="Column1"
Style="{StaticResource ColumnHeaderStyle}" />
</Grid>
<ItemsPresenter />
</StackPanel>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
<Style TargetType="TextBlock"
x:Key="ColumnHeaderStyle"
BasedOn="{StaticResource TextBlock}">
<Setter Property="Padding" Value="4,1,4,1" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
|
Template de visualisation et d'édition
|
<ListBox ItemsSource="...">
<ListBox.Resources>
<views:ControlFactoryConverter x:Key="ControlFactoryConverter"/>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl x:Name="Valeur" Content="{Binding}">
<ContentControl.ContentTemplate>
<DataTemplate>
<Label Content="{Binding Path=Value}" />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Valeur" Property="ContentTemplate"
Value="{Binding Path=., Converter={StaticResource ResourceKey=ControlFactoryConverter}}">
</Setter>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
|
List et Binding
Lorsque l'on bind un ItemControl sur une propriété de type List, l'affichage n'est pas à jour avec l'élément bindé malgré l'appel de la méthode OnPropertyChanged.
Solutions :
- Forcer le rafraichissement des éléments visuel : itemsControl.Items.Refresh()
- Recréer une nouvelle List à chaque get sur la propriété bindée (new List ou yield return)
- Utiliser une ObservableCollection
Filtre
Dans le code-behind
|
<TextBox TextChanged="TextBox_TextChanged"/>
<ItemsControl Name="list" ItemsSource="{Binding Path=Items}"/>
|
|
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var collectionView = CollectionViewSource.GetDefaultView(list.ItemsSource);
collectionView.Filter = item =>
{
var contact = item as Contact;
var tb = sender as TextBox;
return contact.Lastname.ToLower().StartsWith(tb.Text.ToLower());
}
}
|
Dans le XAML
|
<MainControl xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase">
<ComboBox>
<ComboBox.Resources>
<CollectionViewSource x:Key="viewSource"
Source="{Binding Path=Items}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Nom" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<Binding Source="{StaticResource ResourceKey=viewSource}"/>
</ComboBox.ItemsSource>
</ComboBox>
|
ScrollBar
|
<ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="..." />
</ScrollViewer>
|
|
<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" />
|
RenderTransform et ZIndex
Pour éviter le chevauchement avec les autres éléments, il faut définir le ZIndex de l'élément zoomé.
Le changement de ZIndex ne peut se faire que dans un fils direct de Panel.
Les éléments de l'ItemsControl sont encapsulés dans des ContentPresenter, il faut donc modifier le ZIndex du ContentPresenter et non celui de l'élément (ici la TextBox).
|
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock RenderTransformOrigin="0.5 0.5"
Text="{Binding}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="AliceBlue" />
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="2"
ScaleY="2" />
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Panel.ZIndex" Value="99" />
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
|
ItemTemplateSelector
Permet de choisir des templates différent pour chaque item.
|
<UserControl.Resources>
<namespace:MyTemplateSelector x:Key="myTemplateSelector "/>
<UserControl.Resources>
<ItemsControl ItemTemplateSelector="{StaticResource ResourceKey=myTemplateSelector}">
<ItemsControl.Resources>
<DataTemplate x:Key="Item1DataTemplate">
</DataTemplate>
<DataTemplate x:Key="Item2DataTemplate">
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
|
|
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is Item1)
{
return element.FindResource("Item1DataTemplate") as DataTemplate;
}
else if (item is Item2)
{
return element.FindResource("Item2DataTemplate") as DataTemplate;
}
}
return null;
}
}
|
|
<ListBox HorizontalContentAlignment="Stretch" />
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
|
ListBox
Possède la propriété SelectedItem par rapport à ItemsControl.
Redéfinir les couleurs de séléction
|
<ListBox ItemsSource="...">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True" >
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="White" />
</Trigger>
</Style.Triggers>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="LightGreen" />
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10"/>
</Style>
</Style.Resources>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
|
Redéfinir le visuel de sélection
Dans cet exemple, la sélection est représenté par une bordure noire de taille 3.
|
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type Type=ListBoxItem}">
<Setter Property="ListBoxItem.FocusVisualStyle" Value="{x:Null}" />
<Setter Property="ListBoxItem.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Name="Border" BorderBrush="Black"
SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border"
Property="BorderThickness" Value="3"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
|
ListView vs ListBox
ListView est une spécialisation de ListBox
- avec une propriété View
- GridView permet un affichage en plusieurs colonnes
- default selection mode is Extended instead of Single for a ListBox
- Elle permet 4 vues differentes: iconview, small icon-view, list-view, details-view.
|
<UserControl.Resources>
<Style x:Key="GroupStyle"
TargetType="TextBlock">
<d:Style.DataContext>
<x:Type Type="local:ItemViewModel" />
</d:Style.DataContext>
</Style>
<DataTemplate x:Key="ItemCellTemplate"
DataType="local:ItemViewModel">
<TextBlock Text="{Binding GroupName}"
Style="{StaticResource GroupStyle}"
HorizontalAlignment="Right" />
</DataTemplate>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</UserControl.Resources>
<ListView ItemsSource="{Binding Data}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name"
Width="150"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Group"
Width="150"
CellTemplate="{StaticResource ItemCellTemplate}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
|
ScrollIntoView
Permet de forcer le défilement jusqu'à un élément de la liste. Utile quand on modifie par le code l'élément sélectionné.
|
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listBox = (ListBox)sender;
listBox.ScrollIntoView(listBox.SelectedItem);
}
|
ScrollToSelectedItemBehavior
ScrollToSelectedItemBehavior.cs
|
public sealed class ScrollToSelectedItemBehavior : Behavior<ListBox>
{
protected override void OnAttached()
{
this.AssociatedObject.SelectionChanged += OnSelectionChanged;
}
protected override void OnDetaching()
{
this.AssociatedObject.SelectionChanged -= OnSelectionChanged;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 1)
{
if (e.AddedItems[0] is Item addedItem && sender is ListBox listBox)
{
listBox.ScrollIntoView(addedItem);
}
}
}
}
|
|
<ListBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<b:Interaction.Behaviors>
<behaviors:ScrollToSelectedItemBehavior />
</b:Interaction.Behaviors>
</ListBox>
|
Auto scroll vers la fin lors de l’ajout d'un élément
CollectionChanged
|
var collection = myListBox.Items.SourceCollection as INotifyCollectionChanged;
collection.CollectionChanged += ListBox_CollectionChanged;
private void ListBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
var border = VisualTreeHelper.GetChild(lb, 0) as Decorator;
var scrollViewer = border.Child as ScrollViewer;
scrollViewer.ScrollToEnd();
|
ScrollChanged
|
<ListBox ScrollViewer.ScrollChanged="ListBox_ScrollChanged"/>
|
|
private void ListBox_ScrollChanged(object sender, RoutedEventArgs e)
{
if (((ScrollChangedEventArgs)e).ExtentHeightChange > 0.0)
((ScrollViewer)e.OriginalSource).ScrollToEnd();
|
ItemTemplate et ListBoxItem
|
<ListBox xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem>Menu #1</ListBoxItem>
<ListBoxItem>Menu #2</ListBoxItem>
<ListBox.Items>
<sys:String>Menu #1</sys:String>
<sys:String>Menu #2</sys:String>
</ListBox.Items>
</ListBox>
|
ComboBox et Binding
Liaison des valeurs d'un dictionnaire, accessible par la propriété Dictionary, avec la liste des éléments de la ComboBox.
Liaison de l'item sélectionné avec la propriété Valeur du DataContext courant.
|
<ComboBox ItemsSource="{Binding Path=Dictionary.Values}" SelectedItem="{Binding Path=Valeur}" />
|
Liaison d'une liste, accessible par la propriété List, avec la liste des éléments de la ComboBox.
La propriété DisplayMemberPath permet de spécifier quelle propriété sera utilisée pour afficher les item.
La propriété SelectedValuePath permet de spécifier quelle propriété sera utilisée pour comparer la l'item sélectionné avec la liste des items de la ComboBox.
|
<ComboBox ItemsSource="{Binding Path=List}"
DisplayMemberPath="Nom"
SelectedValuePath="Nom"
SelectedValue="{Binding Path=Valeur}" />
|
ComboBox avec une liste d'éléments codée en dur:
|
<ComboBox SelectedIndex="0">
<ComboBox.Items>
<ComboBoxItem Content="string"/>
<ComboBoxItem Content="double"/>
</ComboBox.Items>
</ComboBox>
<ComboBox SelectedIndex="0" ItemsSource="12345"/>
|
|
<Window xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:MyNamespace">
<Window.Resources>
<ObjectDataProvider x:Key="MyEnumODP" MethodName="GetValues"
ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:MyEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<ComboBox ItemsSource="{Binding Source={StaticResource MyEnumODP}}"
SelectedItem="{Binding Path=MySelectedItem, Mode=OneWayToSource}"
SelectedIndex="1" />
|
TreeView
Permet un affichage hiérarchique des données.
TreeView statique
|
<TreeView>
<TreeViewItem Header="Groupe 1">
<TreeViewItem Header="Element 11"/>
<TreeViewItem Header="Element 12"/>
</TreeViewItem>
<TreeViewItem Header="Groupe 2">
<TreeViewItem Header="Element 21"/>
<TreeViewItem Header="Element 22"/>
</TreeViewItem>
</TreeView>
|
Groupement depuis une List
Même exemple que précédemment, mais cette fois le TreeView est binder sur une List.
|
public enum Groupe { Groupe1, Groupe2 }
public class Element
{
public string Description { get; set; }
public Groupe Groupe { get; set; }
}
public ObservableCollection<Element> Elements { get; set; }
Elements = new ObservableCollection<Element>();
Elements.Add(new Element() { Description = "Element 11", Groupe = Groupe.Groupe1 });
Elements.Add(new Element() { Description = "Element 12", Groupe = Groupe.Groupe1 });
Elements.Add(new Element() { Description = "Element 21", Groupe = Groupe.Groupe2 });
Elements.Add(new Element() { Description = "Element 22", Groupe = Groupe.Groupe2 });
|
|
<...Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding Path=Elements}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Groupe"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</...Resources>
<TreeView ItemsSource="{Binding Source={StaticResource ResourceKey=cvs}, Path=Groups}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Items}">
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Description}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
|
Performance
Optimizing WPF Application Performance
Virtualisation
Seul les éléments à afficher sont créés.
|
<ItemsControl ItemsSource="{Binding Path=Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ListBox ItemsSource="{Binding Path=Items}" VirtualizingStackPanel.VirtualizationMode="Recycling" />
|
Modes
- Standard : mode par défaut, les conteneurs des éléments sont libérés dès qu'ils ne sont plus affichés.
- Recycling : les conteneurs des éléments qui ne sont plus affichés sont réutilisés par les éléments à afficher.
A eviter
- les StackPanel horizontaux
Affichage
Writing More Efficient ItemsControls
L'ItemsControl va appliquer l'ItemTemplate à chacun de ses éléments. Simplifier l'ItemTemplate permet donc d'améliorer les performances :
- Réduire le nombre de Control : utiliser un Control avec MultiBinding et StringFormat plutôt que plusieurs Control
- Freeze les freezable objects, ils seront considérés comme constants (appel de la méthode Freeze ou utilisation de l'option the PresentationOptions:Freeze)
- Réduire le nombre de Bindings : utiliser un Presenter (Vue-Modèle?), sur-couche de la classe métier mise en forme pour faciliter les Bindings.
- Créer son propre composant du type FrameworkElement pour l'ItemTemplate.
- Créer son propre composant du type FrameworkElement pour l'ItemsControl afin que lors de la modification d'un Item on ne redessine pas tous les Items mais seulement celui qui a changé.
Scrolling
|
<ListBox ScrollViewer.IsDeferredScrollingEnabled="True" />
|