Category Archives: Astuces

Tester et déboguer une bibliothèque depuis LINQPad

Cela faisait bien longtemps que je voulais bloguer à propos de LINQPad. Au cas où vous ne connaissez pas, LINQPad est un outil qui permet d’écrire et de tester du code très rapidement sans avoir besoin de créer un projet complet dans Visual Studio. Il supporte C#, VB.NET, F# et SQL. Il était initialement conçu comme un outil éducatif pour expérimenter avec LINQ (son auteur, Joe Albahari, l’avait développé pour accompagner son livre C# in a Nutshell), mais il est aussi extrêmement utile comme outil générique pour tester du code .NET.

J’utilise fréquemment LINQPad pour tester rapidement une bibliothèque sur laquelle je travaille. C’est très facile, il suffit de référencer l’assembly à tester et de l’utiliser normalement. Mais quand la bibliothèque ne se comporte pas comme prévu, il est souvent utile de pouvoir la déboguer pas à pas… Il s’avère que c’est assez simple à faire à partir de LINQPad !

La version premium de LINQPad a un débogueur intégré, qui n’est pas aussi puissant que celui de Visual Studio, mais quand même utile pour déboguer les scripts LINQPad. Cependant, il ne permet pas de rentrer dans le code de la bibliothèque… Heureusement, il y a une astuce qui permet d’utiliser le débogueur de Visual Studio pour déboguer le script qui s’exécuter dans LINQPad.

Tout d’abord, ouvrez dans Visual Studio la bibliothèque à déboguer, si ce n’est pas déjà fait. Compilez le projet, et ajoutez une référence à l’assembly dans votre script LINQPad:

Add reference to the library

Ecrivez du code qui utilise votre bibliothèque:

Use your library from LINQPad

Et ajoutez cette ligne au début du script LINQPad:

Debugger.Launch();

Quand vous exécutez le script, un dialogue va s’affichez pour vous demander de choisir un débogueur:

Choose a debugger

Sélectionnez l’instance de Visual Studio dans laquelle votre solution est ouverte et cliquez OK. Cela va attacher le débogueur de Visual Studio au processus qui exécute le script LINQPad, et suspendre l’exécution au niveau de l’appel à Debugger.Launch():

Debugging in Visual Studio

Vous pouvez maintenant déboguer votre script LINQPad et votre bibliothèque. Vous pouvez mettre des points d’arrêt, rentrer dans les méthodes, ajouter des espions, etc, exactement comme quand vous déboguez une application normale !

Passage de paramètres par référence à une méthode asynchrone

L’asynchronisme dans C# est une fonctionnalité géniale, et je l’ai beaucoup utilisé depuis son apparition. Mais il y a quelques limitations agaçantes; par exemple, on ne peut pas passer des paramètres par référence (ref ou out) à une méthode asynchrone. Il y a de bonnes raisons pour cela; la plus évidente est que si vous passez par référence une variable locale, elle est stockée sur la pile, or la pile ne va pas rester disponible pendant toute l’exécution de la méthode asynchone (seulement jusqu’au premier await), donc l’emplacement de la variable n’existera plus.

Cependant, cette limitation est assez facile à contourner : il suffit de créer une classe Ref<T> pour encapsuler la valeur, et de passer une instance de cette classe par valeur à la méthode asynchrone:

async void btnFilesStats_Click(object sender, EventArgs e)
{
    var count = new Ref<int>();
    var size = new Ref<ulong>();
    await GetFileStats(tbPath.Text, count, size);
    txtFileStats.Text = string.Format("{0} files ({1} bytes)", count, size);
}

async Task GetFileStats(string path, Ref<int> totalCount, Ref<ulong> totalSize)
{
    var folder = await StorageFolder.GetFolderFromPathAsync(path);
    foreach (var f in await folder.GetFilesAsync())
    {
        totalCount.Value += 1;
        var props = await f.GetBasicPropertiesAsync();
        totalSize.Value += props.Size;
    }
    foreach (var f in await folder.GetFoldersAsync())
    {
        await GetFilesCountAndSize(f, totalCount, totalSize);
    }
}

La class Ref<T> ressemble à ceci:

public class Ref<T>
{
    public Ref() { }
    public Ref(T value) { Value = value; }
    public T Value { get; set; }
    public override string ToString()
    {
        T value = Value;
        return value == null ? "" : value.ToString();
    }
    public static implicit operator T(Ref<T> r) { return r.Value; }
    public static implicit operator Ref<T>(T value) { return new Ref<T>(value); }
}

Comme vous pouvez le voir, il n’y a rien de très compliqué. Cette approche peut également être utilisée pour les blocs itérateurs (yield return), qui n’autorisent pas non plus les paramètres ref ou out. Elle a aussi un avantage par rapport aux paramètres ref et out standards: elle permet de rendre le paramètre optionel, par exemple si on n’est pas intéressé par le résultat (évidemment il faut que la méthode appelée gère ce cas de façon appropriée).

Gérer les problèmes de timeout lors de l’upload de gros fichiers avec HttpWebRequest

