Welcome to MSDN Blogs Sign in | Join | Help

Lecture de paramètres web.config depuis une application Silverlight

Il est plus que courant qu’une application web ASP.NET ait à lire des paramètres de configuration depuis le fichier de configuration du site, web.config. Cette opération est très simple depuis une page ASPX par le biais de ConfigurationSettings.AppSettings. Ce scénario ne fonctionne cependant pas depuis une application Silverlight qui se voit systématiquement refusée l’accès à web.config.

La raison de cette impossibilité est que l’application Silverlight s’exécute sur le poste client. Le XAP, au même titre que le navigateur, ne peut accéder aux ressources réservées au serveur et en particulier à web.config ; il n’est par exemple par défaut pas possible à un client d’accéder au fichier de configuration par une URI de type http://www.mysite.com/web.config

Voici deux solutions possibles afin de permettre au XAP d’obtenir des paramètres de configuration dynamiques :

La première consiste à ne pas passer par web.config et mettre à disposition sur le même site un fichier de configuration que le XAP récupèrera comme n’importe quelle autre ressource réseau. Ce fichier devra ensuite être parsé et interprété par le XAP.

La seconde solution, souvent moins complexe à mettre en oeuvre, met à profit la classe ConfigurationSettings d’ASP.NET et passer les valeurs du fichier web.config par le biais de la propriété Silverlight.InitParameters. Le principe est le suivant :

1.      Dans la page ASPX, lire les valeurs souhaitées depuis web.config

<script runat="server">

    private void Page_Load()

    {

        var monSetting = ConfigurationSettings.AppSettings["monSetting"];

        mySlPart.InitParameters = string.Format("monSettingSilverlight={0}", monSetting);

    }

</script>

2.      Formater ces valeurs sous la forme "arg1=ma valeur,arg2=ma deuxieme valeur"

3.      Assigner la chaîne formatée précédemment à la propriété Silverlight.InitParameters

4.      Depuis Silverlight, accéder aux valeurs d’InitParameters depuis le handler d’Application.Startup et la propriété InitParams de son argument.

   if (e.InitParams.ContainsKey("monSettingSilverlight "))

      maVariable = e.InitParams["monSettingSilverlight "];

 

 

SlreadwebconfigFR

L’application attachée à ce post donne un exemple concret de passage de paramètre.

Un grand merci à Régis pour l’idée de ce post.

Posted by lvovan | 0 Comments
Attachment(s): SlReadWebConfig.zip

Reading web.config parameters from a Silverlight application

It is a very common ASP.NET scenario to have to read a configuration parameter from web.config. This operation is very easily implemented from ASPX using Configuratoin.AppSettings. However, this scenario does not work out of the box from a Silverlight application, which doesn’t seem to even have access to web.config.

The reason is simple: a Silverlight application runs on the client side. The XAP -just like the browser- cannot access server resources such as web.config; you cannot, for example, get the site’s configuration file using an URI such as http://www.mysite.com/web.config.

This post demonstrates two workarounds to get the configuration data from your server’s configuration to your XAP.

The first solution is to actually not store configuration data in your web.config file, but to another file which would be made available by IIS like any other network resource. This file could then be downloaded, parsed and interpreted by the XAP.

The second solution, usually simpler to implement, makes use of ASP.NET’s ConfigurationSettings class and passes the web.config values to the Silverlight.InitParameters property. This works as follows:

<script runat="server">

    private void Page_Load()

    {

        var mySetting = ConfigurationSettings.AppSettings["mySetting"];

        mySlPart.InitParameters = string.Format("mySilverlightSetting={0}", mySetting);

    }

</script>

1.      In the ASPX page, read the values from web.config using ConfigurationSettings.AppSettings

2.      Format these values as follows : "arg1=ma valeur,arg2=ma deuxieme valeur"

3.      Assign the formatted string to the your Silverlight control’s InitParameter property

4.      From Silverlight, get the InitParameters values from your Application.Startup handler, in the InitParams property of the event’s argument.

   if (e.InitParams.ContainsKey("mySilverlightSetting"))

      maVariable = e.InitParams["mySilverlightSetting"];

 

SlreadwebconfigEN

The attached application gives a working example of the second solution. Thanks to Regis for giving me something to post about !

 

Posted by lvovan | 0 Comments
Attachment(s): SlReadWebConfig.zip

Interacting between Powershell and a host .NET application

An unusual topic in today’s post : interacting between Powershell and a host .NET application. The examples available on MSDN show how to host Powershell runspaces using classes from the System.Management.Automation namespace ; but it is also possible to share objects with hosted Powershell scripts paving the way to hybrid scenarios. This allows, for example, handling Exchange Management Shell objets from a .NET application.

Interactions between the .NET host and the Powershell runspaces are handled by the Runspace.SessionStateProxy property. Once the object is associated with a variable, the scripts running in the runspace are able to access it just like any other local Powershell variable.

rs.SessionStateProxy.SetVariable("myVariable", ic);

using (Pipeline pipeline = rs.CreatePipeline("$myVariable.SomeNumber = $myVariable.SomeNumber + 1"))

pipeline.Invoke();

The other way around, getting an object that was instantiated by a hosted Powershell script, is also possible and requires the following cast:

ic = (rs.SessionStateProxy.GetVariable("mySecondVariable") as PSObject).BaseObject as InstantiatedClass;

The code accompanying this post shows two-operations, modifying the property of an object and calling methods from Powershell. Please note that the example code requires a reference to the Powershell System.Management.Automation DLL.

 

Posted by lvovan | 1 Comments
Attachment(s): Program.cs

Interaction entre Powershell et une application hôte .NET

Sujet inhabituel sur ce blog: l’interaction entre Powershell et une application hôte .NET. Les exemples disponibles sur MSDN montrent comment héberger des runspaces Powershell via les classes du namespace System.Management.Automation; mais outre le simple hébergement de scripts, il est également possible de partager des objets entre les scripts Powershell hébergé et le code .NET, ouvrant ainsi la voie à des scénarios hybrides. Ceci permet, par exemple, le traitement par une application .NET d’objets mis à disposition via Exchange Management Shell (EMS).

Les interactions entre hôte .NET et runspace Powershell s’effectuent via la propriété Runspace.SessionStateProxy. Une fois une instance d’objet associé à une variable, les scripts s’exécutant dans ce runspace pourront y accéder comme à tout autre variable de la session Powershell :

rs.SessionStateProxy.SetVariable("myVariable", ic);

using (Pipeline pipeline = rs.CreatePipeline("$myVariable.SomeNumber = $myVariable.SomeNumber + 1"))

pipeline.Invoke();


Dans l’autre sens, récupérer dans l’hôte un objet instancié depuis script Powershell requiert le cast suivant

ic = (rs.SessionStateProxy.GetVariable("mySecondVariable") as PSObject).BaseObject as InstantiatedClass;


L’exemple de code joint montre ces opérations dans les deux sens, modifie des propriétés d’un objet, et effectue des appels de méthodes. Notez que l’exemple requiert une référence vers la DLL System.Management.Automation de Powershell.

 

Posted by lvovan | 1 Comments
Attachment(s): Program.cs

Exemple de partage de ressource entre Silverlight et WPF

Ci-joint un petit projet démontrant la réutilisation de ressource entre Silverlight et WPF. Les explications détaillées viendront plus tard :)
Posted by lvovan | 1 Comments
Filed under: , ,

Attachment(s): MixedApp.zip

Utilisation de flags binaires [Flags]

Ce post décrit comment utiliser et définir des flags binaires. Ceux-ci permettent de combiner facilement et lisiblement des propriétés. Un des exemples d’utilisation les plus populaires étant avec les attributs de fichiers :

File.SetAttributes(@"C:\Temp\MonFichier",

                FileAttributes.ReadOnly | FileAttributes.Archive | FileAttributes.Temporary);


L'utilisation de flags binaires est recommandée lorsque les propriétés représentées sont liées, non mutuellement exclusives et combinables. Les flags permettent ainsi :

-          De rendre le code plus lisible

-          De laisser la porte ouverte à de nouvelles propriétés

-          D’optimiser certains scénarios de sérialisation

Le principe des flags est le suivant : chaque valeur booléenne est représentée par un seul et unique bit d’un scalaire numérique (byte, short, int, int64…). On considère que la valeur est vraie lorsque le bit correspondant vaut 1, et qu’elle est fausse sinon. Cela permet dans le cas d’un byte (8 bits), de représenter jusqu’à 8 valeurs combinables.

Le programme exemple montre comment utiliser les flags par le biais d’une classe Voiture et d’options qui lui sont applicables.

On déclare tout d’abord une énumération OptionsVoiture, qui définit les valeurs possibles des options, chacune étant associée à une puissance de 2. L’utilisation de MaxValue est une facilité qui permettra de déterminer rapidement si toutes les valeurs ont été cochées. Il est aussi d’usage d’utiliser un type plus grand que nécessaire afin de réserver des bits pour de futures évolutions.

[Flags]

public enum OptionsVoiture: byte

{

    Aucune = 0,

    Aileron = 1,

    ToitOuvrant = 2,

    RetroviseursElectriques = 4,

    VitresTeintees = 8,

    Neons = 16,

    PriseAir = 32,

    Halogenes = 64,

    Rabaissement = 128,

    Toutes = Byte.MaxValue

}

Note : la décoration avec l’attribut optionnel Flags permet simplement aux IDE de proposer une interface conviviale lors de la définition de ce type de valeur (par exemple avec une liste à cocher)

La propriété de Voiture permettant de représenter toutes ces options est de type OptionsVoiture

public OptionsVoiture Options { get; set; }

Et  ses valeurs peuvent être définies en combinant les valeurs de l’énumération avec un OU logique :

var vt = new Voiture();

vt.Options = OptionsVoiture.Aileron | OptionsVoiture.ToitOuvrant | OptionsVoiture.Neons;


 

 

La vérification de la valeur d’une Options est faite à l’aide d’un ET logique, ou dans le cas de OptionsVoiture.Toutes d’une simple vérification d’égalité.

if ((this.Options & OptionsVoiture.PriseAir) == OptionsVoiture.PriseAir)
    sb.Append("Prise d’air !");

if (this.Options == OptionsVoiture.Toutes) sb.Append("Toutes options ! ");

