Dissection des nouveautés de Silverlight 5 beta 1

Téléchargez le tutorial au format PDF

Article par David CATUHE

Nous allons tout au long de cet article passer en revue les nouveautés apportées par Silverlight beta 1. Certaines fonctionnalités prévues pour la RTM ne sont pas encore disponibles et seront donc traitées plus tard.

De ce fait, tout ce que vous voyez ici est déjà disponible en téléchargement.

La liste étant particulièrement longue, nous allons essayer de diviser les nouveautés en plusieurs chapitres. De plus, pour la plupart d’entre elles, un exemple est disponible dans un projet de démonstration téléchargeable ici.

En avant donc pour notre voyage au pays des nouveautés de Silverlight 5…

Sommaire de l'article

  • XAML
    • Support de l'Ancestor dans les Bindings
    • DataTemplates implicites
    • Binding dans les styles
    • Nouvel événement DataContextChanged
    • Breakpoint au niveau des bindings
    • Markup Extensions
    • Mise en oeuvre
    • Extensions pour les événements
    • Scénarii d'utilisations
    • Gestion des propriétés dynamiques et de leur binding
  • Contrôles
    • Fichier par défaut pour le contrôle SaveFileDialog
    • Amélioration de la gestion du texte
    • RichTextBox overflow
    • Support du TextSearch dans les ItemsControl
    • Petites modifications sur les structures CornerRadius, Tickness, Size et Rect
    • Gestion du multi-clic
    • Media SoundEffect
    • PlaybackRate
    • Optimisations
  • Nouveau modèle applicatif
    • In browser VS Out of browser
    • Fonctionnalités supplémentaires
    • Accès complet au système de fichiers
    • Intégration d'un navigateur en mode In browser
    • Intégration des NotificationWindow en mode In browser
    • Fenêtres natives
    • Modification du mode plein écran
  • API 3D
  • Pour aller plus loin

XAML

Support de l'Ancestor dans les Bindings

Dans le cadre du DataBinding en XAML, une fonctionnalité présente en WPF manquait en Silverlight :le binding vers un ancêtre, plus connu sous le nom de “RelativeSource FindAncestor”.

Le principe de ce binding est d’aller chercher l’information dans un contrôle parent de l’arbre visuel comme dans l’exemple ci-dessous

<ListBox x:Name="lst">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Button IsEnabled="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" Content="Edit" />
                <TextBlock Margin="5" Text="{Binding}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Ici, chaque item va se générer avec un DataTemplate qui contient notamment un bouton. Ce bouton sera actif uniquement quand l’élément sera sélectionné. Cela est possible grâce au binding qui va se brancher sur la propriété IsSelected du ListBoxItem parent.

Il existe plusieurs manière d’utiliser cette catégorie de binding:

  • Recherche par le type (Le premier contrôle parent du type donné sera sélectionné) :
<Button IsEnabled="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" />
  • Recherche par le type et le niveau (Silverlight sautera un certain nombre de contrôles correspondant au type. Souvent utile dans le cadre de hiérarchies imbriquées) :
<Button IsEnabled="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem,AncestorLevel=2}}"/>

De plus cette catégorie de binding peut être utiliser aussi bien dans les templates (ControlTemplate, DataTemplate, ItemsPanelTemplate) que dans les styles et bien sûr en direct dans un contrôle.

DataTemplates implicites

Jusqu’alors les DataTemplates étaient liés à un contrôle ou à une clef dans les ressources. Silverlight 5 va permettre d’affecter des DataTemplates sur un type de contrôle donné (Cette fonctionnalité était déjà présente en WPF).

Cela va permettre de définir le comportement visuel d’un type sans avoir à chaque fois à référencer le DataTemplate. De plus une liste de données hétérogènes pourra dorénavant être aisément affichée car cela implique plusieurs DataTemplates sur un seul contrôle.

La définition d’un DataTemplate implicite peut être faite au niveau de l’application ou à n’importe quel niveau de la hiérarchie. De plus il est possible de gérer le scope de définition avec par exemple une définition au niveau de l’application et une autre plus spécifique au sein d’un contrôle.

La mise en place se fait comme en WPF :

<UserControl x:Class="HotSilverlight5.ImplicitDataTemplate"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:my="clr-namespace:HotSilverlight5">
    <UserControl.Resources>
        <DataTemplate DataType="my:Product">
            <StackPanel>
                <TextBlock Text="{Binding Name}"/>
                <TextBlock Text="{Binding Price}" FontSize="10" FontStyle="Italic"/>
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <ListBox Name="lst" />
    </Grid>
</UserControl>

