Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Arnaud Auroux


Silverlight, let there be light...
[WCF Ria Services] Industrialiser vos développements avec le framework Rialternative

J’ai eu le plaisir lors des derniers Techdays d’animer avec Jean-Luc une session sur l’industrialisation du développement avec Silverlight et WCF Ria Services. Nous nous étions concentré sur l’extensibilité de WCF Ria Services pour la conception d’une infrastructure répondant aux exigeances d’une application d’entreprise (séparation des responsabilités, testabilité, etc.).

Dans ce sens, nous avons packagé un framework léger et flexible : “Rialternative” permettant facilement la mise en place d’une infrastructure solide avec WCF Ria Services. Ce framework est disponible sur Codeplex à l’adresse suivante : http://rialternative.codeplex.com/.

Au menu :

  • Implémentation du pattern Repository
  • Simplification de l’implémentation des méthodes de C.R.U.D sur une entité.
  • Simplification de la gestion des autorisations.
  • Génération automatique côté client des autorisations définies côté service.

Vous pourrez retrouver sur la page Codeplex une description de la mise en place rapide de Rialternative ainsi qu’une application exemple dans la catégorie “Source Code”.

Enjoy !

PS : A venir plusieurs posts décrivant en détail chaque fonctionnalité de Rialternative !

[Techdays 2011] Industrialiser le développement avec Silverlight 4 et WCF RIA Services : Slides

Voici les slides de la session que j’ai eu le plaisir d’animer avec Jean-Luc autour de l’industrialisation du développement avec WCF RIA Services et Silverlight.

Merci à tous d’être venu si nombreux et désolé pour ceux qui n’ont pas pu entrer Sad smile

Le timing était un peu court alors n’hésitez pas à nous poser vos questions par rapport à des points sur lesquels on serait passé un peu vite !

A noter que nous sommes en train de packager une solution autour de ce que l’on a présenté et qui sera disponible très prochainement sur codeplex.

Stay tuned !

PS : La vidéo de la session devrait être disponible d’ici quelques semaines sur le site des Techdays.

[Techdays 2011] Industrialiser le développement avec Silverlight 4 et WCF RIA Services
250x250_inscription WCF RIA Services est une nouvelle pièce du Framework .NET 4 livré avec Visual Studio 2010 qui offre un gain réel de productivité et de flexibilité lors du développement d'applications LoB. Cette technologie est souvent présentée comme un outil de développement rapide d’applications, Jean-Luc et moi tenteront de vous prouver Mercredi aux Techdays que WCF RIA Services possède également la flexibilité et l’extensibilité nécessaire pour le développement d’applications d'entreprise solides et de qualité.

La présentation traitera de divers sujets tournant autour de l’industrialisation du développement. Ainsi nous aborderons le sujet de la testabilité ainsi que l’implémentation de certains patterns avec WCF RIA Services comme le repository, l’injection de dépendance ou encore le MVVM. Nous parlerons aussi et surtout de l’extensibilité offerte par WCF RIA Services et ce que le sp1 nous offre dans ce contexte Smile.

En espérant vous voir nombreux !

[Conférence] Présentation de Windows Phone 7

Réinventer l’expérience du mobile », c’est l’objectif que s’est fixé Microsoft avec Windows Phone 7. Sur un marché très concurrentiel avec l’IOS d’Apple et Android de Google, Microsoft a décidé de marquer une rupture avec son précédent système Windows Mobile 6.5 en repartant de zéro.

Rendez-vous le mercredi 8 décembre à 19h sur le campus d’Epitech Paris pour une présentation de Windows Phone 7. Je serai accompagné de Florent Santin, Adrien Sifferman et Niels Freier. Ensemble nous vous présenterons les arcanes de ce nouveau système, ses capacités, ses prérequis, ses atouts ainsi que les choix techniques sur lesquels il a été bâti.

Au programme de cette conférence :

  • Le nouveau design de Windows Phone 7, intitulé Métro, qui le distingue de ses concurrents
  • Une présentation de différents téléphones, dont certains en démonstration sur place
  • MarketPlace, la bibliothèque en ligne d’applications
  • L’environnement de développement de Windows Phone 7 : Visual Studio 2010, Silverlight
  • Développement de jeux vidéo pour ces Smartphones
  • Expression Blend pour Windows Phone 7, comment en tirer parti
  • Démonstrations de plusieurs applications professionnelles pertinentes ainsi que de projets étudiants innovants.

La conférence est suivie d’un cocktail pour échanger avec les intervenants et les auditeurs.

Inscription obligatoire (places limitées) avant le 1er décembre sur Internet

[Silverlight] Effectuer du DataBinding entre une application Silverlight et des éléments HTML

La puissance du moteur de DataBinding de Silverlight fournit un gain de productivité certain sur le développement d’un projet. De plus, il peut arrivé qu’une application Silverlight ait besoin de communiquer avec l’environnement dans lequel elle est hébergée, i.e la page web, afin de répercuter sur le site certains messages, notifications, etc.

Grâce à l’interopérabilité DOM/code managé disponible avec Silverlight, voir cet article pour une première approche assez complète, il est possible d’effectuer du DataBinding avec en cible des éléments Web.

Pour cela je propose la classe suivante :

   1: public string HtmlElementId
   2: {
   3:     get { return (string)GetValue(HtmlElementIdProperty); }
   4:     set { SetValue(HtmlElementIdProperty, value); }
   5: }
   6:  
   7: public string HtmlElementPropertyName
   8: {
   9:     get { return (string)GetValue(HtmlElementPropertyNameProperty); }
  10:     set { SetValue(HtmlElementPropertyNameProperty, value); }
  11: }
  12:  
  13: public string HtmlElementValue
  14: {
  15:     get { return (string)GetValue(HtmlElementValueProperty); }
  16:     set { SetValue(HtmlElementValueProperty, value); }
  17: }
  18:         
  19: public static readonly DependencyProperty HtmlElementIdProperty =
  20:     DependencyProperty.Register("HtmlElementId", typeof(string), typeof(HtmlBinder), null);
  21:  
  22: public static readonly DependencyProperty HtmlElementPropertyNameProperty =
  23:     DependencyProperty.Register("HtmlElementPropertyName", typeof(string), typeof(HtmlBinder), null);
  24:  
  25: public static readonly DependencyProperty HtmlElementValueProperty =
  26:             DependencyProperty.Register("HtmlElementValue",
  27:                                         typeof(string),
  28:                                         typeof(HtmlBinder),
  29:                                         new PropertyMetadata(new PropertyChangedCallback((s, e) =>
  30:                                         {
  31:                                             HtmlBinder htmlBinder = (HtmlBinder)s;
  32:                                             HtmlPage.Window.Eval(String.Format("document.getElementById('{0}')['{1}'] = {2}",
  33:                                                                                htmlBinder.HtmlElementId,
  34:                                                                                htmlBinder.HtmlElementPropertyName,
  35:                                                                                htmlBinder.HtmlElementValue));
  36:                                         })));
  37: }

Ici nous héritons de DependencyObject pour tout simplement pouvoir définir 3 DependencyProperties :

  • HtmlElementId : pour l’id de l’élement HTML qui va être la cible du DataBinding.
  • HtmlElementPropertyName : pour le nom de la propriété de l’élément HTML sur laquelle on veut effectuer du DataBinding.
  • HtmlElementValue : pour la valeur sur laquelle on va se binder côté Silverlight et qui sera reportée sur l’élement HTML grâce à l’interopérabilité DOM / Code managé et plus précisément la méthode HtmlPage.Window.Eval permettant directement d’évaluer du code Javascript.

Un exemple d’utilisation,

Côté XAML :

   1: <UserControl x:Class="HtmlBinder.MainPage"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:              xmlns:local="clr-namespace:HtmlBinder">
   5:     <UserControl.Resources>
   6:         <local:HtmlBinder x:Key="HtmlBinder"
   7:                           HtmlElementPropertyName="value"
   8:                           HtmlElementId="test" />
   9:     </UserControl.Resources>
  10:     <Grid>
  11:         <Slider Value="{Binding HtmlElementValue, Mode=TwoWay, Source={StaticResource HtmlBinder}}" />
  12:     </Grid>
  13: </UserControl>

Côté HTML :

   1: <div>
   2:     <input id=test type="text"  />
   3: </div>

 

Les sources sont disponibles ici :

Enjoy :)

[Techdays 2010] Cas pratique du mode déconnecté de Silverlight : Slides et démo

Comme promis, mais avec un peu de retard, voici les slides ainsi que les sources (disponibles en pièce jointe) de la session que j’ai eu le plaisir d’animer avec Zied autour du mode déconnecté de Silverlight.

Pour ceux qui n’ont pu y assister, elle tournait autour de 2 grands axes :

  • La conception d’une architecture découplée grâce à Unity et au MVVM pattern.
  • La synchronisation des données Online / Offline grâce au Sync Framework.

Merci à tous d’être venu si nombreux à cette session et je reste à votre disposition par rapport à d’éventuels questions.

Enjoy !

PS : La vidéo de la session devrait être disponible d’ici quelques semaines sur le site des Techdays.

