Wilfried Woivré & .Net

Silverlight 5 : Arrivée de l’Ancestor RelativeSource Binding

AVRI13

Et oui, le voilà, comme quoi, on n’arrête pas le progrès dans Silverlight !

Une petite démonstration sur l’ancestor RelativeSource Binding pour commencer cette soirée ….

Je vous ai montré à plusieurs reprises comment lier vos commandes ou vos actions de vos datatemplate compris dans des ItemsControl à des méthodes de vos ViewModel !

 

Vous avez donc accès à une solution qui pouvait être amélioré mais qui était tout de même bien pratique, vous pouvez la retrouver ici ou en vidéo.

 

On va déjà commencer par créer une application en Silverlight 5 !

image

Ensuite on va créer notre ViewModel de façon on ne peut plus classique

public class MainViewModel : ViewModelBase
{
    private readonly ObservableCollection<Person> _people = new ObservableCollection<Person>();
    public ObservableCollection<Person> People
    {
        get { return _people; }
    }

    private ICommand _callCommand;
    public ICommand CallCommand
    {
        get { return _callCommand ?? (_callCommand = new RelayCommand<Person>(Call)); }
    }

    public MainViewModel()
    {
        People.Add(new Person() { FirstName = "Wilfried", LastName = "Woivré" });
        People.Add(new Person() { FirstName = "Harry", LastName = "Cover" });
    }

    public void Call(Person person)
    {
        MessageBox.Show(string.Format("Call Person : {0} {1}", person.FirstName, person.LastName));
    }

    public void Call()
    {
        MessageBox.Show("Call Method");
    }
}

On lie correctement notre Vue à notre ViewModel, comme cela :

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.Loaded += (sender, e) => this.DataContext = new MainViewModel();
    }
}

Et maintenant passons à la vue, puisque le model c’est juste deux propriétés ….

    <Grid x:Name="LayoutRoot" Background="White">

        <ListBox ItemsSource="{Binding People}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Button Content="CallMethodAction" Margin="5"  >
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="Click">
                                    <ei:CallMethodAction MethodName="Call" TargetObject="{Binding DataContext, RelativeSource={RelativeSource AncestorType=UserControl}}" />
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </Button>
                        <Button Content="Command" Margin="5" >
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="Click">
                                    <i:InvokeCommandAction 
                                        Command="{Binding DataContext.CallCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" 
                                        CommandParameter="{Binding}"/>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </Button>
                        <TextBlock Text="{Binding FirstName}" Margin="5" />
                        <TextBlock Text="{Binding LastName}" Margin="5" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>

 

Et donc quelle est la principale différence par rapport à avant, c’est l’apparition de RelativeSource={RelativeSource AncestorType=UserControl} , cela permet d’appeler notre command ou notre méthode selon le DataContext lié à notre UserControl, nous n’avons donc plus besoin de passer via une méthode dans une classe partielle du model !

 

Vous pouvez retrouver les sources de la solution ici

Pour plus d’infos sur les autres nouveautés de Silverlight 5, je vous conseille le blog de Tim Heuer

Remonter

Afficher une énumération avec des attributs personnalisés dans Silverlight

MARS8

Dans différents projets, il est souvent utile d’utiliser des énumérations pour gérer des choix qui sont fixés à l’avance et qui sont voués à ne jamais changer. Par exemple, le choix Masculin/Féminin pour un formulaire d’inscription.

 

Pour commencer, on va utilisé une énumération simple, on lui rajoutera un attribut personnalisé par la suite, on va donc créer dans notre application une énumération de ce type :

public enum EnumValue
{
    EnumValue1 = 0, 
    EnumValue2 = 1,
    EnumValue3 = 2,
    EnumValue4 = 3
}

Maintenant si voyons comment afficher cette liste dans une ListBox Silverlight, commençons par le code behind de notre page.

private readonly ObservableCollection<EnumValue> _values = new ObservableCollection<EnumValue>();
public ObservableCollection<EnumValue> Values
{
    get { return _values; }
}

private EnumValue _selectedValue;
public EnumValue SelectedValue
{
    get { return _selectedValue; }
    set
    {
        _selectedValue = value;
        OnPropertyChanged("SelectedValue");
    }
}