Si vous avez déjà eu à uploader de gros volumes de données en HTTP, vous avez probablement rencontré des problèmes de timeout. La valeur par défault de Timeout pour HttpWebRequest est de 100 secondes, ce qui signifie que s’il s’écoule plus que cette durée entre le moment où vous envoyez les en-têtes de la requête et celui où vous recevez les en-têtes de la réponse, votre requête échouera. Bien sûr, si vous uploadez un gros fichier, vous devez augmenter ce timeout… mais à quelle valeur ?

Si vous connaissez la bande passante disponible, vous pourriez calculer une approximation du temps nécessaire à l’upload, mais ce n’est pas très fiable, parce que si le réseau est encombré, cela prendra plus longtemps, et votre requête échouera alors qu’elle aurait pu réussir si elle avait eu plus de temps. Alors, faut-il définir le timeout à une valeur très grande, comme plusieurs heures, voire Timeout.Infinite ? Probablement pas. La principale raison est que même si le transfert proprement dit peut durer des heures, certaines phases de l’échange ne devraient pas durer si longtemps. Décomposons les phases d’un upload HTTP :

timeout1

Obtenir le flux de la requête ou récupérer la réponse (parties oranges) n’est pas supposé prendre très longtemps, donc il est clair qu’on a besoin ici d’un timeout plutôt court (la valeur par défaut de 100 secondes semble raisonnable). Par contre, envoyer le corps de la requête (partie bleue) peut prendre beaucoup plus longtemps, et il n’y a pas de moyen fiable de déterminer combien de temps ça devrait prendre; tant qu’on arrive à envoyer des données et que le serveur les reçoit, il n’y a aucune raison de ne pas continuer, même si ça prend des heures. Donc en fait, on ne veut pas du tout de timeout dans ce cas ! Malheureusement, le comportement de la propriété Timeout est de tout prendre en compte de l’appel à GetRequestStream jusqu’au retour de GetResponse

A mon avis, c’est un défaut de conception de la classe HttpWebRequest, et il me gêne depuis très longtemps. Donc j’ai fini par trouver une solution, qui se base sur le fait que les versions asynchrones de GetRequestStream et GetResponse n’ont pas de mécanisme de timeout. Voilà ce que dit la documentation :

La propriété Timeout n’a aucun effet sur les requêtes asynchrones lancées à l’aide des méthodes BeginGetResponse ou BeginGetRequestStream.

Dans le cas de requêtes asynchrones, l’application cliente implémente son propre mécanisme de délai d’expiration. Consultez l’exemple de la méthode BeginGetResponse.

Une solution pourrait donc être d’utiliser ces méthodes directement (ou les nouvelles versions basées sur des Task: GetRequestStreamAsync et GetResponseAsync) ; mais bien souvent, il y déjà une base de code existante qui utilise les méthodes synchrones, et changer le code pour le rendre complètement asynchrone n’est généralement pas trivial. L’approche la plus simple est de créer des wrapper synchrones autour de BeginGetRequestStream et BeginGetResponse, avec un moyen de spécifier un timeout pour ces opérations :

    public static class WebRequestExtensions
    {
        public static Stream GetRequestStreamWithTimeout(
            this WebRequest request,
            int? millisecondsTimeout = null)
        {
            return AsyncToSyncWithTimeout(
                request.BeginGetRequestStream,
                request.EndGetRequestStream,
                millisecondsTimeout ?? request.Timeout);
        }

        public static WebResponse GetResponseWithTimeout(
            this HttpWebRequest request,
            int? millisecondsTimeout = null)
        {
            return AsyncToSyncWithTimeout(
                request.BeginGetResponse,
                request.EndGetResponse,
                millisecondsTimeout ?? request.Timeout);
        }

        private static T AsyncToSyncWithTimeout<T>(
            Func<AsyncCallback, object, IAsyncResult> begin,
            Func<IAsyncResult, T> end,
            int millisecondsTimeout)
        {
            var iar = begin(null, null);
            if (!iar.AsyncWaitHandle.WaitOne(millisecondsTimeout))
            {
                var ex = new TimeoutException();
                throw new WebException(ex.Message, ex, WebExceptionStatus.Timeout, null);
            }
            return end(iar);
        }
    }

(notez que j’ai utilisé les méthodes Begin/End plutôt que les méthodes Async, afin de garder la compatibilité avec des versions plus anciennes de  .NET)

Ces méthodes d’extension peuvent être utilisées à la place de GetRequestStream et GetResponse ; chacune d’elle déclenchera une exception de timeout si elle dure trop longtemps, mais une fois que vous avez le flux de la requête, vous avez tout le temps que vous voulez pour uploader les données. Notez que le flux lui-même a ses propres timeouts de lecture et d’écriture (5 minutes par défaut), donc si 5 minutes s’écoulent sans que le moindre octet soit uploadé, la méthode Write déclenchera une exception. Voilà le nouveau scénario d’upload en utilisant ces nouvelles méthodes :

timeout2