[Techdays 2010] Cas pratique du mode déconnecté de Silverlight

original[1] Dans la chasse aux applications monolithiques et aux dépendances fortes au sein de ces applications, le pattern IoC apporte une réponse claire. Tel est le sujet que Zied et moi aborderont demain aux Techdays autour d’un cas pratique. En effet, lorsque l’on parle d’architecture, rien de mieux qu'un exemple concret pour comprendre réellement le fond du problème. Notre exemple tournera autour du mode déconnecté de Silverlight, nous vous montrerons comment bâtir une base solide d’une application destinée à avoir un comportement “occasionnellement connectée”.

Nous en profiterons également pour vous montrer une solution à un problème émergeant avec les applications capables de fonctionner avec ou sans réseau : La synchronisation des données. Aujourd’hui le Sync Framework, un Framework de synchronisation développé par Microsoft, permet de régler efficacement ce problème complexe et nous vous montrerons comment l’utiliser avec le mode déconnecté de Silverlight.

En espérant vous voir nombreux ! :)

A noter que je serai également présent en tant que ATE (Ask The Expert) Silverlight au stand Visual Studio & Expression tenu par Blaise Vignon demain après-midi.

[Silverlight] ResourceDictionary helper

La classe ResourceDictionary est très utile en Silverlight (comme en WPF) pour manipuler facilement des ressources surtout si on désire que ces resources puissent être réutilisables dans différents projets. J’ai pu voir différents messages passés sur certains forums traitant de problèmes pour charger dynamiquement un ResourceDictionary et accéder aux ressources qu’il contient. Ainsi je vous donne un petit helper très simple que j’utilise permettant d’accéder à une ressource donnée d’un ResourceDictionary en particulier :

   1: /// <summary>
   2: /// Provides the logic to recover easily a template in a resource dictionary
   3: /// </summary>
   4: public static class ResourceDictionaryHelper
   5: {
   6:     #region Members
   7:     /// <summary>
   8:     /// The default template key
   9:     /// </summary>
  10:     private const string DEFAULT_TEMPLATE_KEY = "DEFAULT";
  11:  
  12:     /// <summary>
  13:     /// The default resource dictionary path
  14:     /// </summary>
  15:     private const string DEFAULT_RESOURCE_DICTIONARY_PATH =
  16:         "(Chemin du ResourceDictionary à utiliser par défaut)";
  17:  
  18:     /// <summary>
  19:     /// The default resource dictionary
  20:     /// </summary>
  21:     private static readonly ResourceDictionary s_defaultResourceDictionary =
  22:         LoadResourceDictionary(DEFAULT_RESOURCE_DICTIONARY_PATH);
  23:     #endregion Members
  24:  
  25:     #region Public methods
  26:     /// <summary>
  27:     /// Gets a template in the default resource dictionary.
  28:     /// </summary>
  29:     /// <param name="key">The key.</param>
  30:     /// <returns>The template</returns>
  31:     public static FrameworkTemplate GetTemplate(string key)
  32:     {
  33:         return GetTemplate(key, s_defaultResourceDictionary);
  34:     }
  35:  
  36:     /// <summary>
  37:     /// Gets a template in a specified resource dictionary.
  38:     /// </summary>
  39:     /// <param name="key">The key.</param>
  40:     /// <param name="resourceDictionaryPath">The resource dictionary path.</param>
  41:     /// <returns>The template</returns>
  42:     public static FrameworkTemplate GetTemplate(string key, string resourceDictionaryPath)
  43:     {
  44:         return GetTemplate(key, LoadResourceDictionary(resourceDictionaryPath));
  45:     }
  46:     #endregion Public methods
  47:  
  48:     #region Private methods
  49:     /// <summary>
  50:     /// Loads a resource dictionary thanks to a specified path.
  51:     /// </summary>
  52:     /// <param name="path">The path.</param>
  53:     /// <returns>The ressource dictionary</returns>
  54:     private static ResourceDictionary LoadResourceDictionary(string path)
  55:     {
  56:         string xaml = String.Empty;
  57:         StreamResourceInfo xamlInfo = Application.GetResourceStream(new Uri(path, UriKind.Relative));
  58:         using (StreamReader sr = new StreamReader(xamlInfo.Stream))
  59:         {
  60:             xaml = sr.ReadToEnd();
  61:         }
  62:  
  63:         return (ResourceDictionary)XamlReader.Load(xaml);
  64:     }
  65:  
  66:     /// <summary>
  67:     /// Gets a template of a resource dictionary with a specified key or the default key.
  68:     /// </summary>
  69:     /// <param name="key">The key.</param>
  70:     /// <param name="resourceDictionary">The resource dictionary.</param>
  71:     /// <returns>The template</returns>
  72:     private static FrameworkTemplate GetTemplate(string key, ResourceDictionary resourceDictionary)
  73:     {
  74:         FrameworkTemplate dt = null;
  75:         if (resourceDictionary.Contains(key))
  76:         {
  77:             dt = (FrameworkTemplate)resourceDictionary[key];
  78:         }
  79:         else if (resourceDictionary.Contains(DEFAULT_TEMPLATE_KEY))
  80:         {
  81:             dt = (FrameworkTemplate)resourceDictionary[DEFAULT_TEMPLATE_KEY];
  82:         }
  83:  
  84:         return dt;
  85:     }
  86:     #endregion Private methods
  87: }

En espérant que ça puisse être utile à quelqu’un…

Enjoy :)

[Silverlight] ListBox désélection et MVVM

La ListBox est un des contrôles fournit en standard avec Silverlight pour afficher une collection de données. Ainsi lorsqu’on sélectionne un objet dans une ListBox, un évènement SelectionChanged est levé et la propriété SelectedItem a la valeur de la donnée sélectionnée. Si l’on désire désélectionner une donnée dans une ListBox, il suffit d’accéder à la propriété SelectedItem et de lui donner la valeur NULL.

Maintenant, qu'en est-il d’une ListBox utilisée dans un projet faisant intervenir la pattern MVVM. Ce pattern pousse à n’avoir aucun couplage entre la vue (View) et la logique associée à cette dernière plus les données qui vont avec (ViewModel + Model). Ainsi, si l’on veut par exemple désélectionner un élément dans une ListBox sur le clic d’un bouton, que va-t-on faire ? On va tout d’abord utiliser une commande afin que la logique associée au clic du bouton puisse être écrite dans le ViewModel et pas dans la View. Ensuite un problème se pose, le code de cette commande doit accéder à la propriété “SelectedItem” de la ListBox et mettre sa valeur à NULL mais la référence de cette ListBox se trouve dans la View et est bien sûr inaccessible côté ViewModel. Pour régler ce petit problème il suffit simplement de “binder” la propriété SelectedItem en mode TwoWay  sur une propriété de ViewModel, ainsi pour une désélection il suffit d’accéder à cette propriété et lui donner la valeur NULL, valeur qui sera répercuter par le moteur de Binding sur la propriété SelectedItem grâce au mode TwoWay.

Voyons un petit exemple avec une ListBox et un bouton qui permettra de désélectionner l’élément sélectionné.

Voici comment est organisée la solution de l’exemple :

Untitled

A noter que pour la gestion d’évènements j’utilise le pattern “Attached Command Behavior” avec une implémentation proposée par Thomas Lebrun ici.

Mon Model est tout simple et ne mérite pas de commentaire :

   1: namespace UnselectListboxMvvm.Model
   2: {
   3:     public class BusinessObject
   4:     {
   5:         public string Data { get; set; }
   6:     }
   7: }

 

Mon ViewModel est définit comme ceci :

   1: namespace UnselectListboxMvvm.ViewModel
   2: {
   3:     using System.Collections.ObjectModel;
   4:     using System.ComponentModel;
   5:     using System.Windows.Input;
   6:     using UnselectListboxMvvm.Command;
   7:     using UnselectListboxMvvm.Model;
   8:  
   9:     public class MainViewModel : INotifyPropertyChanged
  10:     {
  11:         #region Members
  12:         private string m_listBoxSelectedItem;
  13:         private RelayCommand m_resetListBoxCommand;
  14:         private ObservableCollection<BusinessObject> m_datas;
  15:         #endregion
  16:  
  17:         #region Constructor
  18:         public MainViewModel()
  19:         {
  20:             this.m_datas = new ObservableCollection<BusinessObject>
  21:             {
  22:                 new BusinessObject { Data = "Ceci" },
  23:                 new BusinessObject { Data = "est" },
  24:                 new BusinessObject { Data = "une" },
  25:                 new BusinessObject { Data = "ListBox" },
  26:             };
  27:         }
  28:         #endregion
  29:  
  30:         #region Properties
  31:         public ObservableCollection<BusinessObject> Datas
  32:         {
  33:             get { return this.m_datas; }
  34:         }
  35:  
  36:         public string ListBoxSelectedItem
  37:         {
  38:             get { return this.m_listBoxSelectedItem; }
  39:             set
  40:             {
  41:                 this.m_listBoxSelectedItem = value;
  42:                 if (this.PropertyChanged != null)
  43:                 {
  44:                     this.PropertyChanged(this, new PropertyChangedEventArgs("ListBoxSelectedItem"));
  45:                 }
  46:             }
  47:         }
  48:         #endregion
  49:  
  50:         #region Commands
  51:         public ICommand ResetListBoxCommand
  52:         {
  53:             get
  54:             {
  55:                 if (this.m_resetListBoxCommand == null)
  56:                 {
  57:                     this.m_resetListBoxCommand =
  58:                         new RelayCommand(param => true,
  59:                                          param =>
  60:                                          {
  61:                                              this.ListBoxSelectedItem = null;
  62:                                          });
  63:                 }
  64:  
  65:                 return this.m_resetListBoxCommand;
  66:             }
  67:         }
  68:         #endregion
  69:  
  70:         #region INotifyPropertyChanged Members
  71:         public event PropertyChangedEventHandler PropertyChanged;
  72:         #endregion
  73:     }
  74: }