Il est également très pratique de pouvoir vérifier plusieurs valeurs d’un seul coup. L’exemple qui suit détermine si les options Aileron et Neons sont activées.

var aileronEtNeons = (OptionsVoiture.Aileron | OptionsVoiture.Neons);
bool isAileronEtNeons = (maVoiture.Options & aileronEtNeons) == aileronEtNeons;

Et pour finir, une petite astuce permettant de choisir toutes les options sauf certaines :

var maVoiture = new Voiture();

maVoiture.Options = OptionsVoiture.Toutes;

maVoiture.Options &= ~(OptionsVoiture.Halogenes | OptionsVoiture.Rabaissement);

En ce qui concerne la sérialisation, bien que l’utilisation de flags permette une économie substantielle de mémoire, elle n’est pas la solution parfaite à tous les scénarios. Par exemple, le gain de place peut vite se voir contrecarré par des requêtes SQL plus coûteuses, ainsi qu’une perte au niveau des possibilités d’indexation.

Un grand merci à Olivier Sallaberry, guru des Windows Live Services, pour ses conseils et la relecture de cet article J Et en espérant comme à l’habitude, que cela facilitera vos développements !

 

Posted by lvovan | 1 Comments
Filed under: ,

Attachment(s): FlagsExample.cs

Sorts and filters on ObservableCollection

Using the ObservableCollection<T> collection is a very common practice among UI developers.  Added to the fact that they allow easy UI updates, they also allow sorting and filtering operations via CollectionViews. This post describes a pattern linking an ObservableCollection to sortable and filterable ListCollectionViews, which are themselves displayed in the UI.

image

The pattern works as follows:

 

 

1.       Define a get/set property for the source ObservableCollection, and a read-only property for each view of the source

ObservableCollection<Personne> _sampleData;

public ObservableCollection<Personne> SampleData

    get { return _sampleData; } 
    set 
   
        _sampleData = value
         
    }

}

ListCollectionView _cvSampleDataSortAge, _cvSampleDataFilterAge;
public CollectionView CVSampleDataSortAge { get { return _cvSampleDataSortAge; } }
public CollectionView CVSampleDataFilterAge { get { return _cvSampleDataFilterAge; } }

2.       When the source ObservableCollection is set :

a.       Instanciate the ListCollectionViews

b.      On each one of them add if necessary:

                                                                          i.       SortDescription(s) for views that involve sorting

                                                                         ii.      Filter predicates for views that filter

c.       Raise the PropertyChanged event on the source ObservableCollection property and on each view.

set
{

    _sampleData = value;

    // Instantiates the views, SortDescriptions, filters…

    _cvSampleDataSortAge = new ListCollectionView(_sampleData);

    _cvSampleDataSortAge.SortDescriptions.Add(new SortDescription("Age",
         ListSortDirection.Ascending));

    _cvSampleDataFilterAge = new ListCollectionView(_sampleData);

    _cvSampleDataFilterAge.Filter += new Predicate<object>(FiltreAge);

    // Raises PropertyChanged events on the properties

    OnNotifyPropertyChanged("SampleData");

    OnNotifyPropertyChanged("CVSampleDataSortAge");

    OnNotifyPropertyChanged("CVSampleDataFilterAge");

    // Synchronises with the current item (optional)

    var cv = CollectionViewSource.GetDefaultView(_sampleData);

    cv.CurrentChanged += new EventHandler(delegate

        {

            _cvSampleDataSortAge.MoveCurrentTo(cv.CurrentItem);

            _cvSampleDataFilterAge.MoveCurrentTo(cv.CurrentItem);

        });

}

3.       In your XAML UI code, bind to the ListCollectionView properties

<ListBox ItemsSource="{Binding Path=CVSampleDataSortAge}"/>

The views are automatically updated in regards to the source’s composition (as defined by NotifyCollectionChangedAction). Changes to a property of an element contained within an ObservableCollection will not trigger an update of the views. This functionality, as described in a previous post, can be implemented using another type of Collection which we’ll explore in further details in the next post.

Some more details

CollectionViews separate the presentation of a Collection, from the source collection itself. This allows (for example) scenarios where an ObservableCollection can be filtered and sorted on screen, without having to modify the source Collection or having to handle temporary collections which would have to be kept in sync with the source.

 

 

 

image

Posted by lvovan | 1 Comments
Filed under: ,

Attachment(s): CollectionViewDemo_EN.zip

Tris et filtres sur des ObservableCollection<T>

Utiliser des ObservableCollection<T> est une pratique fondamentale lors du développement d’UI. Outre le fait qu’elles permettent des mises à jour dynamique de l’UI, elles peuvent également être triées et filtrées par le biais des CollectionViews.

Ce post décrit un pattern réutilisable permettant de simplifier le code liant une ObservableCollection à une ou plusieurs ListCollectionViews, avec un projet exemple.

image

 Le principe est le suivant :

1.       Définir une propriété get/set exposant l’ObservableCollection source, et autant de propriétés get qu’il y aura de vues de la source.

ObservableCollection<Personne> _sampleData; // qui est une ObservableCollection<Personne>

public PersonneCollection SampleData

    get { return _sampleData; }

    set 
   
        _sampleData = value
         
    }
}