Comme vous pouvez le voir, la seule différence est que le timeout ne s’applique plus au transfert du corps de la requête, mais seulement à l’obtention du flux de la requête et de la réponse. Voilà un exemple complet qui correspond au scénario ci-dessus :

long UploadFile(string path, string url, string contentType)
{
    // Build request
    var request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = WebRequestMethods.Http.Post;
    request.AllowWriteStreamBuffering = false;
    request.ContentType = contentType;
    string fileName = Path.GetFileName(path);
    request.Headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", fileName);
    
    try
    {
        // Open source file
        using (var fileStream = File.OpenRead(path))
        {
            // Set content length based on source file length
            request.ContentLength = fileStream.Length;
            
            // Get the request stream with the default timeout
            using (var requestStream = request.GetRequestStreamWithTimeout())
            {
                // Upload the file with no timeout
                fileStream.CopyTo(requestStream);
            }
        }
        
        // Get response with the default timeout, and parse the response body
        using (var response = request.GetResponseWithTimeout())
        using (var responseStream = response.GetResponseStream())
        using (var reader = new StreamReader(responseStream))
        {
            string json = reader.ReadToEnd();
            var j = JObject.Parse(json);
            return j.Value<long>("Id");
        }
    }
    catch (WebException ex)
    {
        if (ex.Status == WebExceptionStatus.Timeout)
        {
            LogError(ex, "Timeout while uploading '{0}'", fileName);
        }
        else
        {
            LogError(ex, "Error while uploading '{0}'", fileName);
        }
        throw;
    }
}

J’espère que cela vous sera utile !

Détecter les changements d’une propriété de dépendance dans WinRT

Aujourd’hui j’aimerais partager une astuce que j’ai utilisée en développant ma première application Windows Store. Je suis complètement nouveau sur cette technologie et c’est mon premier billet à ce sujet, donc j’espère que je ne vais pas trop me ridiculiser…

Il est souvent utile d’être notifié quand la valeur d’une propriété de dépendance change ; beaucoup de contrôles exposent des évènements à cet effet, mais ce n’est pas toujours le cas. Par exemple, récemment j’essayais de détecter les changements de la propriété Content d’un ContentControl. En WPF, j’aurais utilisé la classe DependencyPropertyDescriptor, mais elle n’est pas disponible dans WinRT.

Heureusement, il y a un mécanisme qui existe sur toutes les plateformes XAML, et qui peut résoudre ce problème: le binding. La solution est donc simplement de créer une classe avec un propriété “bidon” qui est liée à la propriété qu’on souhaite observer, et d’appeler un handler quand la valeur de cette propriété bidon change. Pour rendre ça un peu plus propre et masquer l’implémentation réelle, j’ai emballé ça sous forme d’une méthode d’extension qui renvoie un IDisposable:

    public static class DependencyObjectExtensions
    {
        public static IDisposable WatchProperty(this DependencyObject target,
                                                string propertyPath,
                                                DependencyPropertyChangedEventHandler handler)
        {
            return new DependencyPropertyWatcher(target, propertyPath, handler);
        }

        class DependencyPropertyWatcher : DependencyObject, IDisposable
        {
            private DependencyPropertyChangedEventHandler _handler;

            public DependencyPropertyWatcher(DependencyObject target,
                                             string propertyPath,
                                             DependencyPropertyChangedEventHandler handler)
            {
                if (target == null) throw new ArgumentNullException("target");
                if (propertyPath == null) throw new ArgumentNullException("propertyPath");
                if (handler == null) throw new ArgumentNullException("handler");

                _handler = handler;

                var binding = new Binding
                {
                    Source = target,
                    Path = new PropertyPath(propertyPath),
                    Mode = BindingMode.OneWay
                };
                BindingOperations.SetBinding(this, ValueProperty, binding);
            }

            private static readonly DependencyProperty ValueProperty =
                DependencyProperty.Register(
                    "Value",
                    typeof(object),
                    typeof(DependencyPropertyWatcher),
                    new PropertyMetadata(null, ValuePropertyChanged));

            private static void ValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var watcher = d as DependencyPropertyWatcher;
                if (watcher == null)
                    return;

                watcher.OnValueChanged(e);
            }

            private void OnValueChanged(DependencyPropertyChangedEventArgs e)
            {
                var handler = _handler;
                if (handler != null)
                    handler(this, e);
            }

            public void Dispose()
            {
                _handler = null;
                // There is no ClearBinding method, so set a dummy binding instead
                BindingOperations.SetBinding(this, ValueProperty, new Binding());
            }
        }
    }

On peut l’utiliser comme ceci:

// Abonnement
watcher = myControl.WatchProperty("Content", myControl_ContentChanged);

// Désabonnement
watcher.Dispose();

J’espère que vous trouverez cela utile!

Utiliser les informations de l’appelant de C# 5 quand on cible une version plus ancienne du .NET Framework