Il définit une collections de données à afficher dans la View, une propriété sur laquelle sera bindée l’élément sélectionné de la ListBox et une commande permettant de donner la valeur NULL à cette dernière.

 

Et ma View bindée sur le ViewModel définit précédemment:

   1: <UserControl x:Class="UnselectListboxMvvm.View.MainPage"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:              xmlns:command="clr-namespace:UnselectListboxMvvm.Command"
   5:              Width="400"
   6:              Height="300"
   7:              DataContext="{StaticResource MainViewModel}">
   8:     <StackPanel x:Name="LayoutRoot"
   9:                 Orientation="Vertical">
  10:         <ListBox ItemsSource="{Binding Datas}"
  11:                  DisplayMemberPath="Data"
  12:                  SelectedItem="{Binding ListBoxSelectedItem, Mode=TwoWay}" />
  13:         <Button Content="Reset"
  14:                 command:EventBehaviorsHelper.Command="{Binding ResetListBoxCommand}"
  15:                 command:EventBehaviorsHelper.EventName="Click" />
  16:     </StackPanel>
  17: </UserControl>

A noter que ceci ne s’applique bien sûr pas seulement au contrôle ListBox mais également à tous les contrôles héritant de la classe Selector comme le contrôle ComboBox.

Enjoy :)

[Silverlight] Crash Visual Studio 2008 avec les Silverlight 3 tools

Plusieurs posts sont passés il y a quelques mois à propos de fermetures inopinées de Visual Studio 2008 SP1 lors de l’ouverture de fichier XAML pour des projets Silverlight ou WPF:

En ce qui me concerne j’ai eu pour la première fois ce problème après l’installation de la RTM des Silverlight 3 tools, VS2008 SP1 se fermait brutalement à la création d’un projet Silverlight et j’avais comme erreur dans mon observateur d’évènements :

.NET Runtime version 2.0.50727.3074 - Fatal Execution Engine Error (6D375FC0) (80131506)

Le problème vient du designer WPF / Silverlight et un fix est heureusement disponible. Je redonne donc le lien pour faire gagner du temps à tout ceux qui ont eu le même problème que moi avec les Silverlight 3 tools :

https://connect.microsoft.com/VisualStudio/Downloads/DownloadDetails.aspx?DownloadID=16827&wa=wsignin1.0

Pour Vista ou Server 2008 il faut installer le .msu, le .exe est réservé à XP et Server 2003.

 

Hope this help ;)

[Silverlight] ItemsControl binding et alignement de l’ItemTemplate

La classe ItemsControl est utilisée en Silverlight pour développer un contrôle destiné à présenter une collection d’éléments, le contrôle ListBox ou ComboBox par exemple dérive de cette classe en raison de leur logique de fonctionnement. Un contrôle héritant de cette classe sera entre autres capable de gérer le Binding (connection d’une source de données à l’interface graphique) grâce à la propriété ItemSource à laquelle on peut donner notre collection d’objets métier à afficher. Pour définir la représentation graphique de nos objets métier, on utilisera la propriété ItemTemplate qui est de type DataTemplate (classe permettant de définir l’arbre visuelle d’un objet métier). Voici un exemple de code en XAML faisant intervenir une ListBox bindée à une collection d’objets métier avec la définition de l’apparence de chacun de ces derniers avec la propriété ItemTemplate :

   1: <ListBox x:Name="MyListBox"
   2:          ItemsSource="{Binding}">
   3:     <ListBox.ItemTemplate>
   4:         <DataTemplate>
   5:             <Grid HorizontalAlignment="Stretch">
   6:                 <Grid.ColumnDefinitions>
   7:                     <ColumnDefinition Width="*" />
   8:                     <ColumnDefinition Width="25" />
   9:                 </Grid.ColumnDefinitions>
  10:                 <TextBlock VerticalAlignment="Center"
  11:                            Grid.Column="0"
  12:                            Text="{Binding Data}" />
  13:                 <Button Content="X"
  14:                         Grid.Column="1" />
  15:             </Grid>
  16:         </DataTemplate>
  17:     </ListBox.ItemTemplate>
  18: </ListBox>

Et voici le code permettant de fournir une collection d’objets métier en source de notre ListBox :

   1: public partial class MainPage : UserControl
   2: {
   3:     public MainPage()
   4:     {
   5:         InitializeComponent();
   6:         this.Loaded += (sender, args) =>
   7:             {
   8:                 this.MyListBox.DataContext = new List<BusinessObject>
   9:                 {
  10:                     new BusinessObject { Data = "Hello" },
  11:                     new BusinessObject { Data = "Hello dude" },
  12:                     new BusinessObject { Data = "Hello everybody" }
  13:                 };
  14:             };
  15:     }
  16: }
  17:  
  18: public class BusinessObject
  19: {
  20:     public string  Data { get; set; }
  21: }

Le rendu est le suivant :

Untitled

Ici nous avons une petite surprise, le rendu est bien celui que l’on attendait excepté que le DataTemplate généré pour chaque élément de la source de données ne prend pas en compte l’alignement que nous avons spécifié. En effet en ItemTemplate de notre ListBox, nous avons définit une Grid en tant qu’élément racine en fixant sa propriété HorizontalAlignement à Stretch et ceci n’a visiblement pas été pris en compte.

Ce qui s’est passé réellement est que cet alignement a bel et bien été pris en compte, néanmoins la taille de la Grid est calculée avant d'être positionnée dans la ListBox, cette Grid n’a donc pas de contrôle parent sur lequel le moteur de rendu peut se baser pour effectuer l’alignement comme on le souhaiterai, ainsi la Grid est alignée horizontalement par rapport à rien, le moteur de rendu lui donne donc la largeur minimum suffisante.

Pour remédier à cela, nous allons créer une Attached property (cf. mes posts précédent) et utiliser une nouvelle fonctionnalité de Silverlight 3 à savoir la possibilité de binder un élément graphique à un autre. La technique va consister simplement à binder l’élément mis en DataTemplate à l’ItemsControl (ici la ListBox) qui lui appartient. Ainsi dans le code de l’attached property, il suffira d’adapter la largeur de cet élément à la largeur de notre ItemsControl. Voici  le code de l’attached property :

   1: public static ItemsControl GetItemsControl(DependencyObject obj)
   2: {
   3:     return (ItemsControl)obj.GetValue(ItemsControlProperty);
   4: }
   5:  
   6: public static void SetItemsControl(DependencyObject obj, ItemsControl value)
   7: {
   8:     obj.SetValue(ItemsControlProperty, value);
   9: }
  10:  
  11: public static readonly DependencyProperty ItemsControlProperty =
  12:     DependencyProperty.RegisterAttached("ItemsControl",
  13:                                         typeof(ItemsControl),
  14:                                         typeof(MainPage),
  15:                                         new PropertyMetadata(new PropertyChangedCallback((sender, args) =>
  16:                                         {
  17:                                             ItemsControl itemsControl = args.NewValue as ItemsControl;
  18:                                             FrameworkElement dataTemplateElement = sender as FrameworkElement;
  19:                                            
  20:                                             if (itemsControl != null && dataTemplateElement != null)
  21:                                             {
  22:                                                 int scrollViewerPadding = 8;
  23:                                                 itemsControl.SizeChanged += (subSender, subArgs) =>
  24:                                                 {
  25:                                                     dataTemplateElement.Width = itemsControl.ActualWidth -
  26:                                                                                 itemsControl.Padding.Left -
  27:                                                                                 itemsControl.Padding.Right -
  28:                                                                                 scrollViewerPadding;
  29:                                                 };
  30:                                             }
  31:                                         })));

Et voici le code XAML modifié utilisant cette attached property :

   1: <ListBox x:Name="MyListBox"
   2:          ItemsSource="{Binding}">
   3:     <ListBox.ItemTemplate>
   4:         <DataTemplate>
   5:             <Grid HorizontalAlignment="Stretch"
   6:                   local:MainPage.ItemsControl="{Binding ElementName=MyListBox}" >
   7:                 <Grid.ColumnDefinitions>
   8:                     <ColumnDefinition Width="*" />
   9:                     <ColumnDefinition Width="25" />
  10:                 </Grid.ColumnDefinitions>
  11:                 <TextBlock VerticalAlignment="Center"
  12:                    Grid.Column="0"
  13:                    Text="{Binding Path=Data}" />
  14:                 <Button Content="X"
  15:                 Grid.Column="1" />
  16:             </Grid>
  17:         </DataTemplate>
  18:     </ListBox.ItemTemplate>
  19: </ListBox>