Cette définition peut se faire au niveau d’un contrôle et concerner autant de types que l’on souhaite :

<ListBox ItemsSource="{Binding}">
    <ListBox.Resources>
        <DataTemplate DataType="my:Achat">
            <TextBlock Foreground="Red"
                        Text="{Binding Path=Debit}}" />
        </DataTemplate>
        <DataTemplate DataType="my:Vente">
            <TextBlock Foreground="Green"
                        Text="{Binding Path=Encaissement}" />
        </DataTemplate>
    </ListBox.Resources>
</ListBox>

Il est même possible de définir un DataTemplate pour une classe de base et de le redéfinir pour une classe fille.

Binding dans les styles

Cette nouveauté va permettre de faire du binding dans le Setter.Value. Cela va permettre de faire des styles très dynamiques (comme en WPF).

Voici un exemple plus parlant :

<Grid x:Name="LayoutRoot" Background="White">
        <Grid.Resources>
            <ScaleTransform x:Key="scale" ScaleX="1" ScaleY="{Binding RelativeSource={RelativeSource Self}, Path=ScaleX}"/>
            <Style x:Key="dynamicSizeStyle" TargetType="TextBlock">
                <Setter Property="RenderTransformOrigin" Value="0.5, 0"/>
                <Setter Property="RenderTransform" Value="{Binding Source={StaticResource scale}}" />
            </Style>
        </Grid.Resources>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <TextBlock Style="{StaticResource dynamicSizeStyle}" Text="Coucou" Margin="5"/>
            <TextBlock Style="{StaticResource dynamicSizeStyle}" Text="Vive" Margin="5"/>
            <TextBlock Style="{StaticResource dynamicSizeStyle}" Text="Le" Margin="5"/>
            <TextBlock Style="{StaticResource dynamicSizeStyle}" Text="Binding" Margin="5"/>
        </StackPanel>
        <Slider VerticalAlignment="Bottom" Minimum="1" Maximum="10" Value="{Binding Path=ScaleX, Source={StaticResource scale}, Mode=TwoWay}" Margin="10" Height="30"/>
    </Grid>

Ce bout de code montre la puissance du binding dans les styles. Ainsi, nous allons pouvoir faire des styles non statiques comme ici ou l’on donne à chaque Textbox la capacité de se binder (par l’intermédiaire du style donc) sur le ScaleTransform. Ainsi lorsque ce dernier évolue, chaque Textbox évolue en fonction.

Il est donc désormais possible de définir des thèmes via les styles beaucoup plus dynamiques ou chaque propriété peut finalement être le relai d’une autre grâce au binding.

Nouvel évènement DataContextChanged

