Wilfried Woivré & .Net

Afficher des informations dans la fenêtre de sortie de Visual Studio

MAI19

Quand on fait du XAML que ce soit en WPF, Silverlight ou WP7, on est bien heureux de voir apparaitre ces petits messages d’erreur dans la fenêtre de sortie lorsque l’on est en mode debug de notre application !

Ici comme on peut le voir il s’agit uniquement d’une erreur de binding, qui nous informe que la propriété “FirstName” n’a pas été trouvée :

 

image

 

Et si on voyait comment ajouter nos propres messages dans cette fenêtre, et bien tout est là pour nous aider, c’est très facilement faisable grâce au namespace System.Diagnotics

 

this.Loaded += (sender, e) => System.Diagnostics.Debug.Print("Message perso => Un petit test ?");

On obtient en mode DEBUG, notre message personnel dans la fenêtre de sortie

image

Et comme la vie est bien faite, en mode release, rien n’apparait !

 

Bon bien entendu, rien ne sert de polluer cette fenêtre outre mesure, mais c’est toujours mieux qu’une trentaine de message box d’information pour lancer votre programme et être sûr que tout ce passe bien !

Remonter

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

Silverlight 4 & le CommandManager

AVRI4

 

En ce weekend de pâques un petit article, vous avez tous aperçu que depuis le début de Silverlight, il n’y a aucune trace du CommandManager qu’on peut avoir en WPF.

Et pourtant Silverlight, tout comme WPF, a en son sein l’interface ICommand permettant de gérer avec plus ou moins de souplesse nos différentes actions.

Donc jusque quelques lignes pour vous dire que sur ma page d’accueil (euh Codeplex), il y a un projet permettant d’ajouter un CommandManager à notre Silverlight.

Voici le lien où vous pouvez récupérer cette petite assembly : http://agcommandmanager.codeplex.com/

Remonter