public MainPage()
{
    InitializeComponent();
    this.Loaded += MainPage_Loaded;   
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    Values.Add(EnumValue.EnumValue1);
    Values.Add(EnumValue.EnumValue2);
    Values.Add(EnumValue.EnumValue3);
    Values.Add(EnumValue.EnumValue4);
    this.DataContext = this;
}

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyName)
{
    if (String.IsNullOrWhiteSpace(propertyName))
        return;

    if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

On remarque qu’on est obligé d’ajouter toutes nos valeurs à la main vu que la méthode GetValues() n’existe pas pour les enums dans Silverlight.

On a donc une interface de ce type, à gauche notre ListBox, et à notre droite, notre item sélectionné.

image

Bon on peut voir avec cette première version que ça marche, cependant ceci demande de rajouter tous les champs à la main, et de déclarer à la fois une liste pour les différentes valeurs, et un champ pour l’item sélectionné.

Commençons donc par nous affranchir de l’ajout des champs à la main, pour cela on va utiliser la réflexion, cela nous donne donc ceci :

var list = from item in typeof (EnumValue).GetFields()
           where item.IsLiteral
           select (EnumValue)Enum.Parse(typeof(EnumValue), item.Name, true);

foreach (var enumValue in list)
{
    Values.Add(enumValue);
}

Bon c’est déjà pas mal, on a évité l’ajout de code, lors de l’ajout d’une valeur. Cependant, cette opération va être effectuer à chaque appel de notre méthode Load, et la liste n’a aucune pertinence à se trouver ici, puisque finalement ce n’est qu’une source de données comme une autre, elle est juste liée à notre type d’énumération.

 

On va donc extraire nos données dans un cache de ce type.

public class EnumCache
{
    private static readonly IDictionary<Type, Object[]> Cache = new Dictionary<Type, Object[]>();

    public static Object[] GetValues(Type type)
    {
        if (!type.IsEnum)
            throw new ArgumentException("Type '" + type.Name + "' is not an enum");

        Object[] values;
        if (!Cache.TryGetValue(type, out values))
        {
            values = (from item in type.GetFields()
                      where item.IsLiteral
                      select Enum.Parse(type, item.Name, true)).ToArray();
            Cache[type] = values;
        }
        return values;
    }
}

On modifie ainsi notre remplissage de liste :

var list = EnumCache.GetValues(typeof (EnumValue));

foreach (var enumValue in list)
{
    Values.Add((EnumValue)Enum.Parse(typeof(EnumValue), enumValue.ToString(), true));
}

 

L’avantage de ce cache, est que l’on peut stocker n’importe quel type d’énumération, et le traitement est fait une seule fois à l’appel de notre méthode GetValues(), puis est stocké en cache pour de futures utilisations.

 

Maintenant qu’on a fait cela, le but serait de s’affranchir de notre déclaration de liste, pour cela on peut utiliser un Converter sur notre Listbox, vu qu’on a uniquement besoin du type de notre enum.

Notre converter est donc le suivant

 

public class MyEnumListConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value is EnumValue)
            return EnumCache.GetValues(typeof(EnumValue));

        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Et la déclaration de notre ListBox est donc la suivante !

        <ListBox 
            ItemsSource="{Binding SelectedValue, Converter={StaticResource myEnumValueConverter}}" 
            SelectedItem="{Binding SelectedValue, Mode=TwoWay}" 
            Margin="0,0,221,0" />

 

Ainsi on peut totalement s’affranchir de notre déclaration de notre ObservableCollection, et uniquement se basé sur notre champ sélectionné.

 

Bon maintenant, c’est bien, mais bon le soucis c’est que les valeurs affichées sont uniquement les différentes résultats des méthodes ToString() de chaque élément de l’énumération.

Ce qu’on peut envisager de faire, c’est d’ajouter pour chaque valeur de l’énumération une image et un texte associé. Pour cela on va donc créer un attribut de type custom.

Commençons donc par définir notre attribut personnalisé, on aurait pu utiliser un Attribut pré-existant dans le framework :