Les attributs d’informations de l’appelant (caller info attributes) sont une des nouveautés de C# 5. Ce sont des attributs qui s’appliquent aux paramètres optionnels d’une méthode, et qui permettent de passer implicitement à cette méthode des informations sur l’appelant. Je ne suis pas sûr que cette description soit très claire, voilà donc un exemple pour bien comprendre :

        static void Log(
            string message,
            [CallerMemberName] string memberName = null,
            [CallerFilePath] string filePath = null,
            [CallerLineNumber] int lineNumber = 0)
        {
            Console.WriteLine(
                "[{0:g} - {1} - {2} - line {3}] {4}",
                DateTime.UtcNow,
                memberName,
                filePath,
                lineNumber,
                message);
        }

La méthode ci-dessous prend plusieurs paramètres destinés à passer des informations sur l’appelant : nom du membre (méthode, propriété…) appelant, chemin du fichier source et numéro de ligne. Les attributs Caller* font que le compilateur va automatiquement passer les valeurs appropriées, vous n’avez donc pas besoin de les spécifier vous-même :

        static void Foo()
        {
            Log("Hello world");
            // Equivalent à:
            // Log("Hello world", "Foo", @"C:\x\y\z\Program.cs", 18);
        }

Cela est bien sûr particulièrement utile pour les méthodes de log…

Remarquez que les attributs Caller* attributes sont définis dans le .NET Framework 4.5. Maintenant, supposons que l’on utilise Visual Studio 2012 pour cibler une version plus ancienne du framework (par exemple 4.0) : les attributs d’informations de l’appelant n’existent pas en 4.5, on ne peut donc pas les utiliser… Mais attendez ! Et si on pouvait faire croire au compilateur que ces attributs sont bien présents ? Définissons nos propres attributs, en prenant soin de les placer dans le namespace où le compilateur s’attend à les trouver :

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerMemberNameAttribute : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerFilePathAttribute : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerLineNumberAttribute : Attribute
    {
    }
}

Si on compile et qu’on exécute le programme, on voit que nos attributs personnalisés on bien été pris en compte par le compilateur. Il n’est donc pas nécessaire qu’ils se trouvent dans mscorlib.dll comme les “vrais” attributs d’informations de l’appelant, il suffit qu’ils soient dans le bon namespace, et le compilateur les accepte. Cela nous permet d’utiliser cette fonctionnalité quand on cible .NET 4.0, 3.5 ou même 2.0 !

Remarquez qu’une astuce similaire permet la création de méthodes d’extension quand on cible .NET 2.0 : il suffit de créer une classe ExtensionAttribute dans le namespace System.Runtime.CompilerServices. C’est d’ailleurs ce qui permet à LinqBridge de fonctionner.

[WPF] Créer des styles paramétrables à l’aide des propriétés attachées

Je voudrais aujourd’hui partager avec vous une petite astuce que j’utilise souvent depuis quelques mois. Supposons que pour améliorer l’apparence de votre application, vous ayez créé des styles personnalisés pour les contrôles standards :

Bon, je ne suis pas designer, hein… mais ça fera parfaitement l’affaire pour illustrer mon propos ;). Ces styles sont très simples, ce sont les styles par défaut des CheckBox et RadioButton dans lesquels j’ai seulement modifié les templates pour remplacer les BulletChrome par ces superbes marques bleues. Voilà le code :

        <Style x:Key="{x:Type CheckBox}" TargetType="{x:Type CheckBox}">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
            <Setter Property="Background" Value="{StaticResource CheckBoxFillNormal}"/>
            <Setter Property="BorderBrush" Value="{StaticResource CheckBoxStroke}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="FocusVisualStyle" Value="{StaticResource EmptyCheckBoxFocusVisual}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type CheckBox}">
                        <BulletDecorator Background="Transparent"
                                         SnapsToDevicePixels="true">
                            <BulletDecorator.Bullet>
                                <Border BorderBrush="{TemplateBinding BorderBrush}"
                                        Background="{TemplateBinding Background}"
                                        BorderThickness="1"
                                        Width="11" Height="11" Margin="0,1,0,0">
                                    <Grid>
                                        <Path Name="TickMark"
                                              Fill="Blue"
                                              Data="M0,4 5,9 9,0 4,5"
                                              Visibility="Hidden" />
                                        <Rectangle Name="IndeterminateMark"
                                                   Fill="Blue"
                                                   Width="7" Height="7"
                                                   HorizontalAlignment="Center"
                                                   VerticalAlignment="Center"
                                                   Visibility="Hidden" />
                                    </Grid>
                                </Border>
                            </BulletDecorator.Bullet>
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              Margin="{TemplateBinding Padding}"
                                              RecognizesAccessKey="True"
                                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </BulletDecorator>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasContent" Value="true">
                                <Setter Property="FocusVisualStyle" Value="{StaticResource CheckRadioFocusVisual}"/>
                                <Setter Property="Padding" Value="4,0,0,0"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="TickMark" Property="Visibility" Value="Visible" />
                            </Trigger>
                            <Trigger Property="IsChecked" Value="{x:Null}">
                                <Setter TargetName="IndeterminateMark" Property="Visibility" Value="Visible" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="{x:Type RadioButton}" TargetType="{x:Type RadioButton}">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
            <Setter Property="Background" Value="#F4F4F4"/>
            <Setter Property="BorderBrush" Value="{StaticResource CheckBoxStroke}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type RadioButton}">
                        <BulletDecorator Background="Transparent">
                            <BulletDecorator.Bullet>
                                <Grid VerticalAlignment="Center" Margin="0,1,0,0">
                                    <Ellipse Width="11" Height="11"
                                             Stroke="{TemplateBinding BorderBrush}"
                                             StrokeThickness="1"
                                             Fill="{TemplateBinding Background}" />
                                    <Ellipse Name="TickMark"
                                             Width="7" Height="7"
                                             Fill="Blue"
                                             Visibility="Hidden" />
                                    <Ellipse Name="IndeterminateMark"
                                             Width="3" Height="3"
                                             Fill="Blue"
                                             Visibility="Hidden" />
                                </Grid>
                            </BulletDecorator.Bullet>
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              Margin="{TemplateBinding Padding}"
                                              RecognizesAccessKey="True"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </BulletDecorator>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasContent" Value="true">
                                <Setter Property="FocusVisualStyle" Value="{StaticResource CheckRadioFocusVisual}"/>
                                <Setter Property="Padding" Value="4,0,0,0"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="TickMark" Property="Visibility" Value="Visible" />
                            </Trigger>
                            <Trigger Property="IsChecked" Value="{x:Null}">
                                <Setter TargetName="IndeterminateMark" Property="Visibility" Value="Visible" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