ListCollectionView _cvSampleDataTriAge, _cvSampleDataFiltreAge;
public CollectionView CVSampleDataTriAge { get { return _cvSampleDataTriAge; } }
public CollectionView CVSampleDataFiltreAge { get { return _cvSampleDataFiltreAge; } }

 

2.       Lorsque l’ObservableCollection est settée :

a.       Instancier autant de ListCollectionViews qu’il y aura de vues différentes

b.      Sur chacune d’elles définir si nécessaire:

                                                                          i.       la ou les SortDescriptions pour les tris

                                                                         ii.      le prédicat de filtre

c.       Déclencher PropertyChanged sur la propriété source et toutes les vues

set

{

    _sampleData = value;

    // On instancie les vues et leurs descriptions de tri/filtre 
    _cvSampleDataTriAge = new ListCollectionView(_sampleData); 
    _cvSampleDataTriAge.SortDescriptions.Add(new SortDescription("Age",
         ListSortDirection.Ascending)); 
    _cvSampleDataFiltreAge = new ListCollectionView(_sampleData); 
    _cvSampleDataFiltreAge.Filter += new Predicate<object>(FiltreAge);

    // On déclenche une notification pour les propriétés 
    OnNotifyPropertyChanged("SampleData"); 
    OnNotifyPropertyChanged("CVSampleDataTriAge"); 
    OnNotifyPropertyChanged("CVSampleDataFiltreAge");  

    // Synchronisation du CurrentItem (optionnel) 
    var cv = CollectionViewSource.GetDefaultView(_sampleData); 
    cv.CurrentChanged += new EventHandler(delegate 
       
            _cvSampleDataTriAge.MoveCurrentTo(cv.CurrentItem); 
            _cvSampleDataFiltreAge.MoveCurrentTo(cv.CurrentItem); 
        });
}

3.       Dans le XAML, se binder aux ListCollectionViews comme à n’importe quelle autre propriété

<ListBox ItemsSource="{Binding Path=CVSampleDataTriAge}"/>

 

Les vues seront automatiquement mises à jour en fonction de la composition (cf. NotifyCollectionChangedAction) de la source. Le changement de valeur d’une propriété d’un élément de la collection source ne déclenche pas de mise à jour des vues. Cette dernière problématique est traitée par le biais de l’ObservableNotifiableCollection, déjà expliqué dans un précédent post, sera décrite avec plus de détails dans un prochain message.

Détails de fonctionnement

Les CollectionViews permettent d’avoir une séparation entre la représentation de la collection utilisée pour l’affichage et la « vraie » collection source. Cela permet par exemple de filtrer une ObservableCollection source sans avoir à maintenir manuellement une synchronisation avec une collection intermédiaire.

image

Posted by lvovan | 1 Comments

Attachment(s): CollectionViewDemo.zip

WPF: Some terms defined for Rudy

I received a mail from Rudy, who asked me for some clarifications on several WPF definitions:

« The terms for which I would like to have some details, relations and usage are DataTemplate, ContentControl, ControlTemplate and Content »

A DataTemplate is a graphical representation of an object. For example if a DataTemplate is defined for a type, instances of that type can be represented on the screen by what is defined inside the DataTemplate.

A ContentControl is a Control containing a unique child (its « content »). That child is defined via the Content property of the ContentControl. Being of type object, this child can be anything : a graphical object, but also an instance of a business object.

Let’s combine these concepts in a simple example:

clip_image002

We declare :

  • a DataTemplate targeting the type String
  • two ContentControls with a Content of type String

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:system="clr-namespace:System;assembly=mscorlib">

            

       <Page.Resources>

             <!-- We can define a default DataTemplate for all instances

                  of String. There’s a text, followed by the value of the

                  string and its length -->

             <DataTemplate DataType="{x:Type system:String}">

                    <StackPanel Orientation="Horizontal">

                           <TextBlock Text="Valeur et longueur de la String : "/>

                           <TextBlock Text="{Binding}" Background="LightBlue"/>

                           <TextBlock Text="{Binding Length}" Background="Orange"/>

                    </StackPanel>

              </DataTemplate>

</Page.Resources>

            

       <StackPanel>

             <ContentControl>

                    <!-- We can define the Content property explicitly   -->

                    <ContentControl.Content>

                           <system:String>Hello world</system:String>

                    </ContentControl.Content>

             </ContentControl>

      

             <ContentControl>

                    <!-- Or we usually just benefit from the fact that Content

                         is the default property defined within the tags. So

                         this is equivalent to the previous example -->

                    <system:String>Hello world</system:String>

             </ContentControl>

       </StackPanel>

</Page>

The idea behind ControlTemplate is the same as the one behind the DataTemplate: redefine the way an object is represented graphically. There is a difference though; graphic objects deriving from Control are special in that they can be redefined graphically only via ControlTemplates and are not affected by DataTemplates. Below is the re-definition of a Button’s ControlTemplate:

clip_image002[4]

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:system="clr-namespace:System;assembly=mscorlib">

            

       <StackPanel>

             <!-- A standard button -->

             <Button>Coucou</Button>

            

             <!-- A button where the ControlTemplate is redefined

                  as an orange ellipse -->

             <Button>

                    <Button.Template>

                           <ControlTemplate TargetType="Button">

                                  <Grid>

                                        <Ellipse Fill="Orange"/>

                                        <ContentPresenter HorizontalAlignment="Center"/>

                                  </Grid>

                           </ControlTemplate>

                    </Button.Template>

             Coucou

             </Button>

       </StackPanel>