Dans la béta actuelle, cet évènement n’est pas disponible. Toutefois il y a fort à parier (c’est mon petit doigt qui me le dit et il en sait des choses ce petit canaillou) que cela ressemblera à ça (comme en WPF) :

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.DataContextChanged += new DependencyPropertyChangedEventHandler(MainWindow_DataContextChanged);
...
       void MainWindow_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
       {
...

L’évènement DataContextChanged proposera (sans doute^^) une propriété OldValue et une propriété NewValue pour gérer les abonnements aux évènements par exemple.

Breakpoint au niveau des bindings

Une grande avancée pour nous autres mineurs de l’extrême qui devons parfois débugger certaines pages XAML bien complexes. Dans Visual Studio 2010 SP1, il est possible de mettre un breakpoint directement dans le XAML au niveau d’un binding :

01

Lorsque le breakpoint est atteint on se retrouve avec la possibilité d’espionner la variable BindingState :

02

Pour avoir commencé à m’en servir, je peux vous garantir que c’est extrêmement pratique!

Markup Extensions

En XAML, le contenu des attributs est par défaut considéré comme une constante à affecter à la propriété cible.

Ainsi le code suivant va mettre la valeur '”coucou” dans la propriété Text du TextBlock :

<TextBlock Text="coucou" />

Le contenu d’un attribut pourra éventuellement passer par un IValueConverter qui permettra de le convertir dans le bon format. Comme par exemple ici, où la chaine “Red” sera convertie en un SolidColorBrush :

<Rectangle Fill="Red"/>

Toutefois, la notion de “markup extension” a été rajouté pour rendre dynamique le contenu des attributs (un peu comme la différence entre une propriété et une donnée membre). Ainsi par exemple peut-on exprimer les bindings grâce à l’extension de markup “Binding” :

<TextBlock Text="{Binding ElementName=lst, Path=SelectedItem.Text}"/>

Dans ce cas, l’extension va aller chercher un élément du contexte qui s’appelle “lst” et récupérera la propriété “SelectedItem.Text” pour affecter la valeur au champ “Text”.

Grâce aux markup extensions, il est également possible de faire appel à StaticResource par exemple.

Silverlight 5 introduit donc la capacité (inspirée de WPF) pour le développeur de faire ses propres extensions en implémentant l’interface IMarkupExtension.

Mise en oeuvre

Pour donner un exemple de l’usage nous allons nous amuser à refaire l’extension x:Static présente en WPF et qui permet d’aller chercher la valeur d’une propriété statique dans une classe statique.

Pour se faire nous allons développer une classe qui implémentera IMarkupExtension en fournissant une méthode [public object ProvideValue(IServiceProvider serviceProvider)]. Dans cette méthode nous devons en nous servant des services du IServiceProvider retourner la valeur qui sera finalement transmise. Dans notre cas, un peu de reflexion nous permettra de trouver ce que l’on veut :

public class MyStaticExtension : IMarkupExtension<object>
{
    public string Source { get; set; }
    public string PropertyName { get; set; }
    public object ProvideValue(IServiceProvider serviceProvider)
    {
        Type type = Type.GetType(Source);
        PropertyInfo propertyInfo = type.GetProperty(PropertyName, BindingFlags.Public | BindingFlags.Static);
        return propertyInfo.GetValue(null, null);
    }
}

Ici nous ne nous servons pas du IServiceProvider. Pour autant, ce dernier nous permet d’accéder à différents services comme le contrôle hôte, la propriété courante ou encore l’objet courant.

Par exemple, sur le code suivant :

<TextBlock Text="{HotSilverlight5:MyStaticExtension Source=HotSilverlight5.Tools, PropertyName=Property}"/>

Il est possible dans notre extension de récupérer les informations suivantes :

IProvideValueTarget targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
IRootObjectProvider rootObject = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;

Les valeurs que l’on pourra obtenir :

targetProvider.TargetObject Instance de mon TextBlock
targetProvider.TargetProperty   Text
rootObject.RootObject   Instance de mon UserControl racine

Cela peut nous permettre de faire des extensions contextuelles riches.

A noter qu’en XAML, les extensions peuvent s’écrire soit en condensé comme ci-dessus soit en éclaté comme ici :

<TextBlock Margin="10" HorizontalAlignment="Center" FontSize="20">
     <TextBlock.Text>
          <HotSilverlight5:MyStatic Source="HotSilverlight5.Tools" PropertyName="Property"/>
     </TextBlock.Text>
</TextBlock>

De plus il n’est pas nécessaire de faire apparaitre le mot “Extension”. Ainsi on peut référencer notre extension en tant que “MyStaticExtension” ou en tant que “MyStatic”.

Extensions pour les évènements

Les extensions peuvent aussi apparaitre au niveau des évènements pour choisir lors du runtime le handler que l’on souhaite :

<Grid Loaded="{HotSilverlight5:MyDynamicHandlerExtension}">

Et notre classe va alors ressembler à ça :

public class MyDynamicHandlerExtension : IMarkupExtension<Delegate>
{
    void MyHandler(object sender, RoutedEventArgs e)
    {
        // Mon handler
    }
    public Delegate ProvideValue(IServiceProvider serviceProvider)
    {
        IProvideValueTarget targetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        EventInfo targetEvent = targetProvider.TargetProperty as EventInfo;

        ParameterInfo[] pars = targetEvent.GetAddMethod().GetParameters();
        Type delegateType = pars[0].ParameterType;
        MethodInfo methodInfo = GetType().GetMethod("MyHandler", BindingFlags.NonPublic | BindingFlags.Instance);

        return Delegate.CreateDelegate(delegateType, this, methodInfo);
    }
}

Dans le cadre d’une extension pour un évènement, il faut retourner un délégué. Libre à nous par la suite de retourner ce que l’on souhaite dans ce délégué.

Scénarii d’utilisations

Les exemples d’utilisations des markup extensions sont nombreux. On peut par exemple citer:

  • Création de données à la volée (comme par exemple un UriMaker) à partir de paramètres, comme une sorte de IValueConverter mais en plus puissant
  • Fournir des traductions automatiques à partir d’une clé
  • Recherche de données (tel notre MyStaticExtension) pour aller récupérer des informations non accessibles directement en XAML
  • Accès à des points de l’arbre visuel depuis n’importe ou

Gestion des propriétés dynamiques et de leur binding

C’est une nouveauté importante à mon sens pour Silverlight car elle donne accès à la possibilité de rajouter des propriétés lors du runtime à un objet et surtout elle permet de se binder dessus depuis XAML.

Prenons par exemple le cas ou vous devez désérialiser des données depuis un flux (XML ou autres). Il va vous être possible de faire une classe dont les propriétés pourront être rajoutées au runtime. De plus rien ne vous empêchera d’avoir également des propriétés standards (design-time) sur votre classe.

Le seul hic que je vois à cette nouvelle technologie (en fait pas si nouvelle puisqu’elle était présente en .NET via ICustomTypeDescriptor et même via IDynamicMetaObjectProvider pour la DLR) c’est la complexité de la mise en œuvre.

Nous allons donc détailler cette dernière pour vous faciliter la vie si vous souhaitez vous en servir (parce que vous le valez bien!).

De manière générale le fonctionnement est le suivant : notre classe doit implémenter ICustomTypeProvider qui implique la création de la méthode GetCustomType. Cette méthode est appelé par Silverlight pour récupérer le type effectif de la classe (à la place donc du GetType). Ce type, nous allons le définir de manière à ce que lorsqu’on lui demande la liste des propriétés il s’arrange pour retourner la liste effective plus notre liste dynamique. Cette liste dynamique sera stockée dans un dictionnaire au niveau de notre classe.

Ce qui, en termes de code va nous donner:

La classe DynamicObject :

public class DynamicObject : ICustomTypeProvider, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        readonly Dictionary<string, object> dynamicProperties = new Dictionary<string, object>();
        MyTypeDelegator<DynamicObject> myTypeDelegator;

        public int TrueProperty { get; set; }

        public object GetPropertyValue(string key)
        {
            object value;
            return !dynamicProperties.TryGetValue(key, out value) ? null : value;
        }

        public void SetPropertyValue(string key, object value)
        {
            dynamicProperties[key] = value;

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

        public Type GetCustomType()
        {
            return myTypeDelegator ?? (myTypeDelegator = new MyTypeDelegator<DynamicObject>());
        }
    }

On peut donc voir les méthodes d’accès aux propriétés (ainsi qu’une vraie propriété pour nos tests) et surtout 'l’implémentation de GetCustomType. Cette méthode se chargeant de retourner notre propre implémentation du type DynamicObject pour y rajouter nos propriétés dynamiques :

public class MyTypeDelegator<T> : Type
    {
        public static List<MyPropertyInfo> _classProperties = new List<MyPropertyInfo>();

        public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
        {
            var properties = typeof(T).GetProperties(bindingAttr);

            return properties.Concat(_classProperties).ToArray();
        }

        protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
        {
            PropertyInfo propertyInfo = typeof(T).GetProperty(name, bindingAttr);

            if (propertyInfo != null)
                return propertyInfo;
            return new MyPropertyInfo(name);
        }

        // NotImplemented

        public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
        {
            throw new NotImplementedException();
        }
...

Cette classe se charge de redéfinir GetProperties et GetPropertyImpl. Pour le reste, dans le cadre de notre exemple, nous pouvons nous contenter d’un simple NotImplementedException. Il aurait été plus élégant d’utiliser la classe TypeDelegator qui permet justement de faire une délégation mais pour une raison inconnue je n’arrive pas à la faire marcher avec la béta.

Finalement, il ne nous reste plus qu’à fournir la classe MyPropertyInfo pour wrapper l’accès à une propriété dynamique :

public class MyPropertyInfo : PropertyInfo
    {
        private readonly string name;

        public MyPropertyInfo(string name)
        {
            this.name = name;
        }

        public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture)
        {
            DynamicObject myObject = (DynamicObject)obj;
            return myObject.GetPropertyValue(name);
        }

        public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture)
        {
            DynamicObject myObject = (DynamicObject)obj;
            myObject.SetPropertyValue(name, value);
        }

        // NotImplemented :)

        public override PropertyAttributes Attributes
        {
            get { throw new NotImplementedException(); }
        }
...

Cette classe fait juste un pont vers les bonnes méthodes au sein de notre DynamicObject.

Comme on peut le voir la mise en œuvre est un peu complexe mais sur la version finale Microsoft devrait fournir des helpers permettant de simplifier un peu tout ça.

En ce qui concerne l’utilisation en XAML (et c’est là que la magie opère), il suffit de considérer nos propriétés dynamiques comme des propriétés standards :

<Grid x:Name="LayoutRoot" Background="White">
    <StackPanel>
        <TextBlock Text="Incredible...there is a dynamic property here?"/>
        <TextBlock Text="{Binding Path=MyProperty}" />
        <TextBlock Text="{Binding Path=TrueProperty}" />
    </StackPanel>
</Grid>

Du côté du code behind :

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
    DynamicObject myDynamicObject = new DynamicObject {TrueProperty = 10};

    myDynamicObject.SetPropertyValue("MyProperty", "Here I am!");

    DataContext = myDynamicObject;
}