Vous avez donc maintenant de magnifiques contrôles qui vont faire de l’application un grand succès, le management est content, tout va pour le mieux dans le meilleur des mondes… Et là, vous réalisez que dans un autre écran de l’application, les contrôles doivent avoir le même style, mais en vert !

La première solution qui vient à l’esprit est de dupliquer le style en mettant du vert à la place du bleu. Mais comme vous êtes un bon développeur soucieux des bonnes pratiques, vous savez que la duplication de code, c’est mal : si vous devez un jour modifier le style de la CheckBox bleue, il faudra aussi modifier celui de la verte… et peut-être aussi la rouge, la noire, etc. Bref, ça deviendrait vite ingérable. Il faut donc refactoriser, mais comment ? Il faudrait pouvoir passer la couleur en paramètre du style, mais un style n’est pas une méthode à laquelle on peut passer des paramètres…

Il faudrait donc avoir une propriété supplémentaire pour indiquer la couleur des “ticks”, et se binder sur cette propriété dans le template. Une approche possible est de créer des contrôles personnalisés hérités de CheckBox et RadioButton, avec une propriété supplémentaire TickBrush… mais personnellement je n’aime pas beaucoup cette approche : je préfère éviter de créer de nouveaux contrôles quand on peut s’en sortir avec les contrôles standard.

En fait, il y a une solution plus simple : il suffit de créer une classe, qu’on appelle par exemple ThemeProperties, et de déclarer dedans une propriété attachée de type Brush:

    public static class ThemeProperties
    {
        public static Brush GetTickBrush(DependencyObject obj)
        {
            return (Brush)obj.GetValue(TickBrushProperty);
        }

        public static void SetTickBrush(DependencyObject obj, Brush value)
        {
            obj.SetValue(TickBrushProperty, value);
        }

        public static readonly DependencyProperty TickBrushProperty =
            DependencyProperty.RegisterAttached(
                "TickBrush",
                typeof(Brush),
                typeof(ThemeProperties),
                new FrameworkPropertyMetadata(Brushes.Black));
    }

On modifie un peu nos templates pour remplacer la couleur en dur par un binding sur cette propriété :

                                ...

                                <!-- CheckBox -->
                                        <Path Name="TickMark"
                                              Fill="{TemplateBinding my:ThemeProperties.TickBrush}"
                                              Data="M0,4 5,9 9,0 4,5"
                                              Visibility="Hidden" />
                                        <Rectangle Name="IndeterminateMark"
                                                   Fill="{TemplateBinding my:ThemeProperties.TickBrush}"
                                                   Width="7" Height="7"
                                                   HorizontalAlignment="Center"
                                                   VerticalAlignment="Center"
                                                   Visibility="Hidden" />

                                ...

                                <!-- RadioButton -->
                                    <Ellipse Name="TickMark"
                                             Width="7" Height="7"
                                             Fill="{TemplateBinding my:ThemeProperties.TickBrush}"
                                             Visibility="Hidden" />
                                    <Ellipse Name="IndeterminateMark"
                                             Width="3" Height="3"
                                             Fill="{TemplateBinding my:ThemeProperties.TickBrush}"
                                             Visibility="Hidden" />

Et quand on utilise les contrôles, on précise la couleur qu’on veut pour le tick :

<CheckBox Content="Checked" IsChecked="True" my:ThemeProperties.TickBrush="Blue" />

On peut donc maintenant avoir des contrôles qui partagent le même style, mais en changeant la couleur d’un élément du template :