public class EnumValueAttribute : Attribute
{
    public string Text { get; set; }
    public Uri ImageUrl { get; set; }

    public EnumValueAttribute(string text)
    {
        this.Text = text;
    }

    public EnumValueAttribute(string text, string imageUrl)
    {
        this.Text = text;
        this.ImageUrl = new Uri(imageUrl, UriKind.Relative);
    }
}

 

Maintenant que nous l’avons créé, il faut l’utiliser dans notre énumération :

public enum EnumValue
{
    [EnumValue("Item 1")]
    EnumValue1 = 0, 
    [EnumValue("Item 2")]
    EnumValue2 = 1,
    [EnumValue("Item 3")]
    EnumValue3 = 2,
    [EnumValue("Item 4", "/VS.jpg")]
    EnumValue4 = 3
}

Il faut noter que pour cette syntaxe, il faut que l’image soit en mode “Content” et “Copy if never” !

Bon maintenant il va nous falloir notre méthode de Cache, vu qu’on veut récupérer l’attribut et non la valeur de notre champ.

public class EnumCache
{
    private static readonly IDictionary<Type, Object[]> Cache = new Dictionary<Type, Object[]>();

    public static Object[] GetValues(Type type)
    {
        if (!type.IsEnum)
            throw new ArgumentException("Type '" + type.Name + "' is not an enum");

        Object[] values;
        if (!Cache.TryGetValue(type, out values))
        {
            values = (from item in type.GetFields()
                      where item.IsLiteral
                      select ((EnumValueAttribute)item.GetCustomAttributes(typeof(EnumValueAttribute), false).First())).ToArray();
            Cache[type] = values;
        }
        return values;
    }
}

 

On va ensuite créer un converter pour notre item sélectionné, ainsi que pour la définition via le code behind. On aura donc en suivant le même principe le code suivant

public class MyEnumValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null)
            return value;
        if (value is EnumValue)
        {
            var enumValueAttribute = from e in typeof(EnumValue).GetFields()
                                     where e.IsLiteral && e.Name == ((EnumValue)value).ToString()
                                     select ((EnumValueAttribute)e.GetCustomAttributes(typeof(EnumValueAttribute), false).First());

            return enumValueAttribute.FirstOrDefault();
        }

        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null)
            return value;

        var valueAttribute = value as EnumValueAttribute;

        var elt = (from f in typeof (EnumValue).GetFields()
                   let attribute =
                       ((EnumValueAttribute) f.GetCustomAttributes(typeof (EnumValueAttribute), false).FirstOrDefault())
                   where f.IsLiteral
                         && attribute.Text == valueAttribute.Text
                         && attribute.ImageUrl == valueAttribute.ImageUrl
                   select (EnumValue) Enum.Parse(typeof (EnumValue), f.Name, false)).FirstOrDefault();

        return elt;
    }
}

On peut noter que j’aurais pu redéfinir le GetHashCode, et le Equals de mon attribut afin d’éviter de faire la vérification dans la requête Linq !

Et il nous suffit d’ajuster notre code XAML en adéquation avec nos modifications.

        <ListBox 
            ItemsSource="{Binding SelectedValue, Converter={StaticResource myEnumListConverter}}" 
            SelectedItem="{Binding SelectedValue, Mode=TwoWay, Converter={StaticResource myEnumValueConverter}}" 
            Margin="0,0,221,0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" 
                                Height="40">
                        <Image Source="{Binding ImageUrl}" 
                               Margin="5" 
                               Height="30"/>
                        <TextBlock Text="{Binding Text}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        
        <TextBlock Height="23" 
                   HorizontalAlignment="Left" 
                   Margin="200,96,0,0"
                   Text="Selected Item" 
                   VerticalAlignment="Top" />
        <ContentControl Height="40" 
                        HorizontalAlignment="Left" 
                        Margin="200,125,0,0" 
                        DataContext="{Binding SelectedValue, Converter={StaticResource myEnumValueConverter}}" 
                        VerticalAlignment="Top" 
                        Width="152">
            <StackPanel Orientation="Horizontal" 
                        Height="40">
                <Image Source="{Binding ImageUrl}" 
                       Margin="5" 
                       Height="30"/>
                <TextBlock Text="{Binding Text}" />
            </StackPanel>
        </ContentControl>