Notre objet DynamicObject est donc construit à la fois en design-time (sur sa propriété TrueProperty) et au runtime (sur sa propriété MyProperty). Le binding en XAML récupère les deux sans s’émouvoir le moins du monde de leur origine !

Contrôles

Fichier par défaut pour le contrôle SaveFileDialog

Attention, grosse nouveautéDescription. En fait c’est plus la correction d’un oubli. Désormais nous pourrons donner le nom du fichier par défaut qui apparaitra à l’ouverture d’une SaveFileDialog.

SaveFileDialog dialog = new SaveFileDialog {DefaultFileName = "temp.txt"};

Ca calme…

Amélioration de la gestion du texte

De nombreuses améliorations (inspirées de WPF) arriveront pour ce qui concerne la gestion du texte. Certaines toutefois sont déjà présentes sur cette béta au niveau des classes RichTextBox, Textblock et Textbox :

  • Gestion de l’espacement entre les caractères :
<TextBlock CharacterSpacing="200" Text="Bla bla bla. bla bla bla" />
  • Gestion des propriétés LineHeight (hauteur de chaque ligne de texte) et LineStackingStrategy (définition de la stratégie de calcul de la boite englobante de chaque ligne de texte
<TextBlock Text="Line Height = 50" FontWeight="Bold"/>
<RichTextBox LineHeight="50">
    <Paragraph>
        Bla bla bla
    </Paragraph>
    <Paragraph>
        Bla bla bla
    </Paragraph>
</RichTextBox>
<TextBlock Text="LineStackingStrategy = MaxHeight" FontWeight="Bold"/>
<RichTextBox LineStackingStrategy="MaxHeight">
    <Paragraph>
        Bla bla bla
    </Paragraph>
    <Paragraph>
        Bla bla bla
    </Paragraph>
</RichTextBox>

03

RichTextBox overflow

Le contrôle RichTextBox possède une nouvelle propriété qui s’appelle OverflowContentTarget qui permet de binder tout le texte qui ne pourrait tenir dans la RichTextBox vers un autre contrôle (un RichTextBoxOverflow) sachant quoi faire de ce texte.

Ainsi, il est par exemple possible de faire une zone de texte avec un overflow vers un tooltip :

<RichTextBox OverflowContentTarget="{Binding ElementName=overflow}" HorizontalAlignment="Center" Width="150" Height="50">
    <Paragraph>
        Ceci est mon message trop long et qui va donc avoir besoin d'un overflow
    </Paragraph>
    <ToolTipService.ToolTip>
        <RichTextBoxOverflow x:Name="overflow"/>
    </ToolTipService.ToolTip>
</RichTextBox>

Il est également possible chainer les overflows :

<RichTextBox OverflowContentTarget="{Binding ElementName=overflow1}" Width="150" Height="50">
    <Paragraph>
        ...
    </Paragraph>
    <ToolTipService.ToolTip>
        <RichTextBoxOverflow x:Name="overflow"/>
    </ToolTipService.ToolTip>
</RichTextBox>
<RichTextBoxOverflow x:Name="overflow1" Width="150" Height="50" OverflowContentTarget="{Binding ElementName=overflow2}"/>
<RichTextBoxOverflow x:Name="overflow2"/>

On peut donc imaginer des interfaces avec du texte qui s’étale sur plusieurs colonnes ou même des constructions plus créatives en chainant simplement les overflows.

Support du TextSearch dans les ItemsControl

Une fonctionnalité bien pratique (existant en WPF) a été ajoutée aux contrôles héritant de ItemsControl. Il est en effet dorénavant possible de définir la propriété attachée TextSearch.TextPath afin que l’on puisse sélectionner en utilisant le clavier un élément dont la propriété [TextPath] commence par le texte saisi.

Prenons l’exemple suivant :

<ListBox x:Name="lst" TextSearch.TextPath="Name">
            <ListBox.ItemTemplate>
                <DataTemplate DataType="my:Product">
                    <StackPanel>
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding Price}" FontSize="10" FontStyle="Italic"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Ainsi si je sélectionne cette liste et que je commence à taper un nom au clavier, la liste va sélectionner le premier item dont la propriété Name commence par le texte saisi.