On a donc binder cette attached property à notre ListBox. Ainsi dans le code de l’attached property on peut récupérer la largeur de la ListBox et ainsi adapter notre ItemTemplate :

Untitled

Enjoy ;)

[Silverlight] Comment effectuer du binding sur une propriété .Net standard ?

Silverlight 2, comme un grand nombre de technologies dans le domaine du développement IHM, supporte le DataBinding, i.e. la possibilité de connecter une source de données à un élément graphique. Pour ce faire, il suffit d’utiliser la classe Binding sur la propriété à laquelle on veut connecter une source de données. Voici un exemple en XAML où la propriété Text d’un TextBlock estbindé” sur la propriété Data (de type string comme la proriété Text) d’un objet métier :

   1: <UserControl x:Class="SilverlightApplication.Page"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:              xmlns:local="clr-namespace:SilverlightApplication">
   5:     <UserControl.Resources>
   6:         <local:BusinessObject Data="Some data"
   7:                               x:Key="bo" />
   8:     </UserControl.Resources>
   9:     <Grid>
  10:         <TextBlock Text="{Binding Data, Source={StaticResource bo}}" />
  11:     </Grid>
  12: </UserControl>

Néanmoins, le binding ne peut s’appliquer que sur des propriétés de type DependancyProperty. C’est un type de propriété spéciale apparu avec le Framework 3.0 qui permet d’étendre les fonctionnalités d’une propriété CLR standard (possibilité de définir une valeur par défaut, d’être notifié lors de la modification de la valeur de cette propriété, etc.). Ainsi, si vous tentez d’effectuer du binding sur une propriété .Net standard, au moment où le XAML va être chargé en mémoire (cf. méthode LoadComponent()), une exception de type XamlParseException sera levée.

Il existe 2 solutions pour résoudre ce problème. La première, simple, consiste a modifier la propriété sur laquelle vous utilisez le binding pour l’a tranformer en DependancyProperty (un snipet VisualStudio “propdp” peut vous aider à accomplir cette tâche). Néanmoins, il peut arriver d’utiliser un contrôle tiers sur lequel vous n’avez pas la main qui contient une propriété CLR sur laquelle vous aimeriez utiliser le binding. La deuxième solution consiste donc à utiliser une AttachedProperty. C’est une DependancyProperty spéciale permettant d’ajouter dynamiquement une propriété à un objet afin d’étendre simplement et proprement ses fonctionnalités. La démarche va donc être de créer une AttachedProperty, de l’utiliser au niveau de la classe contenant la propriété sur laquelle on veut utiliser le binding, et en appliquant le binding non plus sur la propriété standard mais sur cette AttachedProperty. Ensuite il suffit lors de la création de cette AttachedProperty de définir une méthode à appeler lorsque la valeur de l’AttachedProperty est changée et de modifier la valeur de la propriété standard en conséquence.

Voici un exemple avec un UserControl Silverlight dans lequel est défini une propriété standard de type string :

   1: public partial class SilverlightControl : UserControl
   2: {
   3:     public SilverlightControl()
   4:     {
   5:         InitializeComponent();
   6:     }
   7:  
   8:     public string Data { get; set; }
   9: }

Nous voulons maintenant utiliser ce UserControl au sein d’un autre UserControl et binder la propriété Data sur une source de données. Imaginons que ce UserControl est contenu dans dans une assembly sur laquelle nous n’avons pas la main, i.e. on ne peut pas modifier son code. Comme expliquer auparavant, il va falloir passer par une AttachedProperty car la propriété Data est une propriété standard. Nous allons définir cette AttachedProperty au niveau du UserControl parent (ici le UserControl principal : Page) :

   1: public partial class Page : UserControl
   2: {
   3:     public Page()
   4:     {
   5:         InitializeComponent();
   6:     }
   7:  
   8:     public static string GetData(DependencyObject obj)
   9:     {
  10:         return (string)obj.GetValue(DataProperty);
  11:     }
  12:  
  13:     public static void SetData(DependencyObject obj, string value)
  14:     {
  15:         obj.SetValue(DataProperty, value);
  16:     }
  17:  
  18:     public static readonly DependencyProperty DataProperty =
  19:         DependencyProperty.RegisterAttached("Data",
  20:                                             typeof(string),
  21:                                             typeof(Page),
  22:                                             new PropertyMetadata(new PropertyChangedCallback((sender, args) => 
  23:                                             {
  24:                                                 Page parent = sender as Page;
  25:                                                 if (parent != null)
  26:                                                 {
  27:                                                     parent.Loaded += (subSender, subArgs) =>
  28:                                                     {
  29:                                                         parent.UcChild.Data = args.NewValue as string;
  30:                                                     };                                                    
  31:                                                 }
  32:                                             })));
  33: }

Au niveau de la définition de l’AttachedProperty, on a définit une callback (de type PropertyChangedCallback) qui sera appelée lorsque la valeur de l’AttachedProperty sera modifiée. Cette callback va ensuite simplement se contenter d’accéder au UserControl contenant la propriété sur laquelle on voulait appliquer le binding puis changer la valeur de sa propriété avec celle récupérée dans l’EventArg fournit lors du binding.

Côté XAML, il suffit de binder notre source de données à cette AttachedProperty:

   1: <UserControl x:Class="SilverlightApplication.Page"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:              xmlns:local="clr-namespace:SilverlightApplication"
   5:              local:Page.Data="{Binding Data, Source={StaticResource bo}}">
   6:     <Grid>
   7:         <local:SilverlightControl x:Name="UcChild" />
   8:     </Grid>
   9: </UserControl>

La resource bo étant définie de la même façon qu’en début de post mais cette fois ci au niveau des ressources de l’application (fichier App.xaml).

Enjoy !

[Silverlight] Comment gérer le débordement d’un TextBlock ?

Le TextBlock est le contrôle Silverlight fournit en standard pour afficher et manipuler du texte. Il possède de nombreuses propriétés utiles dont la propriété TextWrapping permettant de gérer des retours à la ligne si l’espace alloué pour le texte est insuffisant. Néanmoins il peut arriver que l’on veuille afficher du texte sur une seule ligne et ce quelque soit la taille de ce texte. J’ai eu ce besoin par exemple lors de mon dernier projet Silverlight, je devais afficher le nom de différents livres et pour des contraintes graphiques je devais afficher ces noms sur une seule ligne, le taille des noms étant variable, j’ai décidé de définir une taille maximum avec la propriété MaxWidth, néanmoins certains noms étant plus long que d’autres, il arrivait que le texte soit “coupé” comme vous pouvez le voir ci-dessous :

screen1

J’ai donc essayé de trouver une solution plus élégante, lorsque le texte à afficher est trop long par rapport à la taille qui lui est alloué, une solution serait d’afficher “…” au bout du TextBlock pour signifier que l’intégralité du texte n’est pas entièrement affiché. On veut donc étendre les fonctionnalités du contrôle TextBlock, pour ce faire on pourrait penser à hériter de la classe TextBlock et créer une nouvelle propriété dédiée à cette fonctionnalité. Ceci est impossible du fait que cette classe est marqué comme sealed (interdiction d’en hériter), ce qui est une bonne chose, en effet il serait un peu lourd de créer une nouvelle classe héritant du contrôle TextBlock pour simplement ajouter une fonctionnalité.

 

Attached Property

Nous allons plutôt utiliser une Attached Property, c’est un concept lié au language XAML. Une Attached Property est une sorte de propriété définit dynamiquement sur un objet. Ainsi cela permet d’ajouter dynamiquement une propriété à cet objet tout en définissant cette propriété où bon nous semble. Cette propriété sera ensuite utilisable directement sur les objets pour lesquels la propriété a été faite. Cela permet d’étendre simplement les fonctionnalités d’une classe. Ce type de propriété se définit de façon quasi similaire à une Dependancy Property, c’est à dire que, indépendamment de la définition de la propriété, une méthode est dédiée à la récupération de la valeur de la propriété et une autre méthode est dédiée à la modification de cette valeur. Ceci est dû au fait que cette valeur n’est pas stockée dans la classe qui définit la Dependancy Property mais dans un repository spécial dédié à la gestion des Dependancy Properties et des Attached Properties et contrôlé par le moteur de Silverlight. Pour plus d’information sur ce type de propriété, je vous redirige vers ce post et ce post de Jesse Liberty.

 

TextBlockOverflowProperty

Nous allons donc utiliser ce type de propriété et faire en sorte d’être notifié lorsque la valeur de cette dernière est modifiée. Cette propriété sera de type bool pour activer ou désactiver simplement la gestion du débordement sur un contrôle TextBlock.