</Page>

Thanks to Rudy for proofreading the French version of this post!

Posted by lvovan | 1 Comments
Filed under: ,

WPF : Quelques définitions de termes pour Rudy

Un mail de Rudy, qui souhaitait quelques réponses concernant différentes définitions WPF :

« Les termes pour lesquels j'aimerais avoir quelques précisions à propos de leurs définitions, relations et utilisations sont les suivants : DataTemplate, ContentControl, ControlTemplate et Content »

Un DataTemplate est une représentation graphique d’un objet. Par exemple, si un DataTemplate est défini pour un type, les instances de ce type peuvent être représentées graphiquement par celui-ci.

Un ContentControl est un Control contenant un seul et unique enfant (le « contenu »). Ce dernier est défini par le biais de sa propriété Content. Etant de type object, ce contenu peut donc être de n’importe quel type : aussi bien un autre élément graphique qu’un objet métier.

Combinons ces trois concepts dans un exemple concret :

clip_image002

On y a déclaré :

  • un DataTemplate associé au type String
  • deux ContentControls dont la propriété Content est de type String

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:system="clr-namespace:System;assembly=mscorlib">

            

       <Page.Resources>

             <!-- On définit un DataTemplate par défaut pour toutes les

                  instances de String. Il y a un texte, puis la valeur

                  de la String, et la longueur de la string -->

             <DataTemplate DataType="{x:Type system:String}">

                    <StackPanel Orientation="Horizontal">

                           <TextBlock Text="Valeur et longueur de la String : "/>

                           <TextBlock Text="{Binding}" Background="LightBlue"/>

                           <TextBlock Text="{Binding Length}" Background="Orange"/>

                    </StackPanel>

              </DataTemplate>

</Page.Resources>

            

       <StackPanel>

             <ContentControl>

                    <!-- On définit la valeur de la propriété Content -->

                    <ContentControl.Content>

                           <system:String>Hello world</system:String>

                    </ContentControl.Content>

             </ContentControl>

      

             <ContentControl>

                    <!-- Par défaut, ce qui est directement entre les balises

                         du ContentControl est la définition de la propriété

                         Content, donc ceci est équivalent au ContentControl précédent -->

                    <system:String>Hello world</system:String>

             </ContentControl>

       </StackPanel>

</Page>

En ce qui concerne le ControlTemplate, l’idée est la même que pour le DataTemplate : redéfinir l’aspect visuel d’un objet. Cependant, le ControlTemplate sert à redéfinir uniquement les objets de type Control, sur lesquels les DataTemplate ne s’appliquent pas. Les Control sont différents des autres objets, car étant déjà graphiques, ils peuvent avoir des comportements particuliers à respecter. Ci-dessous un exemple de redéfinition d’un ControlTemplate :

clip_image004

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:system="clr-namespace:System;assembly=mscorlib">

            

       <StackPanel>

             <!-- Un bouton standard -->

             <Button>Coucou</Button>

            

             <!-- Un bouton dont on redéfinit le ControlTemplate

                  (Button.Template est de type ControlTemplate) -->

             <Button>

                    <Button.Template>

                           <ControlTemplate TargetType="Button">

                                  <Grid>

                                        <Ellipse Fill="Orange"/>

                                        <ContentPresenter HorizontalAlignment="Center"/>

                                  </Grid>

                           </ControlTemplate>

                    </Button.Template>

             Coucou

             </Button>

       </StackPanel>

</Page>

 

Merci à Rudy pour sa relecture et ses remarques sur ce post !

Posted by lvovan | 1 Comments
Filed under: ,

Mise à jour de l’UI pendant un chargement asynchrone (WPF)

Dans un programme, les données à afficher n’étant pas toujours disponibles instantanément il est impératif de prévenir l’utilisateur lorsqu’un processus long est succeptible d’affecter les interactions. On peut prendre comme règle informelle que l’utilisateur doit être prévenu de toute opération bloquante pouvant durer un tiers de seconde ou plus.

Cet article et son projet associé montrent deux façons de traiter les phases de chargement au niveau UI. La première utilise une classe wrapper et un BackgroundWorker, tandis que la deuxième fait usage de l’ObjectDataProvider WPF.

image

Wrapper et BackgroundWorker
Ce pattern utilise une classe implémentant INotifyPropertyChanged exposant deux propriétés : IsBusy et Data. La méthode Load définit IsBusy à true, et démarre un BackgroundWorker local. Des notifications de changements pour les propriétés IsBusy et Data sont déclenchés lorsque BackgroundWorker.RunWorkCompleted est appellé. La simplicité de ce wrapper rend facile son extension, en y implémentant par exemple un support pour BackgroundWorker.ReportsProgress (exercice que je laisse au lecteur ;)).
Ci-dessous un exemple d’utilisation démontrant la simplicité du markup nécessaire :