Petites modifications sur les structures CornerRadius, Tickness, Size et Rect

Ces quatre structures peuvent maintenant utiliser la syntaxe d’attributs pour affecter leurs propriétés.

En effet, au préalable pour initialiser l’une d’entre elles il fallait :

  • soit passer par une déclaration inline via un type converter :
<Border CornerRadius="10, 10, 10, 10"/>
  • soit passer par une déclaration externe toujours via un type converter :
<Border>
    <Border.CornerRadius>
        <CornerRadius>10, 10, 10, 10</CornerRadius>
    </Border.CornerRadius>
</Border>

En Silverlight 5, il est maintenant possible de faire comme une classe standard :

<Border>
    <Border.CornerRadius>
        <CornerRadius BottomLeft="10" BottomRight="10" TopLeft="10" TopRight="10"/>
    </Border.CornerRadius>
</Border>

Ce n’est pas la fonctionnalité du siècle mais au moins cela uniformise le tout.

Gestion du multi-clic

Il aura fallu attendre 5 versions mais nous avons finalement accès au support du multi-clic en Silverlight. A nous donc les joies du double-clic voire même soyons fous du triple-clic!

La modification se fait au niveau des évènements souris avec l’ajout d’une nouvelle propriété ClickCount :

private void lst_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    if (e.ClickCount > 1)
    {
        ...
    }
}