Ainsi lorsque cette propriété est passée à true, la technique va consister à récupérer le parent du TextBlock et s’abonner à l’évènement SizeChanged de ce parent. Chaque fois que cet évènement sera appelé, on comparera la taille du parent avec la taille du texte et afficher des “…” au bon endroit si besoin est. On aura également un dictionnaire qui nous permettra d’associer à un TextBlock donné le handler correspondant à l’évènement SizeChanged de son parent, et ce afin de pouvoir accéder facilement à ce handler si la propriété est passée à false pour se désabonner de l’évènement SizeChanged. Voici le code de la classe Helper contenant la définition de la propriété ainsi que des différentes méthodes utilisées par cette dernière :

   1: public static class TextBlockHelper
   2: {
   3:     private static Dictionary<TextBlock, SizeChangedEventHandler> s_handlers =
   4:         new Dictionary<TextBlock, SizeChangedEventHandler>();
   5:  
   6:     public static bool GetTextBlockOverflow(DependencyObject obj)
   7:     {
   8:         return (bool)obj.GetValue(TextBlockOverflowProperty);
   9:     }
  10:  
  11:     public static void SetTextBlockOverflow(DependencyObject obj, bool value)
  12:     {
  13:         obj.SetValue(TextBlockOverflowProperty, value);
  14:     }
  15:  
  16:     public static readonly DependencyProperty TextBlockOverflowProperty =
  17:         DependencyProperty.RegisterAttached(
  18:             "TextBlockOverflow",
  19:             typeof(bool),
  20:             typeof(TextBlockHelper),
  21:             new PropertyMetadata(new PropertyChangedCallback((sender, args) =>
  22:             {
  23:                 TextBlock tb = sender as TextBlock;
  24:                 if (tb != null)
  25:                 {
  26:                     FrameworkElement parent = tb.Parent as FrameworkElement;
  27:                     if (parent == null)
  28:                     {
  29:                         tb.Loaded += (loadedSender, loadedArgs) =>
  30:                         {
  31:                             parent = tb.Parent as FrameworkElement;
  32:                             if (parent != null)
  33:                             {
  34:                                 tb.Tag = tb.Text;
  35:                                 ManageTextBlockOverflow(tb, parent,
  36:                                                         (bool)args.NewValue);
  37:                             }
  38:                         };
  39:                     }
  40:  
  41:                     if (parent != null)
  42:                     {
  43:                         ManageTextBlockOverflow(tb, parent,
  44:                                                 (bool)args.NewValue);
  45:                     }
  46:                 };
  47:             })));
  48:  
  49:     private static void ManageTextBlockOverflow(TextBlock tb,
  50:                                                 FrameworkElement parent,
  51:                                                 bool state)
  52:     {
  53:         tb.TextWrapping = TextWrapping.NoWrap;
  54:         double originalActualWidth = tb.ActualWidth;
  55:         if (state)
  56:         {
  57:             s_handlers[tb] = new SizeChangedEventHandler((sizeChangedSender,
  58:                                                           sizeChangedArgs) =>
  59:             {
  60:                 string originalText = tb.Tag as string;
  61:                 double parentWidth = double.IsNaN(parent.Width) ?
  62:                                      parent.ActualWidth : parent.Width;
  63:                 parentWidth -= tb.Margin.Left + tb.Margin.Right;
  64:                 Thickness padding = new Thickness();
  65:                 if (parent is Border)
  66:                 {
  67:                     padding = (parent as Border).Padding;
  68:                 }
  69:                 else if (parent is ContentControl)
  70:                 {
  71:                     padding = (parent as ContentControl).Padding;
  72:                 }
  73:  
  74:                 parentWidth -= padding.Left + padding.Right;
  75:                 if (originalActualWidth > parentWidth)
  76:                 {
  77:                     tb.Text = originalText.Substring(0, originalText.Length - 3)
  78:                               + "...";
  79:                     while (tb.ActualWidth > parentWidth &&
  80:                            tb.Text.Length > 4)
  81:                     {
  82:                         tb.Text = tb.Text.Substring(0, tb.Text.Length - 3 - 1)
  83:                                   + "...";
  84:                     }
  85:                 }
  86:                 else
  87:                 {
  88:                     tb.Text = originalText;
  89:                 }
  90:             });
  91:             parent.SizeChanged += s_handlers[tb];
  92:             s_handlers[tb].Invoke(tb, null);
  93:         }
  94:         else
  95:         {
  96:             parent.SizeChanged -= s_handlers[tb];
  97:             s_handlers.Remove(tb);
  98:             tb.Text = tb.Tag as string;
  99:         }
 100:     }
 101: }

La propriété pourra ensuite être utilisée ainsi (en ayant bien sûr définit préalablement un namespace xml pointant vers le namespace CLR dans lequel est défini notre classe Helper) :

   1: <TextBlock local:TextBlockHelper.TextBlockOverflow="true"
   2:            Text="Hello World" />

Enjoy :)

[Silverlight] Problème avec l’attribut NeutralResourcesLanguage

Ayant l’habitude d’uiliser FxCop dans la plupart de mes projets, je l’ai donc activé récemment sur l’un de mes projets Silverlight. Lors de la compilation, parmi les avertissements, j’ai reçu le suivant :

CA1824 : Microsoft.Performance :

Because assembly 'MonAssembly.dll' contains a ResX-based resource file, mark it with the NeutralResourcesLanguage attribute, specifying the language of the resources within the assembly. This could improve lookup performance the first time a resource is retrieved.

Ici, FxCop, dans un soucis d’optimisation, nous conseille simplement d’ajouter l’attribut NeutralResourceLanguage dans le fichier AssemblyInfo.cs (ou AssemblyInfo.vb) en lui donnant le nom de la culture à utiliser et ce afin d’informer le ResourceManager qu’il doit afficher les resources de la culture neutre de l’assembly. Ainsi lorsqu’il recherche une resource dans la même culture que la culture neutre, le ResourceManager utilise directement les resources de l’assembly au lieu de les chercher dans une assembly satellite. J’ai donc ajouter cet attribut. Néanmoins lorsque j’ai voulu lancer ma solution, aucun des contrôles graphiques de mon application ne s’est affiché, je n’avais qu’une page blanche tout simplement mais sans aucune erreur.

Après quelques recherches, il s’est avéré que j’avais commis une erreur au niveau de l’argument que prend le constructeur de l’attribut NeutralResourceLanguage. En effet j’avais donné comme nom de culture “en-EN” au lieu de “en-US”. J’ai donc essayé de comprendre en quoi cette erreur pouvait empêcher le chargement des éléments de mon XAML en mémoire. En Silverlight comme en WPF, pour chaque UserControl est généré un fichier d’extension “.g.cs” (ou “.g.vb”) contenant le code nécessaire au chargement des différents éléments XAML et notamment ces lignes :

   1: System.Windows.Application.LoadComponent(this, new System.Uri("/NomAssembly;component/NomUserControl.xaml", System.UriKind.Relative));
   2: this.UnMembre = ((TypeDuControl)(this.FindName("NomElementXAML")));

Ce code permet de charger le XAML en mémoire puis d’y récupérer un élément en particulier. J’ai donc mis un point d’arrêt au niveau de la ligne 2 et il s’est avéré que la méthode FindName renvoyait la valeur null. Le problème se produit donc lors de l’appel à la méthode LoadComponent. Après un rapide coup d’oeil avec Reflector sur le code de cette méthode, je suis tombé sur cette ligne :

   1: UnmanagedMemoryStream resourceForType = ResourceManagerWrapper.GetResourceForType(component.GetType(), resourceLocator) as UnmanagedMemoryStream;

Le ResourceManager est donc utilisé par cette méthode LoadComponent. Ainsi la valeur de la culture neutre a une incidence sur la chargement du XAML d’un UserControl.

Faites donc attention à passer un nom de culture valide au constructeur de l’attribut NeutralResourceLanguage si vous êtes amené à l’utiliser car dans le cas contraire, aucun élément graphique ne sera chargé et aucune erreur ne sera affichée :(.

Merci à Simon pour ses lumières ;)

Enjoy

[Silverlight] TreeView, HierarchicalDataTemplate et erreur AG_E_PARSER_BAD_TYPE

La nouvelle version étant accessible depuis Mars 2009, le Silverlight Toolkit, développé par Microsoft, contient un ensemble de contrôles très utiles pour le développement d'applications Silverlight. Un de ces contrôles, le "TreeView", permet d'afficher des données sous forme hiérarchique. De nombreux posts sur le net expose les différentes facettes de ce contrôles et notamment comment utiliser ce contrôle avec le databinding. Voici un exemple de code en xaml mettant un oeuvre un TreeView connecté à une source de données :

   1: <UserControl x:Class="SilverlightApplication.Page"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:              xmlns:slt="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit">
   5:      <Grid>
   6:          <slt:TreeView ItemsSource="{Binding Items}">
   7:              <slt:TreeView.ItemTemplate>
   8:                  <slt:HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
   9:                      <TextBlock Text="{Binding Name}" />
  10:                  </slt:HierarchicalDataTemplate>
  11:              </slt:TreeView.ItemTemplate>
  12:          </slt:TreeView>
  13:      </Grid>
  14: </UserControl>

On peut voir que ce code utilise un "HierarchicalDataTemplate" qui est un élément semblable au DataTemplate mais qui est en plus capable de naviguer récursivement dans un arbre.