On a donc effectivement rendu les styles paramétrables ! Il reste cependant un petit souci : étant donné que tous les contrôles d’un même éran utilisent tous la même couleur, il n’est pas très pratique de devoir la répéter sur chaque contrôle. L’idéal serait de pouvoir indiquer à la racine de la vue la couleur à utiliser pour tous les contrôles… et justement, les dependency properties (et donc les propriétés attachées) offrent une fonctionnalité qui permet de faire exactement ça : l’héritage de valeur. Il suffit d’indiquer le flag Inherits lors de la déclaration de la propriété TickBrush :

        public static readonly DependencyProperty TickBrushProperty =
            DependencyProperty.RegisterAttached(
                "TickBrush",
                typeof(Brush),
                typeof(ThemeProperties),
                new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.Inherits));

Avec cette modification, la propriété devient “ambiante” : il suffit d’indiquer sa valeur sur un contrôle parent (par exemple la racine de la vue) pour que tous les descendants prennent en compte cette valeur. On peut donc très facilement faire des écrans avec des contrôles qui partagent le même style, mais en appliquant des couleurs différentes.

Le concept peut bien sûr être étendu à d’autres cas : en fait, dès qu’un élément du template doit varier selon un critère arbitraire, cette technique peut s’appliquer. Cela évite bien souvent de devoir dupliquer le template pour ne changer qu’un petit détail.

[WPF] Comment faire un binding dans les cas où on n’hérite pas du DataContext

La propriété DataContext de WPF est extrêmement pratique, car elle est automatiquement héritée par tous les enfants de l’élément où elle est définie ; il n’est donc pas nécessaire de la redéfinir pour chaque élément qu’on veut lier aux données. Cependant, il arrive que le DataContext ne soit pas accessible pour certains éléments : c’est le cas des éléments qui n’appartiennent pas à l’arbre visuel ni à l’arbre logique. Il devient alors très difficile de définir une propriété ce ces éléments par un binding…

Prenons un exemple simple : on veut afficher une liste de produits dans un DataGrid. Dans la grille, on veut pouvoir afficher où masquer la colonne du prix, en fonction d’une propriété ShowPrice exposée par le ViewModel. L’approche évidente consiste à binder la propriété Visibility de la colonne à la propriété ShowPrice :

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding ShowPrice,
                        Converter={StaticResource visibilityConverter}}"/>

Malheureusement, changer la valeur de la propriété ShowPrice n’a aucun effet, et la colonne reste toujours affichée… pourquoi ? Si on regarde la fenêtre de sortie de Visual Studio, on remarque la ligne suivante :

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=ShowPrice; DataItem=null; target element is ‘DataGridTextColumn’ (HashCode=32685253); target property is ‘Visibility’ (type ‘Visibility’)

Derrière cet obscur charabia se cache une explication toute simple : WPF ne sait pas quel FrameworkElement utiliser pour récupérer le DataContext, car la colonne n’appartient pas à l’arbre visuel ni à l’arbre logique du DataGrid.

On peut toujours essayer de “triturer” le binding pour obtenir le résultat voulu, par exemple en essayant de binder par rapport au DataGrid lui-même :

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding DataContext.ShowPrice,
                        Converter={StaticResource visibilityConverter},
                        RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

Ou encore, en ajoutant une CheckBox bindée sur ShowPrice et en essayant de binder la visibilité de la colonne sur la propriété IsChecked, en spécifiant le nom de l’élément :

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding IsChecked,
                        Converter={StaticResource visibilityConverter},
                        ElementName=chkShowPrice}"/>

Mais rien à faire, on obtient toujours le même résultat…

A ce stade, il semble que la seule approche qui pourrait marcher est de passer par le code-behind, ce qu’on préfère généralement éviter quand on suit le pattern MVVM… mais ce serait dommage d’abandonner aussi vite 😉

La solution est en fait assez simple, et se base sur la classe Freezable. La vocation première de cette classe est de définir des objets qui ont un état modifiable et un état non modifiable. Mais en l’occurrence, la caractéristique qui nous intéresse est qu’un objet qui hérite de Freezable peut hériter du DataContext, bien qu’il ne s’agisse pas d’un élément visuel. Je ne connais pas le mécanisme exact qui permet d’obtenir ce comportement, mais toujours est-il que cela va nous permettre d’arriver au résultat voulu…

L’idée est de créer une classe, que j’ai appelée BindingProxy, qui hérite de Freezable et dans laquelle on va déclarer une dependency property Data :

    public class BindingProxy : Freezable
    {
        #region Overrides of Freezable

        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }

        #endregion

        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }

On va ensuite déclarer une instance de cette classe dans les ressources du DataGrid, et binder la propriété Data sur le DataContext courant :

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

Il suffit ensuite de spécifier que la source de notre binding est cet objet BindingProxy, facilement accessible puisqu’il est déclaré comme ressource :

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                    Visibility="{Binding Data.ShowPrice,
                        Converter={StaticResource visibilityConverter},
                        Source={StaticResource proxy}}"/>