<!-- The ListBox est bindée à la propriété Data de IsBusyDataClass -->
<ListBox ItemsSource="{Binding Source={StaticResource myDataClass},Path=Data,Mode=OneWay}"/>
<Button Click="Button2_Click">Load from DataClass.LoadData()!</Button>
<!-- La visibilité du StackPanel est bindée à la propriété de IsBusyDataClass -->
<StackPanel 
        Margin="2,20,2,0" 
        Visibility="{Binding Source={StaticResource myDataClass}, 
         Path=IsBusy,Converter={StaticResource BooleanToVisibilityConverter}}">

    <ProgressBar Value="5" Height="30" IsIndeterminate="True"/>

    <TextBlock Text="Loading data..."/>

</StackPanel>

ObjectDataProviderReporter
On pourrait tout d’abord se demander pourquoi implémenter une nouvelle classe, alors que l’ObjectDataProvider WPF dispose déjà de la propriété IsAsynchronous. En creusant un peu, on s’aperçoit rapidement des deux problèmes suivants :

  • IsAsynchronous n’est pas une dependency property
  • Affecter un binding à ObjectDataProviderIsAsynchronous ne fonctionne pas comme on pourrait le croire. ObjectDataProvider dérivant de DataSourceProvider, les bindings y faisant référence s’appliquent aux données résultantes et non à l’instance du DataSourceProvider lui même.

La technique du reporter requiert l’utilisation de deux classes : une dérivant de ObjectDataProvider (que j’ai appellé IsBusyObjectDataProvider) et réutilisant la propriété IsAsynchronous (forcée à true) pour effectuer une tâche en arrière plan. Une propriété IsBusy est ajoutée à IsBusyObjectDataProvider et est définie à true et false respectivement lors des appels à BeginQuery et OnQueryFinished. La deuxième classe, le reporter, surveille les changements de IsBusyObjectDataProvider.IsBusy, et relaie ceux-ci directement via sa propre propriété IsBusy.
Le code client se doit déclarer une instance du reporter :

<Window.Resources>

   

    <local:IsBusyObjectDataProviderReporter x:Key="odp1reporter">

        <local:IsBusyObjectDataProviderReporter.IsBusyObjectDataProvider>

            <local:IsBusyObjectDataProvider ObjectType="{x:Type local:OdpDataClass}"

                                            MethodName="SlowLoadData"

                                            IsInitialLoadEnabled="True"/>

        </local:IsBusyObjectDataProviderReporter.IsBusyObjectDataProvider>

    </local:IsBusyObjectDataProviderReporter>

   

</Window.Resources>

Puis la logique d’affichage de données se bind à l’enfant du reporter, tandis que la logique d’affichage s’attachera directement à la propriété IsBusy du reporter :

<!-- La ListBox est bindée à la propriété Data de l’ObjectDataProvider (IsBusyObjectDataProviderReporter.IsBusyObjectDataProvider) -->

<ListBox ItemsSource="{Binding Source={StaticResource odp1reporter}, Path=IsBusyObjectDataProvider.Data,Mode=OneWay}"/>

<Button Click="Button_Click">Load from SlowLoadData2()!</Button>

<!-- La visibilité du StackPanel est bindée à la propriété IsBusy du reporter -->

<StackPanel

        Margin="2,20,2,0"

        Visibility="{Binding Source={StaticResource odp1reporter},

         Path=IsBusy,Converter={StaticResource BooleanToVisibilityConverter}}">

    <ProgressBar Value="5" Height="30" IsIndeterminate="True"/>

    <TextBlock Text="Loading data..."/>

</StackPanel>   

 

Comme toujours, chaque approche a ses avantages et ses inconvénients. Le pattern wrapper requiert l’implémentation d’une classe par type de chargement, tandis que le pattern reporter demandera un peu plus de XAML, en retour d’une réutilisabilité améliorée.

 

Merci à Christophe Marty pour m'avoir soumis cette problématique !

Posted by lvovan | 1 Comments
Filed under: ,

Attachment(s): AsynchronousDataLoading.zip

GUI feedback when loading data asynchronously (WPF)

Data is not always readily available for applications to display, and it is essential to notify the user when any long running process is going to hinder the smoothness of a user's workflow: it is indeed the least of things as loading time is a purely technical issue. As a rule of thumb, consider any delay superior to a third of a second as an event the user should be notified about.

This post and its associated example project, show two ways to provide users with feedback during a long running operation. The first one uses a wrapper class to load data using the BackgroundWorker, while the other leverages WPF's ObjectDataProvider.

image

Wrapper with BackgroundWorker
This pattern is made up of a single class implementing INotifyPropertyChanged exposing two properties: IsBusy and Data. The Load method sets the IsBusy property to True and starts up a local BackgroundWorker. When BackgroundWorker.RunWorkerCompleted is fired, property change notifications are called for IsBusy and Data. The simplicity of this wrapper class allows it to be easily extended to include support for finer-grained details using BackgroundWorker.ReportsProgress (which is left as an exercise for the reader ;).
Below is a usage example demonstrating the simplicity of the consuming markup:

<!-- The ListBox is bound to the Data property of the IsBusyDataClass -->

<ListBox ItemsSource="{Binding Source={StaticResource myDataClass},Path=Data,Mode=OneWay}"/>

<Button Click="Button2_Click">Load from DataClass.LoadData()!</Button>