Néanmoins si vous testez le code ci-dessus avec la dernière version du toolkit, vous devriez tombez face à l'exception suivante lors de l'appel à la méthode "InitializeComponent" : AG_E_PARSER_BAD_TYPE.

Ceci est tout simplement dû au fait que dans la dernière version du toolkit, le contrôle "HierarchicalDataTemplate" a été déplacé du namespace "System.Windows.Controls" de l'assembly "System.Windows.Controls.Toolkit" vers le namespace "System.Windows" de la même assembly. Il suffit donc de déclarer un nouveau namespace XML correspondant au namespace CLR "System.Windows" de l'assembly "System.Windows.Controls.Toolkit" pour accéder correctement à l'élément "HierarchicalDataTemplate" :

   1: <UserControl x:Class="SilverlightApplication.Page"
   2:              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:              xmlns:slt="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
   5:              xmlns:hdt="clr-namespace:System.Windows;assembly=System.Windows.Controls.Toolkit">
   6:     <Grid>
   7:         <slt:TreeView ItemsSource="{Binding Items}">
   8:             <slt:TreeView.ItemTemplate>
   9:                 <hdt:HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
  10:                     <TextBlock Text="{Binding Name}" />
  11:                 </hdt:HierarchicalDataTemplate>
  12:             </slt:TreeView.ItemTemplate>
  13:         </slt:TreeView>
  14:     </Grid>
  15: </UserControl>

Enjoy !

[Silverlight] Problème de références avec les templates du Silverlight unit test framework

Présenté lors du MIX 2008 à Las Vegas par Scott Guthrie, le Silverlight unit test framework est une solution flexible et puissante de test pour des composants Silverlight. Afin de faciliter la mise en place des tests dans vos applications Silverlight, des templates de projets ont été mis au point. Ces templates permettent de créer un projet avec un exemple de classe de tests ainsi que les références pour utiliser le framework.

Néanmoins lors de ma première utilisation de ce template, il s'est avéré que les références vers le framework de test étaient incorrectes :

Sans titre 

Si on regarde les propriétés de la référence "Microsoft.Silverlight.Testing" :

Sans titre 

On voit que la valeur de l'attribut "Specific Version" est à "True", c'est la valeur par défaut. Ce booléen indique comment retrouver le nom complet de l'assembly, i.e. est-ce que la version, la culture et le jeton de clé publique doivent être inclus ?

Continuons notre investigation en concentrant notre attention sur le fichier de projet (extension ".csproj") du template. A cet emplacement :

%userprofile%\Documents\Visual Studio 2008\Templates\ProjectTemplates\Visual C#

doit normalement se trouver un fichier de type "zip" nommé "SilverlightTestProject_CSharp.zip" qui contient les fichiers du template pour la version C# et dont notamment le fichier de projet qui sera utilisé lors de la création d'un nouveau projet de test :

Sans titre 

Si on ouvre ce fichier directement à partir de l'archive et que l'on se rend au niveau du noeud "ItemGroup" qui contient les informations relatives au différentes références du projets, on retrouve les 2 références inhérentes à un projet de test Silverlight :

  • Microsoft.Silverlight.Testing :

<Reference Include="Microsoft.Silverlight.Testing, Version=2.0.20930.1042, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />

  • Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight :

<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight, Version=2.0.20930.1042, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />

On peut noter l'existence d'un attribut "Version" pour chacune des deux références. Cet attribut correspond à la version de l'assembly du framework de test qui sera utilisée dans le projet. Or si l'on consulte les propriétés de ces 2 assemblies en nous rendant à leur emplacement (chez moi cela revient à : C:\Program Files (x86)\Microsoft SDKs\Silverlight\v2.0\Libraries\Client) et en faisant clic droit -> Propriétés -> Détails :

Sans titre

Sans titre

On se rend compte que les numéros de version ne correspondent pas. Pour résumé, le template du projet de test utilise une référence vers l'assembly nommé "Microsoft.Silverlight.Testing" de version 2.0.20930.1042 ainsi que l'assembly "Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight" de version 2.0.20930.1042. Le problème est que ces assemblies n'existent pas sur la machine (avec la dernière version du framework), plus précisément les versions requises n'existent pas.

Ainsi pour corriger le problème, il suffit de mettre le bon numéro de version pour chaque assembly dans le fichier "SilverlightTestProject.csproj" ce qui donne :

  • Microsoft.Silverlight.Testing :

<Reference Include="Microsoft.Silverlight.Testing, Version=2.0.21103.1925, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />

  • Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight :

<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTesting.Silverlight, Version=2.0.21024.1838, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />

 

Un petit refresh sur la solution et cette fois-ci, on voit que nos références sont correctes ! :

Sans titre

Enjoy :)

[VirtualEarth] Image au format de pixel indexé et GDI+

Fin septembre 2008 est apparue la première version du web service VirtualEarth. Ce dernier fournit différents types de services dont le service d'imagerie. Le service d'imagerie permet de récupérer des informations par rapport aux images utilisées dans le contrôle AJAX VirtualEarth comme par exemple l'url des images. Il est également possible de demander au service de positionner des punaises sur une image avant que ce dernier nous renvoie cette même image. Cette fonctionnalité est intéressante et il n'est malheuresement pas encore possible de demander au service de nous positionner d'autres types d'élements graphiques, je pense notamment à des cercles, des polygones, etc. Pour ce faire, il faut donc nous même dessiner sur l'image les éléments souhaités en utilisant GDI+ et sa fameuse classe Graphics. En effet cette dernière possède une méthode statique FromImage prenant en paramètre un objet de type Image et retournant un objet de type Graphics permettant de tracer différents types d'éléments sur l'image.

Si on consulte la page msdn de la méthode FromImage (http://msdn.microsoft.com/en-us/library/system.drawing.graphics.fromimage.aspx) on s'aperçoit que cette dernière ne supporte par les images au format de pixel indexé, i.e. les images où les couleurs sont gérées via une tablette de couleurs indexée, ainsi que quelques autres types d'image. Or il m'est arrivé plusieurs fois lors du développement d'une application utilisant le service d'imagerie VirtualEarth de récupérer une image ayant un format non pris en charge par cette méthode et de tomber face à l'exception : "A Graphics object cannot be created from an image that has an indexed pixel format". Dans cette situation, la seule solution consiste a créer une nouvelle image avec un format pris en charge par la méthode FromImage et de copier l'ancienne image dans cette nouvelle image. Voici la méthode que j'ai utilisé :

   1: private Image CheckImagePixelFormat(Image image)
   2: {
   3:      if (image.PixelFormat == PixelFormat.Format1bppIndexed ||
   4:          image.PixelFormat == PixelFormat.Format4bppIndexed ||
   5:          image.PixelFormat == PixelFormat.Format8bppIndexed ||
   6:          image.PixelFormat == PixelFormat.DontCare ||
   7:          image.PixelFormat == PixelFormat.Undefined ||
   8:          image.PixelFormat == PixelFormat.Format16bppArgb1555 ||
   9:          image.PixelFormat == PixelFormat.Format16bppGrayScale)
  10:      {
  11:          Bitmap correcter = new Bitmap(image.Width, image.Height);
  12:          Graphics g = Graphics.FromImage(correcter);
  13:          g.DrawImage(image, 0, 0);
  14:          g.Dispose();
  15:          return correcter;
  16:      }
  17:  
  18:      return image;
  19: }

Could help... ;)

[Silverlight] Multi-Files Downloader

Dans mon précédent post, je proposais une technique permettant de mettre en place de façon propre et générique le téléchargement de fichiers à partir d'une application Silverlight. Néanmoins l'architecture proposée ne permettait pas à un utilisateur le téléchargement de plusieurs fichiers à la fois. Ainsi j'ai modifié mon code pour ajouter cette fonctionnalité, l'idée étant de générer une archive côté serveur puis renvoyer cette archive au client lorsque l'utilisateur sélectionne plusieurs fichiers .