Remarquez qu’on a aussi préfixé le chemin du binding par “Data”, puisque le chemin est maintenant relatif à l’objet BindingProxy.

Le binding fonctionne maintenant comme prévu, moyennant une solution relativement simple à mettre en oeuvre…

[C#] Relation parent/enfant et sérialisation XML

Me revoilà avec un peu de retard, j’ai un peu manqué de temps libre ces dernières semaines… Voilà donc un petit post pour présenter une idée qui m’est venue récemment. Pour une fois, il ne sera pas question de WPF, c’est de conception C# qu’il s’agit !

Le problème

Il est assez courant, dans un programme, d’avoir un objet parent qui possède une collection d’enfants ayant une référence vers leur parent. C’est le cas, par exemple, des contrôles Windows Forms, qui possèdent une collection de contrôles enfants (Controls), et une propriété qui indique le contrôle parent (Parent).

Ce type de structure est assez simple à réaliser, cela nécessite juste un peu de code pour maintenir la cohérence de la relation. Là où ça se complique un peu, c’est quand on veut sérialiser l’objet parent en XML… Prenons un exemple simple (purement théorique bien sûr) :

    public class Parent
    {
        public Parent()
        {
            this.Children = new List<Child>();
        }

        public string Name { get; set; }

        public List<Child> Children { get; set; }

        public void AddChild(Child child)
        {
            child.ParentObject = this;
            this.Children.Add(child);
        }

        public void RemoveChild(Child child)
        {
            this.Children.Remove(child);
            child.ParentObject = null;
        }
    }
    public class Child
    {
        public string Name { get; set; }

        public Parent ParentObject { get; set; }
    }

Créons une instance de Parent avec quelques enfants, et essayons de la sérialiser en XML :

            Parent p = new Parent { Name = "The parent" };
            p.AddChild(new Child { Name = "First child" });
            p.AddChild(new Child { Name = "Second child" });

            string xml;
            XmlSerializer xs = new XmlSerializer(typeof(Parent));
            using (StringWriter wr = new StringWriter())
            {
                xs.Serialize(wr, p);
                xml = wr.ToString();
            }

            Console.WriteLine(xml);

Quand on sérialise l’objet Parent, il se produit une InvalidOperationException due à une référence circulaire : en effet, le parent référence les enfants, qui référencent le parent, qui référence les enfants… et ainsi de suite. La première solution qui vient à l’esprit est de ne pas sérialiser la propriété Child.ParentObject : il suffit pour cela de lui appliquer l’attribut XmlIgnore. La sérialisation se passe alors sans problème, mais quand on désérialise, mauvaise surprise : la propriété ParentObject n’est pas renseignée, et pour cause, puisqu’elle n’a pas été sauvegardée…

On pourrait opter pour une solution simple : après la désérialisation : parcourir la liste des enfants pour affecter manuellement la propriété ParentObject. Mais ce n’est vraiment pas très élégant… et comme j’aime bien écrire du beau code, j’ai donc cherché autre chose 😉

La solution

La solution qui m’est venue à l’esprit consiste en une collection générique spécialisée ChildItemCollection<P,T>, et une interface IChildItem<P> que les enfants doivent implémenter.

L’interface IChildItem<P> définit simplement une propriété Parent, de type P :

    /// <summary>
    /// Defines the contract for an object that has a parent object
    /// </summary>
    /// <typeparam name="P">Type of the parent object</typeparam>
    public interface IChildItem<P> where P : class
    {
        P Parent { get; set; }
    }

La collection ChildItemCollection<P,T> implémente IList<T> en délégant l’implémentation à une List<T> ou à la collection passée en paramètre du constructeur, et gère le maintien de la relation parent/enfant :

    /// <summary>
    /// Collection of child items. This collection automatically set the
    /// Parent property of the child items when they are added or removed
    /// </summary>
    /// <typeparam name="P">Type of the parent object</typeparam>
    /// <typeparam name="T">Type of the child items</typeparam>
    public class ChildItemCollection<P, T> : IList<T>
        where P : class
        where T : IChildItem<P>
    {
        private P _parent;
        private IList<T> _collection;

        public ChildItemCollection(P parent)
        {
            this._parent = parent;
            this._collection = new List<T>();
        }

        public ChildItemCollection(P parent, IList<T> collection)
        {
            this._parent = parent;
            this._collection = collection;
        }

        #region IList<T> Members

        public int IndexOf(T item)
        {
            return _collection.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            if (item != null)
                item.Parent = _parent;
            _collection.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            T oldItem = _collection[index];
            _collection.RemoveAt(index);
            if (oldItem != null)
                oldItem.Parent = null;
        }

        public T this[int index]
        {
            get
            {
                return _collection[index];
            }
            set
            {
                T oldItem = _collection[index];
                if (value != null)
                    value.Parent = _parent;
                _collection[index] = value;
                if (oldItem != null)
                    oldItem.Parent = null;
            }
        }

        #endregion

        #region ICollection<T> Members

        public void Add(T item)
        {
            if (item != null)
                item.Parent = _parent;
            _collection.Add(item);
        }

        public void Clear()
        {
            foreach (T item in _collection)
            {
                if (item != null)
                    item.Parent = null;
            }
            _collection.Clear();
        }

        public bool Contains(T item)
        {
            return _collection.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            _collection.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return _collection.Count; }
        }

        public bool IsReadOnly
        {
            get { return _collection.IsReadOnly; }
        }

        public bool Remove(T item)
        {
            bool b = _collection.Remove(item);
            if (item != null)
                item.Parent = null;
            return b;
        }

        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            return _collection.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (_collection as System.Collections.IEnumerable).GetEnumerator();
        }

        #endregion
    }