Donc rien de bien compliqué dans notre vue, uniquement un template pour notre listbox.

Et pour finir, ça nous donne donc un rendu de ce type :

image

Voilà en tout cas, ça peut être pratique pour plein de développement avec des enums, et des données un peu customisables, comme de la localisation (DisplayAttribute) ou des images.

Bon, bien entendu, comme je pense à vous, je vous fournis le code source final ici.

Remonter

WPF : Utiliser plusieurs templates dans un ItemControls

JANV14

Dans certains cas d’utilisation, il peut vous arriver de vouloir utiliser différents templates dans un ItemControls tel qu’une listbox. Prenons par exemple, un cas concret, soit twitter, si vous voulez réaliser un client WPF pour ce célèbre réseau sociaux, vous pouvez avoir envie de définir différents templates pour les messages que vous avez envoyé, ou ceux que vous recevez (comme on peut le voir dans de nombreux clients).

Le soucis c’est que pour réaliser cela, on voudrait afficher une listbox avec les dernières activités, et donc mixer les templates dans cet ItemControl. Ce qu’on peut faire c’est donc d’utiliser des DataTemplate que l’on va typer selon l’objet. Comment cela marche.

Prenons un cas plus simple d’un model objet de ce type :

image

On a donc une classe Figure, dont tous nos éléments que l’on souhaite afficher vont dériver de cette classe.

Après il nous suffit de déclarer en XAML nos différents DataTemplate en ressources de cette façon :

    <Window.Resources>
        <DataTemplate DataType="{x:Type Model:Rectangle}">
            <Rectangle Height="20" Width="60" Fill="Blue" Margin="2" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type Model:Ellipse}">
            <Ellipse Height="20" Width="40" Fill="Red" Margin="2" />
        </DataTemplate>
    </Window.Resources>

On va donc typer nos différents DataTemplate grâce à la propriété DataType. Il nous suffit donc de créer notre jeu de données de façon on ne peut plus classique.

private void InitData()
{
    var figures = new List<Model.Figure>();
    figures.Add(new Model.Rectangle());
    figures.Add(new Model.Rectangle());
    figures.Add(new Model.Ellipse());
    figures.Add(new Model.Ellipse());
    figures.Add(new Model.Rectangle());
    figures.Add(new Model.Ellipse());

    this.DataContext = figures;
}

Et ensuite de les lier à notre ItemControls de la même façon :

<ListBox ItemsSource="{Binding}" />

Et voilà, ces quelques lignes vous donnent le rendu souhaité

image

 

Tout le code est dans l’article, donc pas besoin que je fournisse le projet de démo!

Merci à Pascal Louveau de m’avoir posé la question.

Remonter

Customiser une ListBox en WPF

NOVE29

Il est très courant en WPF que l’on doivent redéfinir le rendu des composants de base afin d’avoir une interface plus ergonomique pour l’utilisateur.

Prenons par exemple le cas d’une ListBox de personne, qui ressemblerait à peu près à ça :

image

Pour ce code XAML :

<ListBox ItemsSource="{Binding}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock>
                <TextBlock.Text>
                    <MultiBinding StringFormat="{}{0} {1}">
                        <Binding Path="FirstName" />
                        <Binding Path="LastName" />
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Maintenant, on va supposer que le client veut que tous les Items soit placé, non pas l’un en dessous de l’autre, mais disperser au niveau de la fenêtre selon différents critères. Il voudrait donc que l’interface ressemble à quelque chose de ce genre :

image

Donc là on voit clairement que selon les besoins des spécifications, les items de la liste ne sont ni vraiment ordonnées, mais plus vraisemblablement placé au hasard dans l’application. Cependant on a tout de même bien affaire à une liste d’élément.

Nous avons donc plusieurs choix qui s’offre à nous, le premier est de créer un Canvas, ou l’on ajoute les éléments 1 à 1 en code behind. Ce choix peut être pas trop mal, mais va de ce fait nécessité un peu de code, et la maintenance sur cet ajout de composant peut rapidement devenir compliqué, surtout si vous voulez “binder” les différents champs.