<!-- This StackPanel's visibility is bound to the IsBusy property of IsBusyDataClass -->

<StackPanel

        Margin="2,20,2,0"

        Visibility="{Binding Source={StaticResource myDataClass},

         Path=IsBusy,Converter={StaticResource BooleanToVisibilityConverter}}">

    <ProgressBar Value="5" Height="30" IsIndeterminate="True"/>

    <TextBlock Text="Loading data..."/>

</StackPanel>

The ObjectDataProviderReporter
First of all, one might at first question the need for a new class as WPF's ObjectDataProvider already features the IsAsynchronous property. But digging a bit deeper uncovers two important issues:

  • IsAsynchronous is not a dependency property
  • Binding to ObjectDataProviderIsAsynchronous doesn’t work as expected because as ObjectDataProvider derives from DataSourceProvider, any binding to it will reference the resulting data, and not the DataSourceProvider instance itself.

The reporter technique requires two classes: a reporter and a derivation from ObjectDataProvider (which I called IsBusyObjectDataProvider) which will leverage IsAsynchronous for the background operation. IsBusyObjectDataProvider’s IsAsynchronous is forced to true, and its IsBusy property is set to true and false respectively in the BeginQuery and OnQueryFinished overrides. The second class, the reporter, watches for changes in its IsBusyObjectDataProvider.IsBusy, and relays the changes via its own IsBusy property.
The client code has to declare an instance of the reporter:

<Window.Resources>

   

    <local:IsBusyObjectDataProviderReporter x:Key="odp1reporter">

        <local:IsBusyObjectDataProviderReporter.IsBusyObjectDataProvider>

            <local:IsBusyObjectDataProvider ObjectType="{x:Type local:OdpDataClass}"

                                            MethodName="SlowLoadData"

                                            IsInitialLoadEnabled="True"/>

        </local:IsBusyObjectDataProviderReporter.IsBusyObjectDataProvider>

    </local:IsBusyObjectDataProviderReporter>

   

</Window.Resources>

And then binds data loading logic to the reporter’s child ObjectDataProvider, and UI notification logic to the reporter’s IsBusy property:

<!-- The ListBox is bound to the Data property of the reporter's inner ObjectDataProvider (IsBusyObjectDataProviderReporter.IsBusyObjectDataProvider) -->

<ListBox ItemsSource="{Binding Source={StaticResource odp1reporter}, Path=IsBusyObjectDataProvider.Data,Mode=OneWay}"/>

<Button Click="Button_Click">Load from SlowLoadData2()!</Button>

<!-- This StackPanel's visibility is bound to the IsBusy property of the reporter -->

<StackPanel

        Margin="2,20,2,0"

        Visibility="{Binding Source={StaticResource odp1reporter},

         Path=IsBusy,Converter={StaticResource BooleanToVisibilityConverter}}">

    <ProgressBar Value="5" Height="30" IsIndeterminate="True"/>

    <TextBlock Text="Loading data..."/>

</StackPanel>

 

Each approach has its pro and cons. The wrapper pattern requires creating a class with some “plumbing” for every kind of load operation, while the reporter one other makes the XAML slightly more verbose, in exchange for improved reusability.

Thanks to Christophe Marty for submitting his question about asynchronous loading :)

Posted by lvovan | 0 Comments
Filed under: ,

Attachment(s): AsynchronousDataLoading.zip

Pourquoi : InvalidOperationException : Impossible de trouver le nom dans la portée de nom de 'System.Windows.Controls.ControlTemplate'.

(correction : quel thread créé effectivement l'élément, 2ème workaround)

Cette exception est déroutante à plusieurs titres: le code en cause semble parfois marcher, le ou les storyboard(s) référençant le nom sont valides, le nom de l'élément existe et est déclaré avant sa référence dans le fichier XAML. Ce post décrit une des causes fréquentes de cette exception.

Ci-dessous du code démontrant le problème (disponible dans le projet accompagnant cet article)

<EventTrigger RoutedEvent="ButtonBase.Click">

    <EventTrigger.Actions>

        <BeginStoryboard>

            <Storyboard>

                <!-- La référence à myRectangle peut échouer! -->

                <ColorAnimation Storyboard.TargetName="myRectangle"

                                Storyboard.TargetProperty="Fill.Color"

                                To="Blue" AutoReverse="True"/>

            </Storyboard>

        </BeginStoryboard>

    </EventTrigger.Actions>

</EventTrigger>

et

Button btn = new Button();

btn.Style = this.FindResource("myFailingStyle") as Style;

gbxNewParent.Content = btn;

btn.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); // Crash

Au premier abord, tout devrait marcher :
- le bouton est créé et ajouté au GroupBox
- on y déclenche l'évènement Click
- qui déclenche à son tour l'EventTrigger qui animera l'élément myRectangle du ControlTemplate du bouton

Cette exécution provoque cependant une InvalidOperationException. La raison? Les threads du système de rendu de WPF. En effet - et contrairement à Windows Forms - ce n'est pas parceque la main nous est rendue après gbxNewParent.Content = btn, que les éléments visuels qui composent btn ont été créés. En effet, et comme l'explique très bien Nick Kramer, le thread UI pose des messages qui seront traités de manière asynchrone plus tard par le thread UI (lors de la passe de layout). Dans notre cas l'élément myRectangle du ControlTemplate n'a pas été créé, et une référence à celui-ci lève logiquement une exception. (Notons qu'il n'y a pas d'erreur lorsque le bouton change de parent car il n'y pas dans ce cas une re-création complète)