Voyons donc comment utiliser cette solution dans notre exemple précédent… Nous allons d’abord modifier la classe Child pour qu’elle implémente l’interface IChildItem<Parent> :

    public class Child : IChildItem<Parent>
    {
        public string Name { get; set; }

        [XmlIgnore]
        public Parent ParentObject { get; internal set; }

        #region IChildItem<Parent> Members

        Parent IChildItem<Parent>.Parent
        {
            get
            {
                return this.ParentObject;
            }
            set
            {
                this.ParentObject = value;
            }
        }

        #endregion
    }

Vous remarquerez qu’on implémente l’interface IChildItem<Parent> de façon explicite : l’intérêt est de “masquer” la propriété Parent, qui ne sera accessible que si on manipule l’objet Child via une variable de type IChildItem<Parent>. On définit aussi l’accesseur set de la propriété ParentObject comme étant internal, de façon à empêcher sa modification à partir d’un autre assembly.

Dans la classe Parent, il suffit de remplacer la List<Child> par une ChildItemCollection<Parent, Child>. On supprime au passage les méthodes AddChild et RemoveChild, qui ne sont plus nécessaires puisque la classe ChildItemCollection<P,T> se charge de renseigner la propriété Parent.

    public class Parent
    {
        public Parent()
        {
            this.Children = new ChildItemCollection<Parent, Child>(this);
        }

        public string Name { get; set; }

        public ChildItemCollection<Parent, Child> Children { get; private set; }
    }

Notez qu’on passe au constructeur de ChildItemCollection<Parent, Child> une référence vers l’objet courant : c’est de cette façon que la collection sait quel doit être le parent de ses éléments.

Le code utilisé précédemment pour sérialiser un Parent fonctionne maintenant correctement. Lors de la désérialisation, la propriété Child.ParentObject n’est pas affectée quand le Child est désérialisé (puisqu’elle a l’attribut XmlIgnore), mais lors de l’ajout à la collection Parent.Children.

Cette solution permet donc de conserver la relation parent/enfant lors de la sérialisation XML, sans recourir à des bricolages peu élégants… Notez cependant une restriction : si la propriété ParentObject est modifiée autrement que par la classe ChildItemCollection<P,T>, la cohérence de la relation parent/enfant est rompue. Il est cependant assez simple d’ajouter à l’accesseur set la logique pour maintenir la cohérence, je ne l’ai pas fait dans cet exemple par souci de clarté et de simplicité.

[Visual Studio] Astuce : définir un élément du projet comme sous-élément d’un autre

Vous avez certainement remarqué que, dans un projet C#, certains éléments sont placés sous un élément parent : c’est le cas, par exemple, pour les fichiers générés par un designer ou assistant :

Explorateur de solution
Le fichier Model1.Designer.cs est placé sous le fichier Model1.edmx

L’astuce suivante permet d’obtenir le même comportement pour vos propres fichiers.

Supposons que vous souhaitiez personnaliser les classes générées par le designer d’entités. Vous ne pouvez pas modifier le fichier Model1.Designer.cs, puisque vos modifications seraient écrasées par le designer. Vous allez donc créer un nouveau fichier, par exemple Model1.Custom.cs, où vous allez mettre votre code pour les classes d’entité (à l’aide du mot clé partial). Par défaut, ce fichier est placé directement à la racine du projet :

Explorateur de solution
Le fichier Model1.Custom.cs est placé à la racine du projet

Pour bien mettre en évidence le lien avec Model1.edmx, on préfèrerait le voir “sous” Model1.edmx… Bien que l’interface de Visual Studio ne propose pas cette option, c’est possible : il faut pour celà modifier le fichier .csproj à la main. Le plus simple, pour celà, est de décharger le projet (clic droit sur le projet, “décharger le projet“) et de l’éditer directement dans Visual Studio (clic droit sur le projet déchargé, “modifier FooBar.csproj“). Cherchez l’élément correspondant au fichier Model1.Custom.cs, et ajoutez un sous-élément comme indiqué ci-dessous :

    <Compile Include="Model1.Custom.cs">
        <DependentUpon>Model1.edmx</DependentUpon>
    </Compile>

Rechargez le projet : Model1.Custom.cs apparait maintenant sous Model1.edmx.

Explorateur de solution
Model1.Custom.cs apparait sous Model1.edmx

Cette astuce permet de mieux structurer son projet pour s’y retrouver plus facilement.