Notre deuxième choix serait donc de customiser la ListBox, ce qui permettrait uniquement grâce au binding de placer nos Items, sans nous embêter, et de plus nous pourrons rendre ça plus facilement dynamique. Donc, pour cela, on va commencer par faire des tests, on va changer la propriété ItemsPanelTemplate, pour y mettre un Canvas.

        <ListBox ItemsSource="{Binding}">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <Canvas />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>

Puis après, nous allons ajouter deux propriétés à notre objet Personne afin d’y spécifier les coordonnées X et Y de l’item dans la ListBox, notons qu’on pourrait ajouter ces propriétés d’une autre manière, mais là ce n’est qu’une démonstration….

Ceci fait, nous allons maintenant lier les positions aux propriétés Canvas.Top et Canvas.Left de notre TextBlock. Alors déjà premier petit problème, l’intellisense ne nous fournit pas ces propriétés dans notre DataTemplate, on peut donc se dire que cela provient d’un bug de l’Intellisense, après tout Visual Studio 2008 et le XAML n’apporte pas beaucoup d’aide là ou il le faudrait. Donc on va tout de même le taper et voir si ça compile.

                <DataTemplate>
                    <TextBlock Canvas.Top="{Binding Pos.Y}" Canvas.Left="{Binding Pos.X}">
                        <TextBlock.Text>

 

Et là c’est magique, Visual Studio compile correctement le projet, ne signale aucune erreur, n’annonce pas de problème de binding dans la sortie. Mais par contre l’application quand à elle ne marche pas comme on le souhaite, car on obtient ceci :

image

On voit ici très bien qu’il ne prend pas en compte nos coordonnées, on se dit maintenant que Visual Studio avait peut être raison de ne pas nous proposer les propriétés Canvas.Left et Canvas.Top sur notre TextBlock.

Après un peu de réflexion, on se dit qu’il faudrait essayer de définir un style à notre Item, et là encore, la ListBox, est bien équipé pour ça, avec sa propriété ItemContainerStyle.

            <ListBox.ItemContainerStyle>
                <Style>
                    <Setter Property="Canvas.Left" Value="{Binding Pos.X}" />
                    <Setter Property="Canvas.Top" Value="{Binding Pos.Y}" />
                </Style>
            </ListBox.ItemContainerStyle>

 

On enlève maintenant nos propriétés de la TextBlock , vu que visiblement elles ne fonctionnent pas, et puis on relance notre application. On obtient donc ainsi notre joli fenêtre :

image

Alors, effectivement comme ça on peut ne pas trop voir l’utilité de repositionner chacun des Items, en fait c’est parce que pour mon projet Imagine Cup, je suis en train de réaliser une ListBox ou les items sont disposés sous forme de cercle. Mais je ne vous dévoile pas tout !

D’ailleurs j’en profite pour passer une annonce, si vous connaissez un étudiant sur Paris qui est intéressé par les technologies comme le XAML, ainsi que l’ergonomie et le design d’application, notre équipe recherche quelqu’un, vous pouvez donc m’envoyer un mail à wilfried.woivre[at]gmail.com.

Remonter

WPF : Utiliser des commandes dans un DataTemplate

NOVE21

Après une très longue période sans réutiliser de technologies tel que WPF ou Silverlight, on s’aperçoit que l’on perd très vite la main. C’est donc durant un projet éclair que je me suis décidé de refaire un petit projet en utilisant le MVVM.

C’est donc confiant que je commence mon code, que je me décide à le lancer au bout d’une heure, histoire de voir ce que ça donne en dehors du Designer WPF. Mon interface ressemble donc à ce que je veux, cependant je m’aperçois que certaines de mes commandes ne marchaient pas, je passe donc 10 bonnes minutes pour trouver ce qu’il se passe et enfin corriger.

On va donc voir dans cet article un moyen de contourner la déclaration des commandes dans un DataTemplate, sans pour autant perdre en fonctionnalité.

 