Pour ce faire, j'ai dû tout d'abord modifier le contrôle Silverlight afin qu'il puisse gérer l'envoie de plusieurs ID de fichiers la fois :

   1: /// <summary>
   2: /// Provides file downloader datas and operations
   3: /// </summary>
   4: public class FileDownloader : Button
   5: {
   6:     #region Constants
   7:     /// <summary>
   8:     /// The default parameter name
   9:     /// </summary>
  10:     private const string DEFAULT_PARAM_NAME = "FileId";
  11:     #endregion
  12:  
  13:     #region Members
  14:     /// <summary>
  15:     /// The number of file id
  16:     /// </summary>
  17:     private int m_FileIdCount = 0;
  18:     #endregion
  19:  
  20:     #region Properties
  21:     /// <summary>
  22:     /// Gets or sets the gate URL.
  23:     /// </summary>
  24:     /// <value>The gate URL.</value>
  25:     public Uri GateUrl { get; set; }
  26:  
  27:     /// <summary>
  28:     /// Gets or sets the file id list.
  29:     /// </summary>
  30:     /// <value>The file id list.</value>
  31:     public List<int> FileIdList { get; set; }
  32:     #endregion
  33:  
  34:     #region Constructor
  35:     /// <summary>
  36:     /// Initializes a new instance of the <see cref="FileDownloader"/> class.
  37:     /// </summary>
  38:     public FileDownloader()
  39:     {
  40:         this.FileIdList = new List<int>();
  41:         this.Click += new RoutedEventHandler(onClick);
  42:     }
  43:     #endregion
  44:  
  45:     #region Callbacks
  46:     /// <summary>
  47:     /// Ons the click.
  48:     /// </summary>
  49:     /// <param name="sender">The sender.</param>
  50:     /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
  51:     private void onClick(object sender, System.Windows.RoutedEventArgs e)
  52:     {
  53:         StringBuilder stringBuilder = new StringBuilder();
  54:         stringBuilder.Append(this.GateUrl.AbsoluteUri + "?");
  55:         foreach (int fileId in this.FileIdList)
  56:         {
  57:             stringBuilder.Append(String.Format(CultureInfo.InvariantCulture,
  58:                                                "{0}{1}={2}&",
  59:                                                DEFAULT_PARAM_NAME,
  60:                                                this.m_FileIdCount++,
  61:                                                fileId));
  62:         }
  63:  
  64:         HtmlPage.PopupWindow(new Uri(stringBuilder.Remove(stringBuilder.Length - 1, 1).ToString()),
  65:                              null, null);
  66:     }
  67:     #endregion
  68: }

On peut constater que l'on n'hérite plus du contrôle HyperlinkButton mais du contrôle Button et ceci afin d'avoir plus de flexibilité pour gérer plus facilement la sélection de plusieurs fichiers.

Il a fallu ensuite modifier le code de la page aspx appelée lors du lancement du téléchargement côté client :

   1: /// <summary>
   2: /// The server side logic of the file downloader 
   3: /// </summary>
   4: public partial class Gate : Page
   5: {
   6:     #region Constants
   7:     /// <summary>
   8:     /// The parameter name
   9:     /// </summary>
  10:     private const string PARAM_BASE_NAME = "FileId";
  11:  
  12:     /// <summary>
  13:     /// The package name
  14:     /// </summary>
  15:     private const string PACKAGE_NAME = "package.zip";
  16:     #endregion
  17:  
  18:     #region Members
  19:     /// <summary>
  20:     /// The data access object
  21:     /// </summary>
  22:     private IDataAccessObject m_DataAccessObject;
  23:  
  24:     /// <summary>
  25:     /// The list of all file path
  26:     /// </summary>
  27:     private List<string> m_ListFilePath = new List<string>();
  28:     #endregion
  29:  
  30:     /// <summary>
  31:     /// Handles the Load event of the Page control.
  32:     /// </summary>
  33:     /// <param name="sender">The source of the event.</param>
  34:     /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  35:     protected void Page_Load(object sender, EventArgs e)
  36:     {
  37:         this.m_DataAccessObject = DataAccessLayer.CreateDataAccessObjectInstance(typeof(DataAccessObject));
  38:         for (int i = 0; i < Request.Params.Count; i++)
  39:         {
  40:             if (Request.Params.GetKey(i).Contains(PARAM_BASE_NAME))
  41:             {
  42:                 string filePath = this.getPathFromParams(Request.Params[Request.Params.GetKey(i)]);
  43:                 if (filePath != null)
  44:                 {
  45:                     this.m_ListFilePath.Add(filePath);
  46:                 }
  47:             }
  48:         }
  49:  
  50:         if (this.m_ListFilePath.Count == 1)
  51:         {
  52:             Response.Redirect(this.m_ListFilePath[0]);
  53:         }
  54:         else
  55:         {
  56:             Response.Redirect(this.generateArchive());
  57:         }
  58:     }
  59:  
  60:     #region Private Methods
  61:     /// <summary>
  62:     /// Gets the path from params.
  63:     /// </summary>
  64:     /// <param name="param">The param.</param>
  65:     /// <returns></returns>
  66:     private string getPathFromParams(string param)
  67:     {
  68:         int fileId;
  69:         if (!int.TryParse(param, out fileId))
  70:         {
  71:             return (param);
  72:         }
  73:         else if (this.m_DataAccessObject == null)
  74:         {
  75:             this.LabelError.Text = "[ FileDownloader error ] : Can not create data access object.";
  76:             return (null);
  77:         }
  78:         else
  79:         {
  80:             return (this.m_DataAccessObject.GetFilePath(fileId));
  81:         }
  82:     }
  83:  
  84:     /// <summary>
  85:     /// Generates the archive.
  86:     /// </summary>
  87:     /// <returns></returns>
  88:     private string generateArchive()
  89:     {
  90:         string packagePath = String.Format(CultureInfo.InvariantCulture, "{0}{1}",
  91:                                            AppDomain.CurrentDomain.BaseDirectory, PACKAGE_NAME);
  92:         using (Package package = Package.Open(packagePath, FileMode.Create))
  93:         {
  94:             Uri partUriDocument;
  95:             PackagePart packagePartDocument;
  96:             foreach (string filePath in this.m_ListFilePath)
  97:             {
  98:                 partUriDocument =
  99:                     PackUriHelper.CreatePartUri(new Uri("/" + filePath, UriKind.Relative));
 100:                 packagePartDocument =
 101:                     package.CreatePart(partUriDocument, MediaTypeNames.Application.Octet, CompressionOption.Maximum);
 102:                 using (FileStream fileStream = new FileStream(AppDomain.CurrentDomain.BaseDirectory + filePath,
 103:                                                               FileMode.Open, FileAccess.Read))
 104:                 {
 105:                     this.copyStream(fileStream, packagePartDocument.GetStream());
 106:                 }
 107:             }
 108:         }
 109:         return (PACKAGE_NAME);
 110:     }
 111:  
 112:     /// <summary>
 113:     /// Copies the stream.
 114:     /// </summary>
 115:     /// <param name="source">The source.</param>
 116:     /// <param name="target">The target.</param>
 117:     private void copyStream(Stream source, Stream target)
 118:     {
 119:         const int bufSize = 0x1000;
 120:         byte[] buf = new byte[bufSize];
 121:         int bytesRead = 0;
 122:         while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)
 123:         {
 124:             target.Write(buf, 0, bytesRead);
 125:         }
 126:     }
 127:     #endregion
 128: }

Ainsi, lorsque la page est chargée, on récupère le ou les id de fichiers. Si il n'y a qu'un seul id on utilise la technique habituel pour renvoyé le fichier. Dans le cas contraire nous allons générer une archive contenant tous les fichiers correspondant aux id récupérées.

Capturer

Capturer 

Vous pouvez télécharger en pièce jointe une solution exemple mettant en oeuvre le téléchargement de plusieurs fichiers à partir d'une application silverlight. Dans cet exemple la source de données est un fichier xml.

Enjoy :)

[Silverlight] File Downloader

Comme chacun sait, Microsoft a mis un point d'honneur à la sécurité dans Silverlight, isolant chaque application dans un univers confiné, "sandbox", coupé du monde extérieur. Ainsi à l'heure actuelle, il n'est pas possible pour une application Silverlight d'accéder aux périphériques communément utilisés (webcam, etc.) ou au système de fichier de la machine sur laquelle elle s'execute si ce n'est une petite partie lui étant réservée appelée "Isolated Storage".

Dans ces conditions, il est assez aisé de deviner les problématiques de sécurité tournant autour du téléchargement de fichiers et de constater ainsi  l'absence d'un contrôle offrant cette fonctionnalité (contrôle qui devrait exister dans une futur version de Silverlight). Je me suis donc penché sur le problème et je vous expose ici ma solution.