Media

SoundEffect

L’équipe d’Aaron Oneal qui est en charge notamment de l’intégration de la 3D dans Silverlight 5 a également ajouté le support de la classe SoundEffect en provenance de XNA. Cette dernière permet de jouer un son depuis n’importe quel thread et de manière performante (ce n’est pas un MediaElement qui lui est un DependencyObject et qui est plus indiqué pour jouer des sons longs).

La mise en œuvre est simple au possible :

Stream soundfile = Assembly.GetExecutingAssembly().GetManifestResourceStream("HotSilverlight5.ding.wav");
SoundEffect soundEffect = SoundEffect.FromStream(soundfile);
soundEffect.Play();

Pour le moment cette classe ne supporte que les WAV (8 ou 16 bits en mon ou stéréo et sur 22, 44 ou 48 kHz). Dans un futur probable, le format MP3 sera pris en compte.

Cette classe SoundEffect permet également de gérer :

  • le looping
  • le pitch avec une valeur de –1(bas) à 1(haut)
  • le panning de –1 (gauche) à 1 (droite)
  • le volume avec une valeur de 0 (rien) à 1 (fort)

Ce qui donne :

Stream soundfile = Assembly.GetExecutingAssembly().GetManifestResourceStream("HotSilverlight5.ding.wav");
SoundEffect soundEffect = SoundEffect.FromStream(soundfile);

SoundEffectInstance instance = soundEffect.CreateInstance();

instance.IsLooped = true;
instance.Pan = 0.3f;
instance.Pitch = 0.8f;
instance.Volume = 1;

instance.Play();

Il est tout à fait possible du fait de la faible consommation en ressources du SoundEffect d’en lancer plusieurs en simultanée (jusqu’à 32).

PlaybackRate

Le MediaElement supporte maintenant une nouvelle propriété appelée PlaybackRate (ou TrickPlay si on veut se la raconter en soirée). Cette propriété s’accompagne d’un évènement PlaybackRateChanged pour pouvoir suivre son évolution.

Grâce à cette propriété, il est désormais possible d’accélérer ou de ralentir la lecture des médias entre les limites de –32 jusqu’à 32 :

<MediaElement Source="Babylon.wmv" Width="500" Height="300" AutoPlay="True" 
                                     PlaybackRate="{Binding ElementName=trickPlayer, Path=Value}"/>
<Slider x:Name="trickPlayer" Minimum="-32" Maximum="32" Value="0"/>

Optimisations

De nombreuses optimisations (certaines visibles d’autres moins) sont présentes dans cette béta :

  • Accélération matérielle pour les vidéos au format H.264
  • Le compilateur JIT (Just-In-Time) supporte maintenant les CPU multi-coeurs, ce qui permet une compilation plus rapide de l’IL et donc un lancement plus rapide des applications
  • La boucle graphique a été réécrite pour un rendu plus rapide. Ce sont des optimisations issues de Silverlight pour WP7 qui ont été utilisées (rien ne se perd ^^)
  • Le parser XAML a été optimisé
  • Le moteur de layout pour le texte a été amélioré
  • Au sein de IE9, l’accélèration matérielle est disponible
  • La pile réseau a été entièrement revue :
    • Elle tourne désormais dans le background thread (et donc ne bloque plus l’interface)
    • Le nombre d’activités réseau par requêtes a été réduit
    • Le gestionnaire d’évènements réseau est plus réactif

Nouveau modèle applicatif

In browser VS Out of browser

Le nouveau modèle applicatif de Silverlight 5 permet de développer des applications embarquées dans le navigateur (In browser) avec un niveau de droit élevé ce qui permet de faire autant de choses que la version hors du navigateur (Out of browser).