Pour cette démonstration je vais utiliser comme base le template de MVVM qui vient de codeplex (WPF Model-View-ViewModel Toolkit 0.1) que je vais modifier de tel sorte que ViewModelBase dérive de DependencyObject, et implémente deux méthodes abstraites qui sont OnViewReady(), et OnViewDispose(). On obtient donc ceci :

/// <summary>
/// Provides common functionality for ViewModel classes
/// </summary>
public abstract class ViewModelBase : DependencyObject, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public abstract void OnViewReady();
    public abstract void OnViewDispose();

}

Note les modifications apportées ici ne sont pas nécessaire pour la démonstration, mais grâce à cela vous pouvez utiliser les DependencyProperty dans votre ViewModel, et propager les évènements Loaded et Unloaded de vos vues, vers vos ViewModel. Voilà pour l’astuce du jour !

Voici donc  la commande que nous allons utiliser :

#region GestionCommandes
private DelegateCommand<Models.Person> editPersonCommand;

public ICommand EditPersonCommand
{
    get
    {
        if (editPersonCommand == null)
            editPersonCommand = new DelegateCommand<Models.Person>(EditPerson);
        return editPersonCommand;
    }
}

private void EditPerson(Models.Person person)
{
    MessageBox.Show("Ici on éditera la personne", "", MessageBoxButton.OK);
}
#endregion 

Bon maintenant, affichons nos données dans la vue au pas à pas. Après un binding initial on obtient quelque chose du style

<Window x:Class="WpfModelViewApplication3.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:c="clr-namespace:WpfModelViewApplication3.Commands"
    Title="Main Window" Height="400" Width="800">

    <Grid>
        <ListBox ItemsSource="{Binding People}" />
    </Grid>
</Window>

Avec pour interface générée :

image

Bon ceci, n’étant pas très lisible pour un client, on va donc créer un DataTemplate. Or pour des raisons de lecture, nous allons ajouter ce DataTemplate en ressources de la fenêtre.

Donc confiant, j’écris ceci :

<Window.Resources>
    <DataTemplate DataType="{x:Type localModel:Person}">
        <StackPanel Orientation="Horizontal">
            <Label Content="{Binding FirstName}" />
            <Button Content="Modifier" Margin="5 0" Command="{Binding EditPersonCommand}" CommandParameter="{Binding}" />
        </StackPanel>
    </DataTemplate>
</Window.Resources>

<Grid>
    <ListBox ItemsSource="{Binding People}" />
</Grid>

J’ai donc mon interface qui change en conséquence :

image

Le problème se situe en fait lorsque je clique sur le bouton Modifier, en effet rien ne se produit. J’ai donc regardé si lors de la construction de la fenêtre il allait bien me chercher ma commande, mais il ignora mon point d’arrêt dans Visual Studio.

Alors l’astuce facile à réaliser avec ce template est d’utiliser leur CommandReference, de tel sorte :

<Window.Resources>
    <c:CommandReference x:Key="EditPerson" Command="{Binding EditPersonCommand}" />


    <DataTemplate DataType="{x:Type localModel:Person}">
        <StackPanel Orientation="Horizontal">
            <Label Content="{Binding FirstName}" />
            <Button Content="Modifier" Margin="5 0" Command="{StaticResource EditPerson}" CommandParameter="{Binding}" />
        </StackPanel>
    </DataTemplate>
</Window.Resources>

<Grid>
    <ListBox ItemsSource="{Binding People}" />
</Grid>

Là, notre code fonctionne comme on le souhaitait, et l’on récupère bien entendu le bon membre lors du déclenchement de la commande. En fait, ce qui a changé, nous avons déclaré la command, dans l’objet CommandReference. Notre bouton peut donc appeler la commande via le nom qu’on lui a donné. En effet, il passe par une ressource statique qui existe, et non une donnée dynamique comme il était question dans le DataTemplate.

Vous pouvez retrouvez la CommandReference dans le MVVM Toolkit, ce qui nous prouve encore une fois qu’avec des Toolkit bien pensé on optimise fortement notre temps de développement.

En espérant que cet article vous aide dans de futurs développement !

Remonter