Puisque nous n'avons pas accès au système de fichier, la seule solution s'offrant à nous, pour permettre à un utilisateur de télécharger un fichier où bon lui semble dans son espace de travail, est d'utiliser les fonctionnalités du navigateur. En effet chaque navigateur propose un "SaveFileDialog" pour permettre d'enregistrer du contenu sur un système de fichier. C'est d'ailleur le comportement par défaut lorsque nous fournissons une url correspondant à du contenu non représentable par le navigateur (archive, certains formats d'images, etc.), une boite de dialogue apparaît pour permettre de sélectionner une destination d'enregistrement. C'est sur ce point que nous allons pouvoir jouer : mettre en place une architecture client-serveur permettant de générer une url correspondant à l'emplacement exacte du fichier à télécharger et l'envoyer au navigateur pour faire apparaître sa boite de dialoque de sauvegarde de fichier.

Tout d'abord, prévoyons deux cas. En effet Il est possible, selon le design de l'application, d'avoir accès directement, côté client, à l'url du fichier à télécharger ; auquel cas il suffit d'envoyer directement cette url au navigateur et le tour est joué. Mais il est également possible du côté client, comme c'est souvent le cas, d'avoir accès simplement à une id car cela suffit pour effectuer toutes les tâches relatives au fichier. Le développeur pourrait en effet modifier son code pour stocker l'url du fichier côté client mais rendons notre architecture plus générique afin que l'id du fichier soit suffisante côté client pour pouvoir lancer un téléchargement. Dans ce dernier cas, il faudra envoyer cette id au serveur et résoudre le chemin du fichier en conséquence.

 

Côté serveur

Dans le cas où l'id du fichier est la seule information existante côté client, la technique pour lancer le téléchargement serait d'ouvrir une nouvelle fenêtre en lui donnant l'url d'un fichier aspx et en fournissant à ce dernier l'id du fichier en paramètre. Ensuite nous pouvons utiliser une couche d'accès aux données ("DataAccessLayer") pour récupérer l'url du fichier associée à l'id récupérée en paramètre de la page. Cette couche utilisera un "DataAccessObject" qui effectuera réellement la résolution du chemin du fichier. Nous allons créer une interface simple qui sera utilisée par notre DataAccessLayer et qui permettra de pouvoir utiliser différents DataAccesObjects, chaque DataAccessObject ayant une technique de résolution propre à la source de donnée (un DataAccesObject pour la résolution à partir d'une base de donnée, un autre pour la résolution à partir d'un fichier XML,etc.).

Voici le code de l'interface qui sera utilisée :

   1: public interface IDataAccessObject  
   2: {  
   3:    string GetFilePath(int fileId); 
   4: }

Et voici un exemple de DataAccessObject qui ira récupérer le chemin d'un fichier à partir d'une base de donnée :

   1: public class BddDataAccessObject : IDataAccessObject  
   2: {  
   3:    public string GetFilePath(int fileId)  
   4:    {  
   5:      using (TestDataContext dataContext =
   6:             new TestDataContext())  
   7:      {  
   8:        var query = from p in dataContext.Files  
   9:                    where p.Id == fileId  
  10:                    select String.Format("{0}/{1}",
  11:                                          p.Path, p.Name);  
  12:        return (query.FirstOrDefault());  
  13:      }  
  14:    }  
  15: }

Notre DataAccessLayer aura au final pour seule fonction de permettre la création de différents types de DataAccessObject en ayant préalablement vérifié par réflection que le type du DataAccessObject voulu implémente bien l'interface précédemment décrite :

   1: public class DataAccessLayer  
   2: {
   3:    private static DataAccessLayer s_Instance;
   4:    private static object s_InstanceIdentifier = new object();
   5:  
   6:    public static DataAccessLayer Instance  
   7:    {  
   8:      get  
   9:      {  
  10:        lock (s_InstanceIdentifier)  
  11:        {  
  12:          if (s_Instance == null)  
  13:          {  
  14:            s_Instance = new DataAccessLayer();  
  15:          }  
  16:  
  17:          return (s_Instance);  
  18:        }  
  19:      }  
  20:    }  
  21:  
  22:    private DataAccessLayer() { }  
  23:  
  24:    public IDataAccessObject CreateDataAccessObjectInstance(Type dataAccessObject)  
  25:    {  
  26:      if (dataAccessObject.GetInterfaces().FirstOrDefault(type => type == typeof(IDataAccessObject)) != null)  
  27:        return (Activator.CreateInstance(dataAccessObject)
  28:                as IDataAccessObject);  
  29:      return (null);  
  30:    }  
  31: } 

Et voici le contenu de notre fichier aspx qui va utiliser notre DataAccessLayer pour créer une instance d'un DataAccessObject spécifique et ensuite utiliser ce dernier pour aller récupérer le chemin du fichier à télécharger et faire une simple redirection vers ce chemin :

   1: public partial class Gate : System.Web.UI.Page  
   2: {
   3:    private const string PARAM_NAME = "FileID";  
   4:  
   5:    protected void Page_Load(object sender, EventArgs e)  
   6:    {  
   7:      IDataAccessObject dataAccessObject =
   8:        DataAccessLayer.Instance.CreateDataAccessObjectInstance(typeof(BddDataAccessObject));  
   9:  
  10:      if (Request.Params[PARAM_NAME] != null && dataAccessObject != null)  
  11:      {  
  12:        int fileId;  
  13:        if (int.TryParse(Request.Params[PARAM_NAME], out fileId))  
  14:        {  
  15:          string filePath = dataAccessObject.GetFilePath(fileId);  
  16:          if (!String.IsNullOrEmpty(filePath))  
  17:            Response.Redirect(filePath);  
  18:        }  
  19:      }  
  20:    }  
  21: }

Le contrôle

Silverlight fournit un contrôle jouant le rôle de lien hypertexte, le fameux "HyperlinkButton". Il peut être intéressant d'hériter de ce contrôle, en effet une fois l'url du fichier récupéré et enregistré, un clic sur le bouton suffira à ouvrir la boite de dialogue :

   1: public class FileDownloader : HyperlinkButton  
   2: {  
   3:    private const string DEFAULT_GATE_NAME = "Gate.aspx";  
   4:    private const string DEFAULT_GATE_PARAM_NAME = "FileID";  
   5:  
   6:    private int m_FileID;  
   7:  
   8:    public string GateName { get; set; }  
   9:  
  10:    public string GateParamName { get; set; }  
  11:  
  12:    public int FileId  
  13:    {  
  14:      get { return (this.m_FileID); }  
  15:      set  
  16:      {  
  17:        this.m_FileID = value; 
  18:        this.NavigateUri = 
  19:          new UriBuilder(HtmlPage.Document.DocumentUri.Scheme,  
  20:                         HtmlPage.Document.DocumentUri.Host,  
  21:                         HtmlPage.Document.DocumentUri.Port,  
  22:                         this.GateName,  
  23:                         String.Format("?{0}={1}",
  24:                                       this.GateParamName,  
  25:                                       value.ToString())).Uri;  
  26:       }  
  27:    }  
  28:  
  29:    public string FileUrl  
  30:    {  
  31:      get { return(this.NavigateUri.AbsoluteUri); }  
  32:      set { this.NavigateUri = new Uri(value); }  
  33:    }  
  34:  
  35:    public FileDownloader()  
  36:    {  
  37:      this.TargetName = "_blank";  
  38:      this.GateName = DEFAULT_GATE_NAME;  
  39:      this.GateParamName = DEFAULT_GATE_PARAM_NAME;  
  40:    }  
  41: }

SilverlightFileDownloader 

 

Voilà je pense que vous avez compris l'idée. Dans un prochain post, j'étenderais cette architecture pour pouvoir gérer la sélection multiple (création d'une archive côté serveur etc...).

Enjoy !

[Perso] A new one !

Hi all ! Voici mon premier post sur ce blog. Je vais donc me présenter et vous expliquer le type de contenu que je compte publié.

Je m'appelle Arnaud Auroux et j'ai 21 ans. Je suis actuellement en dernière année à Epitech et je travaille en alternance à Winwise depuis Novembre 2007. J'ai intégré le pôle Rich Internet & Desktop Application et j'interviens ainsi principalement sur des projets Silverlight, WPF, ASP.Net etc. Je m'intéresse également à la géolocalisation (VirtualEarth) et aux technologies de Synchronisation (SyncFramework). Dans le cadre de mon projet de fin d'étude, je suis actuellement responsable du développement de l'IHM de Touareg, un projet de WebOS en Silverlight 2.

Concernant ce blog, je compte écrire principalement du contenu relatif à des retours d'expériences avec Silverlight : effectuer des zooms techniques sur des points intéressants, partager mon point de vue sur la résolution de certains problèmes, mettre à disposition des contrôles personnalisés que j'aurai développé pour des besoins spécifiques etc. Bien que désireux de me concentré sur ce nouveau produit, je compte également écrire du contenu sur les autres technologies citées précédemment et auxquelles je m'intéresse.

Je tiens à remercier Cyril pour la création du blog, Thomas Lebrun qui m'a orienté vers codes-sources et également Adrien Siffermann et Philippe Sentenac qui m'ont encouragé à me lancer dans l'aventure Wink



Les 10 derniers blogs postés

- Technofolies, votre évènement numérique de l'année par Le Blog (Vert) d'Arnaud JUND le 09-26-2014, 18:40

- Xamarin : From Zero to Hero par Fathi Bellahcene le 09-24-2014, 17:35

- Conférences d’Automne 2014 par Le blog de Patrick [MVP SharePoint] le 09-24-2014, 14:53

- [TFS] Supprimer un projet de Visual Studio Online par Blog de Jérémy Jeanson le 09-22-2014, 20:42

- Nouveau blog en anglais / New blog in english ! par Le blog de Patrick [MVP SharePoint] le 09-18-2014, 18:42

- [ #Yammer ] From Mailbox to Yammer and back / De votre messagerie vers Yammer et retour ! par Le blog de Patrick [MVP SharePoint] le 09-15-2014, 11:31

- [ #Office 365 ] New service settings panel / Nouveau panneau de paramétrage des services par Le blog de Patrick [MVP SharePoint] le 09-11-2014, 08:50

- Problème de déploiement pour une démo SharePoint/TFS? par Blog de Jérémy Jeanson le 09-10-2014, 21:52

- [ #Office365 ] Delve first impressions / Premières impressions sur Delve par Le blog de Patrick [MVP SharePoint] le 09-09-2014, 16:57

- [ #Office365 ] How to change Administration console language ? / Comment changer la langue de la console d’administration ? par Le blog de Patrick [MVP SharePoint] le 09-09-2014, 08:25