Pour se faire il faut signer son application avec un certificat et demander à l’administrateur du poste client (ou sera utilisée l’application) de mettre ce certificat dans la liste des certificats de confiance.

A partir de là, et sans que l’application Silverlight ne sorte du navigateur, il est possible de réaliser toutes les actions nécessitant un contexte de sécurité élevé (accéder aux ressources du système de fichier, faire de l’interopérabilité avec COM, etc.).

Pour le développeur, il suffit pour activer ce service d’aller dans les propriétés de l’application et de signer son application avec un certificat :

04

Le certificat en question peut soit être émis par une autorité sur Internet soit directement par le contrôleur de domaine courant.

Puis par la suite, il faut activer le mode Out of browser pour aller chercher l’élévation de droits :

05

06

Le manifest associé ressemble donc à cela :

<OutOfBrowserSettings ShortName="HotSilverlight5 Application" EnableGPUAcceleration="False" ShowInstallMenuItem="True">
  <OutOfBrowserSettings.Blurb>Bla bla bla…</OutOfBrowserSettings.Blurb>
  <OutOfBrowserSettings.WindowSettings>
    <WindowSettings Title="HotSilverlight5 Application" />
  </OutOfBrowserSettings.WindowSettings>
  <OutOfBrowserSettings.SecuritySettings>
    <SecuritySettings ElevatedPermissions="Required" />
  </OutOfBrowserSettings.SecuritySettings>
  <OutOfBrowserSettings.Icons />
</OutOfBrowserSettings>

A noter que même si nous souhaitons rester dans le navigateur, il faut quand même activer le mode Out of browser (oui je sais c’est bizarre).

De plus pour tester le mode In browser sur votre machine, il n’y a pas besoin de mettre un certificat. En effet, sur le localhost, Silverlight considère l’application comme nativement de confiance. Ceci bien sûr pour faciliter la vie des développeurs.

Fonctionnalités supplémentaires

Avec une application de confiance, en plus des possibilités Out of browser déjà accessibles avec Silverlight 4, de nouvelles fonctionnalités apparaissent avec Silverlight 5.

Accès complet au système de fichiers

Dans une application de confiance Silverlight 5, il est possible d’accéder à la totalité du système de fichiers (donc sans limitation de répertoire comme en SL4) et ceci sans avoir à lancer un OpenFileDialog ou un SaveFileDialog :

foreach (string filename in Directory.EnumerateFiles("c:\\", "*.txt"))
{
    txt.Text += File.ReadAllText(filename);
}

Les classes de System.IO accessibles sont les suivantes (attention toutefois, certaines méthodes ne sont pas disponibles) :

  • Directory
  • DirectoryInfo
  • File
  • FileInfo
  • FileStream
  • Path
  • StreamReader
  • StreamWriter

Intégration d’un navigateur en mode In browser

Alors qu’en Silverlight 4, il n’était possible d’embarquer un navigateur dans une fenêtre Silverlight qu’en mode Out of browser, en Silverlight 5, il est possible de le faire en In browser si l’application est de confiance (et si vous êtes dans Internet Explorer).

<Grid x:Name="LayoutRoot" Background="White">
    <WebBrowser Source="https://blogs.msdn.com/b/eternalcoding" />
</Grid>

Intégration des NotificationWindow en mode In browser

De la même manière que pour le WebBrowser, on peut désormais lancer une NotificationWindow en mode In browser si l’application est de confiance. Par contre (et c’est tant mieux) il n’y a pas de contrainte de navigateurs :

NotificationWindow wnd = new NotificationWindow
                             {
                                 Content = new NotificationContent(),
                                 Width = 200,
                                 Height = 30
                             };

wnd.Show(2000);

Fenêtres natives

Avec une application de confiance en mode Out of browser (important), il devient possible de créer des fenêtres natives du système d’exploitation qui portent leur propre arbre visuel. Silverlight 5 devient ainsi une technologie réellement professionnelle avec cette possibilité dont l’absence était pénalisante.

Pour définir une nouvelle fenêtre, il faut instancier une classe Window (rohh l’originalité!!) :

Window wnd = new Window
                 {
                     Width = 400,
                     Height = 300,
                     Title = "Fenetre native depuis SL5",
                     Content = new WindowContent(),
                     Visibility = Visibility.Visible
                 };

Comme l’on peut voir, la taille et le titre de la fenêtre sont modifiables. De plus, ce sont de vrais fenêtres au sens du système d’exploitation. Elles peuvent donc :

  • Exister au-delà des limites de la fenêtre principale
  • Etre visibles sur plusieurs écrans
  • Etre déplacées et redimensionnées
  • Etre indépendantes de la fenêtre principale