20081201 WPF Render thread

Il n'y a pour le moment (malheureusement) pas de moyen de connaître exactement le moment auquel un élément a été créé par le thread rendering. La solution que j'utilise est donc simple : différer l'appel de méthode déclenchant le référencement risqué au Dispatcher, et ce avec une faible priorité.

this.Dispatcher.BeginInvoke( 
    new Action(delegate { btn.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); }), 
    System.Windows.Threading.DispatcherPriority.ContextIdle);


Nick propose également une autre manière de procéder (merci à lui!), en appelant explicitement UpdateLayout() avant de déclencher l'évènement. Attention cependant aux performances car UpdateLayout() est une opération potentiellement gourmande.

btn.Style = this.FindResource("myFailingStyle") as Style;

gbxNewParent.Content = btn;

UpdateLayout();

btn.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); // No crash

Posted by lvovan | 2 Comments
Filed under: ,

Attachment(s): EarlyResourceAccessIssue.zip

Why: InvalidOperationException name cannot be found in the name scope of 'System.Windows.Controls.ControlTemplate'

(edit : correction following Nick's comment, about which thread actually creates the elements)

This exception is confusing for multiple reasons: the code causing it does works on some occasions, the storyboard(s) referencing the infringing name are valid, an element does exist with the name, and has been declared prior to its reference in the storyboard(s) in XAML. This post describes one of the cause for this exception:

Below is a XAML snippet demonstrating the issue (available in the project accompanying this article)

<EventTrigger RoutedEvent="ButtonBase.Click">

    <EventTrigger.Actions>

        <BeginStoryboard>

            <Storyboard>

                <!-- The reference to myRectangle can fail! -->

                <ColorAnimation Storyboard.TargetName="myRectangle"

                                Storyboard.TargetProperty="Fill.Color"

                                To="Blue" AutoReverse="True"/>

            </Storyboard>

        </BeginStoryboard>

    </EventTrigger.Actions>

</EventTrigger>

and

Button btn = new Button();

btn.Style = this.FindResource("myFailingStyle") as Style;

gbxNewParent.Content = btn;

btn.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); // Crash

At first glance, everything should work:
- The button is created and added to the GroupBox
- The Click event is fired on the button
- which in turns triggers the EventTrigger which will animation the myRectangle element of the button's ControlTemplate

Unfortunately, running the program will cause an InvalidOperationException. The reason? The threads responsible for WPF's layout. As opposed to Windows Forms -which is completely single threaded- the WPF runtime does not wait until everything is created and rendered to give back control to the UI thread. In this example, and as Nick Kramer explains in his post, the UI thread queues messages that will be handled by the very same thread, but at a later time (during the layout pass). In our case, the ControlTemplate's myRectangle element has not yet been created when the storyboard tries to reference it, thus causing the exception. As a side note, no exception is thrown when changing the button's parent, as it does not cause a complete re-creation of the control.

20081201 WPF Render thread

As of 3.5 SP1, there is unfortunately no way to determine exactly when an element has been created by the rendering thread. The workaround I use is quite simple, queue the RaiseEvent call to the Dispatcher thread with a (very) low priority.

this.Dispatcher.BeginInvoke(

    new Action(delegate { btn.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); }),

    System.Windows.Threading.DispatcherPriority.ContextIdle);

Another workaround, provided by Nick himself (thanks!), is to call UpdateLayout() prior to raising the event.

Button btn = new Button();
btn.Style = this.FindResource("myFailingStyle") as Style;
gbxNewParent.Content = btn;

UpdateLayout();

btn.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); // No crash

 

Posted by lvovan | 3 Comments
Filed under: ,

Attachment(s): EarlyResourceAccessIssue.zip

Source Code Editor has XAML Intellisense support (in VS2008SP1) / L'éditeur de code source dispose de l'Intellisense (dans VS2008SP1)

Visual Studio 2008 Service Pack 1 brought numerous improvements to the IDE's WPF support, especially in regard to XAML files. Not only has the WPF designer (Cider) been made faster and more stable, but the Source Code Editor has also had its capabilities enriched with XAML Intellisense! Even though this editor does not feature a graphic designer, it proves to be very useful when opening and editing files quickly while consuming less RAM than its full-fledged counterpart.

Le Service Pack 1 de Visual Studio 2008 a apporté de nombreuses améliorations concernant WPF, et en particulier l'édition de fichiers XAML. Outre le fait que le designer WPF (Cider) soit plus stable est plus rapide, le Source Code Editor permet désormais de profiter de l'Intellisense pour les fichiers XAML. Bien que celui-ci ne permette pas de concevoir des contrôles de manière graphique, il accélèrera grandement l'ouverture et l'édition exclusivement textuelle de vos fichiers tout en consommant moins de mémoire vive que l'éditeur WPF par défaut.

SourceCodeEditor

Posted by lvovan | 2 Comments
Filed under: , ,
More Posts Next page »
 
Page view tracker