En gros, il s’agit de vraies fenêtres !

De même, afin de faciliter les communications entre les fenêtres la classe Application porte une nouvelle collection : Windows. Il est ainsi simple de parcourir la liste des fenêtres ouvertes :

foreach (Window window in Application.Current.Windows)
{
   ... 
}

De plus, il est possible de contrôler l’état de la fenêtre ainsi que son style de bordure :

WindowStyle = WindowStyle.None,
WindowState = WindowState.Maximized,

Grâce notamment au mode WindowStyle.None, il nous est possible de faire une fenêtre entièrement customisée (nous redéfinissons son Chrome. Je ne plaisante pas c’est bien le terme consacré). Ainsi, on peut redéfinir toute l’interface de gestion de la fenêtre dans notre contrôle contenu. La classe fenêtre fournit alors deux méthodes qui vont nous permettre de gérer le redimensionnement et le déplacement de la fenêtre :

  • wnd.DragMove() : cette fonction est a appelée lors du clic gauche de la souris sur la barre de titre (par exemple) ou sur toute partie pouvant déclencher le déplacement de la fenêtre
  • wnd.DragResize(WindowResizeEdge.xxxx) : cette fonction permet le redimensionnement de la fenêtre dans les 8 directions (Left, Bottom, Right, Top, BottomLeft, BottomRight, TopLeft, TopRight). Elle sera donc lancée sur le clic sur un élément symbolisant le redimensionnement

Donc imaginons que nous ayons le contrôle utilisateur suivant :

<UserControl x:Class="HotSilverlight5.WindowContent"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    

    <Grid x:Name="LayoutRoot" Background="White">
        <Rectangle StrokeThickness="1" Stroke="Black" />
        <Rectangle VerticalAlignment="Top" HorizontalAlignment="Stretch" Fill="Gray"               MouseLeftButtonDown="Rectangle_MouseLeftButtonDown" Height="30"/>
        <Button VerticalAlignment="Top" HorizontalAlignment="Right" Content="Close" Click="Button_Click" Margin="5"/>
        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="Ma super interface dans sa fenêtre" />
        <Rectangle VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="16" Height="16"               MouseLeftButtonDown="Rectangle_MouseLeftButtonDown_1" Fill="Black"/>
    </Grid>
</UserControl>

Et le code behind suivant :

public partial class WindowContent
{
    public WindowContent()
    {
        InitializeComponent();
    }

    public Window ParentWindow { get; set; }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        ParentWindow.Close();
    }

    private void Rectangle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        ParentWindow.DragMove();
    }

    private void Rectangle_MouseLeftButtonDown_1(object sender, MouseButtonEventArgs e)
    {
        ParentWindow.DragResize(WindowResizeEdge.BottomRight);
    }
}

On se retrouve donc avec une fenêtre complète ET opérationnelle (et moche) :

07

Finalement il faut s’attendre à ce que les futures versions nous permettent (sans doute) de faire des fenêtre modales et de faire passer des données directement entre les fenêtres.

Modification du mode plein écran

En Silverlight 5, il est bien sur toujours possible de passer en plein écran mais si votre application est de confiance, elle ne demandera plus l’avis de l’utilisateur avant de basculer.

De plus, en passant par le code suivant, il sera possible de garder la fenêtre en plein écran même si elle perd le focus :

Application.Current.Host.Content.FullScreenOptions = FullScreenOptions.StaysFullScreenWhenUnfocused;

Finalement, une application de confiance qui passe en plein d’écran n’affichera plus le message “Appuyez sur ECHAP pour sortir du plein écran”. En effet, Silverlight n’interceptera plus de manière automatique les KeyUp/KeyDown et laissera tout loisir à l’application de gérer la totalité du clavier (pensez bien à prévoir une option pour sortir du mode plein écran du coup ^^).

API 3D

Les nouvelles fonctionnalités de 3D accélérés sont très riches et donneront lieu de facto à un article à part.

Dans un premier temps, je vous renvoie vers mon blog sur Babylon (un moteur 3D réalisé en Silverlight 5 et dont les sources sont disponibles ici).

Pour aller plus loin

Comme nous avons pu le voir tout au long de cet article, Silverlight 5 arrive avec une grosse fournée de nouveautés (et encore nous n’avons pas tout vu) qui lui permettent à la fois de se rapprocher de WPF mais aussi d’en faire une plateforme encore plus multimédia et professionnelle.

Quelques liens pour digérer :

Vous n’avez dorénavant plus aucune excuse pour ne pas vous lancer dans Silverlight 5!