Présentation Kinect - semaine Internet des Objets - Brest
Entre le 3 et le 7 octobre 2011 se tenait à Brest la “semaine Internet des Objets”. Ucaya y était présent pour faire une présentation / atelier autour de Kinect.
La présentation
Cette session avait plusieurs buts que nous avons axé en destination d’un public novice.
- En premier nous avons introduit le capteur en lui même et son principe de fonctionnement.
- Puis à travers de brèves démos nous avons présenté comment accéder aux différents flux avec “Kinect for windows sdk”.
- Nous avons terminé la présentation par les usages de Kinect. Les réflexions liées à un cette nouvelle manière d’agir avec la machine et leurs applications sur un lecteur de flux videos au travers du framework KinectExtensions.
L’atelier
Pour cette partie nous avons proposé de réaliser un casse brique. A partir du squelette du jeu, les participants ont pû comprendre le fonctionnement du sdk et cabler les interactions.
Désactiver le bouton d’une ScrollBar quand elle est au Minimum ou au Maximum
Le contrôle ScrollBar de Silverlight ne désactive pas les boutons des ScrollBars lorsque le curseur arrive à une extrémité.
Ce design n’est en général pas satisfaisant dès l’instant ou on veut faire une application un peu jolie et ergonomique.
Pour activer ce comportement sur une ScrollBar, il faut éditer son template et utiliser la classe ScrollBarExtensions. Dans le Template nous commençons par activer la classe d’extensions sur la ScrollBar. Sur n’importe quel élément du template il suffit de déclarer l’AttachedProperty suivante:
<Grid x:Name="Root"
slec:ScrollBarExtensions.RegisterScrollbar="{Binding RelativeSource={RelativeSource TemplatedParent}}">
Une fois initialisée, la classe d’extensions calcule si la valeur est différente du Minimum ou Maximum et pose deux AttachedProperty sur la ScrollBar : CanIncrease et CanDecrease. Il ne reste plus en suite qu’à Binder les propriété IsEnabled des boutons à ces valeurs.
IsEnabled="{Binding (slec:ScrollBarExtensions.CanDecrease), RelativeSource={RelativeSource TemplatedParent}}"
La classe ScrollBarExtensions:
/// <summary>
/// Provide behavior extensions to a ScrollBar control. When registered add CanIncrease and CanDecrease attached property
/// </summary>
public static class ScrollBarExtensions
{
/// <summary>
/// Get the epsillon value arround the Maximum or Minimum value that activate or deactive the CanIncrease and CanDecrease property
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static double GetEpsillon(DependencyObject obj)
{
return (double)obj.GetValue(EpsillonProperty);
}
/// <summary>
/// Set the epsillon value arround the Maximum or Minimum value that activate or deactive the CanIncrease and CanDecrease property
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
public static void SetEpsillon(DependencyObject obj, double value)
{
obj.SetValue(EpsillonProperty, value);
}
// Using a DependencyProperty as the backing store for Epsillon. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EpsillonProperty =
DependencyProperty.RegisterAttached("Epsillon", typeof(double), typeof(ScrollBarExtensions), new PropertyMetadata(0.001d));
/// <summary>
/// Get the registered ScrollBar
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static ScrollBar GetRegisterScrollbar(DependencyObject obj)
{
return (ScrollBar)obj.GetValue(RegisterScrollbarProperty);
}
/// <summary>
/// Register a ScrollBar
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
public static void SetRegisterScrollbar(DependencyObject obj, ScrollBar value)
{
obj.SetValue(RegisterScrollbarProperty, value);
}
public static readonly DependencyProperty RegisterScrollbarProperty =
DependencyProperty.RegisterAttached("RegisterScrollbar", typeof(ScrollBar), typeof(ScrollBarExtensions), new PropertyMetadata(RegisterScrollbarChangedCallback));
private static void RegisterScrollbarChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sb = (ScrollBar)e.OldValue;
if (sb != null)
{
sb.ValueChanged -= storyBoardPropertyChanged;
sb.RemoveChangedHandler("Maximum", storyBoardDependecyPropertyChanged);
sb.RemoveChangedHandler("Minimum", storyBoardDependecyPropertyChanged);
}
sb = (ScrollBar)e.NewValue;
if (sb != null)
{
sb.ValueChanged += storyBoardPropertyChanged;
sb.AddChangedHandler("Maximum", storyBoardDependecyPropertyChanged);
sb.AddChangedHandler("Minimum", storyBoardDependecyPropertyChanged);
RefreshValue(sb);
}
}
private static void RefreshValue(ScrollBar sb)
{
var epsillon = GetEpsillon(sb);
SetCanDecrease(sb, (sb.Value - sb.Minimum) > epsillon);
SetCanIncrease(sb, (sb.Maximum - sb.Value) > epsillon);
}
static void storyBoardDependecyPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
RefreshValue((ScrollBar)sender);
}
static void storyBoardPropertyChanged(object sender, EventArgs e)
{
RefreshValue((ScrollBar)sender);
}
/// <summary>
/// true when the Value of a ScrollBar is greater than the Minimum + the Epsillon value
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool GetCanDecrease(DependencyObject obj)
{
return (bool)obj.GetValue(CanDecreaseProperty);
}
public static void SetCanDecrease(DependencyObject obj, bool value)
{
obj.SetValue(CanDecreaseProperty, value);
}
public static readonly DependencyProperty CanDecreaseProperty =
DependencyProperty.RegisterAttached("CanDecrease", typeof(bool), typeof(ScrollBarExtensions), null);
/// <summary>
/// true when the Value of a ScrollBar is loawer than the Maximum - the Epsillon value
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool GetCanIncrease(DependencyObject obj)
{
return (bool)obj.GetValue(CanIncreaseProperty);
}
public static void SetCanIncrease(DependencyObject obj, bool value)
{
obj.SetValue(CanIncreaseProperty, value);
}
public static readonly DependencyProperty CanIncreaseProperty =
DependencyProperty.RegisterAttached("CanIncrease", typeof(bool), typeof(ScrollBarExtensions), null);
}
Etre notifié du changement de valeur d’une DependencyProperty
Les contrôles n’implémentent pas tous des événements notifiant du changement de valeur de leurs propriétés. Un moyen d’être notifié est d’utiliser le Binding. Voici une classe fournissant des méthodes d’extensions sur les DependencyObject pour ajouter des handlers d’événements.
Ajouter un événement très simple :
ScrollBar sb = new ScrollBar();
sb.AddChangedHandler("Value", (o, e) =>
{
MessageBox.Show(Convert.ToString(e.NewValue));
});
Code :
public static class DependencyObjectExtensions
{
/// <summary>
/// Inner class bound to the Property we want to monitor
/// </summary>
private class BindingSubscription : DependencyObject, IDisposable
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="source">The DependencyObject instance on which we want to monitor the property</param>
/// <param name="name">The PropertyPath to the property we want to monitor</param>
/// <param name="handler">The handler to invoke when the property changed</param>
public BindingSubscription(DependencyObject source, string name, DependencyPropertyChangedEventHandler handler)
{
this.Handler = handler;
binding = new Binding(name) { Source = source };
this.PropertyPath = name;
BindingOperations.SetBinding(this, ValueProperty, binding);
}
private Binding binding;
/// <summary>
/// The PropertyPath to the property we want to monitor
/// </summary>
public string PropertyPath { get; set; }
/// <summary>
/// The handler to invoke when the property changed
/// </summary>
public DependencyPropertyChangedEventHandler Handler { get; set; }
/// <summary>
/// Value depedency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value",
typeof(object),
typeof(BindingSubscription),
new PropertyMetadata((d, e) =>
{
var subscription = ((BindingSubscription)d);
var handler = subscription.Handler;
if (handler != null)
handler(subscription.source, e);
}));
/// <summary>
/// Release the binding from the source
/// </summary>
public void Dispose()
{
this.ClearValue(ValueProperty);
}
}
/// <summary>
/// Get the list of BindingSubscription stored on one object
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
private static List<BindingSubscription> GetListeners(DependencyObject obj)
{
var list = (List<BindingSubscription>)obj.GetValue(ListenersProperty);
if (list == null)
{
list = new List<BindingSubscription>();
SetListeners(obj, list);
}
return list;
}
/// <summary>
/// Set the list of BindingSubscription on an object
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
private static void SetListeners(DependencyObject obj, List<BindingSubscription> value)
{
obj.SetValue(ListenersProperty, value);
}
/// <summary>
/// Listeners dependency property
/// </summary>
private static readonly DependencyProperty ListenersProperty =
DependencyProperty.RegisterAttached("Listeners", typeof(List<BindingSubscription>), typeof(DependencyObjectExtensions), null);
/// <summary>
/// Add a handler to a DependencyProperty for change notification
/// </summary>
/// <param name="source">The DependencyObject instance on which we want to monitor the property</param>
/// <param name="name">The PropertyPath to the property we want to monitor</param>
/// <param name="handler">The handler to invoke when the property changed</param>
public static void AddChangedHandler(this DependencyObject source, string name, DependencyPropertyChangedEventHandler handler)
{
// Create the binding
BindingSubscription subscriber = new BindingSubscription(source, name, handler);
// Store the binding into de DependencyObject to prevent garbage collection of the subscriber
GetListeners(source).Add(subscriber);
}
/// <summary>
/// Remove a handler from a DependencyProperty
/// </summary>
/// <param name="source">The DependencyObject instance on which we want to monitor the property</param>
/// <param name="name">The PropertyPath to the property we want to monitor</param>
/// <param name="handler">The handler to invoke when the property changed</param>
public static void RemoveChangedHandler(this DependencyObject source, string name, DependencyPropertyChangedEventHandler handler)
{
var subscribers = GetListeners(source);
var subscriber = subscribers.FirstOrDefault(s => s.Handler == handler && s.PropertyPath == name);
if (subscriber != null)
{
subscriber.Dispose();
subscribers.Remove(subscriber);
}
if (subscribers.Count == 0)
source.ClearValue(ListenersProperty);
}
}
SLExtensions – Création d’un player vidéo avec SLMedia – partie 1
La partie SLMedia du projet SLExtensions contient tout le nécessaire pour réaliser un player video. Contrairement à un projet comme SMF qui vous fourni un contrôle tout prêt, SLMedia vous fourni des classes “Controller” vous permettant de développer votre player à base de MVVM. Si un player packagé est simple à templater, il trouve vite ses limites lorsque vous voulez intégrer des comportements spécifiques.
Il y a quelque temps sur ce blog j’avais commencé la création d’un player video ici et ici. SLMedia a évolué je vais donc mettre à jour mes exemples.
Affichage de la vidéo
La classe principale de gestion reposant sur la notion de playlist, le passage d’un élément smoothstreaming à un élément video normal n’était pas aisé. Le conteneur affichant la vidéo (le MediaElement ou le SmoothStreamingMediaElement) et maintenant déterminé en fonction du IMediaElement courrant. Pour être plus facilement modulaire et pour permettre de séparer la librairie affichant du smoothstreaming, les éléments video reposent sur des VideoAdapters qui se chargent de la création et de s’attacher aux différents événements des composants videos.
Le design de l’application n’a plus qu’à créer un ContentControl et récupérer le contrôle d’affichage à partir du VideoController.
<ContentControl Content="{Binding VideoAdapter.Content}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch" />
Etats visuels
Grâce à Silverlight 4 qui permet maintenant de faire du binding sur des objets de type DependencyObject, l’écriture d’un MapState est un peu simple. Il suffit de binder la propriété Value sur l’objet à surveiller. Et lorsque la valeur change, en fonction des différents mappings définis, l’état visuel correspondant sera déclenché.
<i:Interaction.Behaviors>
<slint:MapState Value="{Binding IsPlaying}">
<slint:MapStateMapping StateName="Playing"
Value="true" />
<slint:MapStateMapping StateName="Paused"
Value="false" />
</slint:MapState>
</i:Interaction.Behaviors>
StringFormat
De même, grâce à Silverlight 4, exit le StringFormatConverter. Il est maintenant possible de directement spécifier la conversion en string dans l’objet Binding.
<TextBlock Text="{Binding Path=BufferingProgress, Mode=TwoWay, StringFormat=p}" />
La playlist
Le MediaController fournit maintenant des commandes permettant de naviguer dans la playlist. Il suffit de relier deux boutons Précédent / Suivant à ces commandes pour passer d’un élément à l’autre. De même, pour enchainer les éléments à la fin de la lecture, il suffit d’activer la propriété IsChaining.
<slvideo:VideoController x:Name="controller"
AutoPlay="True"
IsChaining="True">
<slvideo:VideoController.Playlist>
<slvideo:VideoItem Source="http://labs.ucaya.com/lake.wmv" />
<slvideoSmooth:SmoothVideoItem Source="http://mediadl.microsoft.com/mediadl/iisnet/smoothmedia/Experience/BigBuckBunny_720p.ism/Manifest" />
<slvideo:VideoItem Source="http://labs.ucaya.com/butterfly.wmv" />
<slvideo:VideoItem Source="http://labs.ucaya.com/bear.wmv" />
</slvideo:VideoController.Playlist>
</slvideo:VideoController>
Navigation :
<Button Content="Previous"
Command="{Binding PreviousItem}" />
<Button Content="Next"
Command="{Binding NextItem}" />
Les sources
Live meeting "Ma première appli Windows Phone 7"
Tu es fan de Silverlight ou XNA, Microsoft te convie à un eEvent pour t’apprendre comment appliquer ta techno au développement d’application Windows Phone. Tu pourras te rendre compte de la facilité de mise en oeuvre d’une application et peut être te donner l’envie de réaliser l’application qui “déchire” et que tout le monde voudra sur son téléphone.
Je suis sur que tu n’as rien de mieux à faire le 29 avril à 18h, alors n’hésite pas à te connecter là : https://msevents.microsoft.com/CUI/EventDetail.aspx?EventID=1032448416&Culture=fr-FR
Originally posted at
Mix10 - Silverlight 4 RC - Windows Phone 7 - IE9 Preview – OData – Codename Dallas
Silverlight 4 RC
La première annonce a été pour Silverlight 4, qui est disponible en version RC. Un an après le version finale de SL3, cette version apporte son lot d'amméliorations.
Entre autre:
- Rich Text:
- Propriété "Xaml" pour la sérialisation du contenu - Réseau
- Les application FullTrust outofbrowser n'ont plus besoin de serveur de crossdomain
- Le crossdomain pour les appels sockets est récupéré par http. - Out of Browser (Full Trust)
- Signature des applications. La boite de dialogue d'installation des applications fulltrust vous indique si l'application est certifiée. Un utilisateur pourra donc s'assurer de la provenance de l'application qu'il est en train d'installer.
- Personnalisation de la fenêtre principale - Media
- Possibilité de garder une application fullscreen sur un deuxième écran
- Support du multicast - Graphics and Printing
- Les perspective transforms supportent l'accélération materielle
- Accélération matérielle pour deepzoom - Data
- Support du XPath pour le XML - Plugin
- Support de Google Chrome sur windows
- Support de MEF
- Support de Windows Phone
@ScottGu nous annonce aussi une sortie finale pour le mois prochain.
Windows Phone 7 alias WP7
La deuxième grosse annonce est la disponibilité des kits de développements pour windows phone 7. Ces kits contiennent des outils gratuits qui permettront à tout le monde de pouvoir concevoir des applications pour WP7. Visual Studio 2010 Express fait son apparition ainsi qu'une marchine virtuelle faisant tourner un device WP7. Coté designer, une version beta de Blend 4 est disponible en téléchargement sur laquelle il est possible d'installer les outils pour windows phone.
http://developer.windowsphone.com
Internet Explorer 9
Le deuxième jour, Microsoft a présenté IE9, la nouvelle mouture de son navigateur. Les développements d'ie9 s'orrientent vers les besoins utilisateurs. Pour cette nouvelle version, au menu : performances et conformités aux standards (HTML5, CSS3).
Pour améliorer les perfomances javascript, Microsoft a analysé les 7000 premiers sites webs afin de déterminer les fonctions les plus fréquement utilisées et de les optimiser en priorité. Le nouveau moteur javascript compile les scripts pour gagner en performance d'exécution. Cette compilation est effectuée en arrière plan et est multi coeur.
Une autre piste pour l'amélioration des performances est l'utilisation de l'accélération graphique pour tout ce qui est rendu. L'utilisation de DirectX permet un gain non negligeable de performances pour l'affichage des images mais aussi pour le rendu cleartype du text.
Ce rendu DirectX permet d'afficher du SVG et des videos HD avec des performances impressionantes. Lors de la keynote, un netbook affichait 2 videos HD 720p avec la balise video de html5 sans ralentissement alors que chrome ne permettait pas un rendu fluide d'une seule.
La preview de ie9 est téléchargeable ici : http://www.ietestdrive.com
Open Data Protocol
La dernière annonce concerne oData. Une standardisation d'un système de requêtage sur un modèle REST. Les détails de ce protocole sont accessibles sur http://www.odata.org. De nombreux clients sont capables d'utiliser oData. Microsoft fourni sur le site oData des clients pour différents langages (java, php, .net). Le code mis à disposition sur odata.org est publié sous licence Apache 2. Sur les environements Microsoft comme silverlight, un proxy est généré par visual studio est permet de traduire des requêtes linq en requêtes oData et de récupérer le résultat sous forme d'objets fortements typés. Ce type de services sont déjà exposés par Sharepoint 2010. Chaque liste de donnée sharepoint est récupérable par ce biais. Des sources oData peuvent être crées manuellement sur des webservices WCF mais aussi directement sur des bases de données SQL Azure.
Codename Dallas
Microsoft propose une place de marché où vous pouvez référencer vos services créés avec oData. Il devient alors très facile à un développeur de tirer parti de toutes ces sources et de construire une application (sur WP7 par exemple). Le market place est disponible ici : http://www.sqlazureservices.com
Serialisation webservices Proto Buffer et client Silverlight
Un de mes amis m’a conseiller de jeter un oeil à la sérialisation Proto Buffer. Elle est rapide et légère. Idéale donc pour la consommer par webservice. Il existe plusieurs implémentation pour c#, je me suis intéressé à protobuf-net. Pour une comparaison des performances par rapport à du BinaryFormatter du Soap ou du Json, vous pouvez vous rendre sur cette page.
Utilisation avec WCF
Dans mes premiers tests, je voulais remplacer le DataContractSerializer de mes ServiceContract par le serializer protobuf-net, malheureusement le support de WCF de silverlight est assez léger et il n’est actuellement pas possible de le faire. Dans ses exemples protobuf-net contient un serveur et client RPC. J’ai donc configuré WCF avec une adaptation de ce serveur RPC et remplacé le client généré par le wizard de visual studio par le client protobuf-net.
Côté serveur
Pour avoir un serveur RPC compatible avec l’exemple fourni par protobuf-net, il faut rajouter à WCF un OperationSelector routant les urls entrantes vers le bon service contract. J’ai réalisé un EndPointBehavior paramettrant tout ça. La configuration ressemble à ceci:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="protoRPC" type="ProtoBuf.RPC.ProtoRpcEndpointBehaviorSection, protobuf-wcf, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="rpcBehavior">
<protoRPC/>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="SilverlightApplication4.Web.TestService">
<endpoint address="" binding="webHttpBinding" contract="TestServices.ITestService" behaviorConfiguration="rpcBehavior"/>
</service>
</services>
</system.serviceModel>
La classe implémentant le ServiceContract doit également autoriser toutes les urls:
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class TestService : ITestService
{
#region ITestService Members
public TestServices.Customer DoWork(string a, TestServices.Customer c)
{
return TestServices.Customer.Invent();
}
#endregion
}
J’ai choisi d’externaliser le ServiceContract dans une interface pour pouvoir générer simplement un client dans la partie Silverlight. Voici à quoi il ressemble:
[ServiceContract]
public interface ITestService
{
[OperationContract(Action = "dowork")]
Customer DoWork(string a, Customer c);
}
Côté Silverlight
Pour fonctionner avec Silverlight, il a fallu que je corrige un bug dans les sources de protobuf-net. Les classes temporaires de sérialization générées par protobuf-net sont privées et Silverlight ne permet pas de faire de réflection sur celles-ci. Il faut les mettres en internal.
DataContracts
Dans l’implémentation de mon exemple, j’ai isolé mes datacontracts dans une assembly séparée. J’ai créé la même assembly pour Silverlight en référençant simplement les fichiers par liens, ce qui me permet de n’avoir qu’une seule version de ces fichiers. Pour ceux qui ne saurait pas comment faire et pour éviter de perdre 2 heures à chercher, il suffit lorsque vous ajouter un élément à la solution de cliquer sur la petite flêche à côté du boutton Add et de choisie Add as link.
Génération du client
Nous voici donc avec la même assembly que côté serveur, mais compilée au format Silverlight. Cette assembly contenant notre interface de ServiceContract, nous allons rajouter dans la version Silverlight la génération du client. J’ai fais cette génération par l’utilisation des fichiers t4. Il suffit de copier coller le fichier .tt qui se trouve dans la solution et de le nommer du nom du fichier qui contient l’interface de ServiceContract pour que la génération s’effectue (ici ITestService.tt). La génération utilise le FileCodeModel pour déterminer les Méthodes de l’interface à implémenter dans le client. Le client généré contient des fonctions d’appels asynchrones et les événements completed qui correspondent.
Utilisation
L’utilisation de notre client reste aussi simple que l’utilisation d’un client WCF silverlight standard.
private void Button_Click(object sender, RoutedEventArgs e)
{
ITestServiceClient client = new ITestServiceClient(new Uri("../", UriKind.Relative));
client.DoWorkCompleted += new EventHandler<ITestServiceClient.DoWorkResultEventArgs>(client_DoWorkCompleted);
client.DoWorkAsync("aazer", Customer.Invent());
}
void client_DoWorkCompleted(object sender, ITestServiceClient.DoWorkResultEventArgs e)
{
var r = e.Result;
}
Il y a plusieurs façons d’instancier le client. Tout dépend d’où vos webservices se trouvent.
Le constructeur par défaut va chercher un webservice se nommant du même nom que l’interface de service contract sans le I se trouvant de le même dossier que votre .xap. Sur une surcharge du constructeur, vous pouvez spécifier soit une Url donnant le dossier dans lequel va se trouver le webservice ou alors un url d’appel direct vers le fichier svc.
France Télévisions – L’info en vidéo
Et une nouvelle version pour l’application L’info en vidéo.
Maintenant vous avez accès aux Directs !
Une nouvelle entrée “Evenements” dans le menu donne accès à tous les directs. Et lorsqu’une catégorie du menu contient un direct, un picto rouge vous l’indique.
SLMedia et Smooth Streaming
Comme les français ont put le voir avec Roland Garros, le Smooth Streaming permet de diffuser des flux de grande qualité qui vont s’adapter à la connexion et à la puissance de la machine et de pouvoir faire du timeshifting (contrôle du direct) sur les emissions live.
Smooth Streaming c’est quoi ?
Le smooth streaming n’est pas un format de compression de video. C’est une technologie de diffusion qui permet de découper la vidéo et le son en morceaux de 2 secondes et diffuser le tout par http. La vidéo étant encodé en plusieurs niveau de qualité, nous obtenons des échantillons de différents poids qui vont être mis à disposition sur un serveur web.
La partie client web (silverlight) va récupérer ces différents paquets et les mettre bout à bout pour reconstituer le flux vidéo. L’application silverlight récupérera le paquet de poids correspondant au mieux à la qualité de la connexion à internet.
Pour la partie diffusion en live, un ou plusieurs encodeur envoyent au serveur web ces paquets de 2 secondes au fur et à mesure qu’ils sont reçu de la source et encodés.
Pour silverlight 2, la vidéo est au format VLC1 et nous devrions avoir droit au H264 avec silverlight 3.
Le découpage en paquets et la diffusion au format HTTP permettent de tirer partie des différents Cache HTTP qui existent sur internet.
Côté serveur
La diffusion des flux smooth streaming nécessitent des composants côté serveur. Actuellement, IIS7 est la seule plateforme supportant la diffusion de ce format. Deux versions existent. La première vous permet de diffuser des vidéos préenregistrées au format smooth streaming et la deuxième encore en beta permet la diffusion de flux live.
Côté client
Silverlight est actuellement le seul moyen de lire de la vidéo en Smooth Streaming. Des exemples existent pour Silverlight 2 et Silverlight 3.
Encodeur
Pour encoder des vidéos préenregistrées, vous devrez passer par Microsoft Expression Encoder SP1. Pour les flux live, des encodeurs hardwares sont en cours de développement. Pour Roland Garros, c’est la société Inlet qui les a fourni. Vous pouvez cependant simuler un flux live à partir d’une vidéo préencodée en Smooth Streaming. Allez sur le site officiel et téléchargez le starter kit. Vous trouverez 2 exécutables PushEncoder32.exe et PushEncoder64.exe qui poussent votre vidéo préencodée sur le point de distribution live.
SLMedia
La librairie de décodage du flux smoothstreaming (Microsoft.Web.Media.SmoothStreaming.dll) est récupérable dans le player d’exemple silverlight du starter kit.
Pour cet exemple, je vais réutiliser le VideoPlayer d’un précédent billet.
L’assignation d’une vidéo à un MediaElement ne se fait pas de la même manière que la video soit du smooth streaming ou non. Pour gérer ceci, la notion de VideoSourceAdapter et VideoAdapter a été ajoutée à SLExtensions. Le VideoSourceAdapter va instancier la bonne source en fonction de l’url de la vidéo. La première chose à faire dans le projet est de référencer le projet SLMedia.SmoothStreaming et d’initialiser le SmoothStreamingVideoSourceAdapter. Il suffit de rajouter la ligne ci-dessous au constructeur de la classe App.xaml.cs.
SLMedia.SmoothStreaming.SmoothStreamingVideoSourceAdapter.Initialize();
Ensuite, changez le binding sur le source du MediaElement et l’url de la video.
<MediaElement x:Name="mediaelement"
slvideo:VideoSourceAdapter.Source="{Binding Path=CurrentItem.SourceUri}"
slvideo:VideoController.MediaElement="{StaticResource controller}"
IsMuted="{Binding Path=IsMuted}"
Volume="{Binding Path=Volume}"
AutoPlay="{Binding Path=AutoPlay}"
/>
controller.Playlist.Add("http://localhost/live.isml/Manifest");
Attention, le chemin d’accès au Manifest est sensible au crossdomain. Pensez à autoriser votre player à accéder à la video si le player n’est pas dans le même domaine (crossdomain.xml)
Poussez votre video en live PushEncoder64.exe http://localhost/live.isml "Big Buck Bunny.ism" et lancez votre player. Vous devriez voir la video. Si vous voulez un contenu préencodé, téléchargez la video Big Buck Bunny ici.
Par défaut, la lecture du flux commence toujours au début de la vidéo qui a été encodée. Si vous voulez rejoindre le direct, vous pouvez appeler la fonction GoToLive du VideoAdapter ou créer un mediaitem de type LiveVideoItem et lui assigner sa propriété JoinLive.
void Page_Loaded(object sender, RoutedEventArgs e)
{
controller.Playlist.Add(new LiveVideoItem
{
Source = "http://localhost/live.isml/Manifest",
JoinLive = true
});
controller.Next();
}
la timeline pour le Live Smooth Streaming
Pour les événements live, les informations de position ne démarrent pas forcément à 0. Il faut changer la déclaration des bindings du slider pour tenir compte des propriétés Start et LivePosition.
<slec:Slider Maximum="{Binding Path=VideoAdapter.LivePosition, Converter={StaticResource timeSpanConverter}}"
Minimum="{Binding Path=VideoAdapter.Start, Converter={StaticResource timeSpanConverter}}"
MoveValue="{Binding Converter={StaticResource timeSpanConverter}, Mode=TwoWay, Path=Position}"
Width="200" Margin="20,0,0,0"
/>
Quelle représentation pour la timeline
- Un slider simple
Ce contrôle est utilisé dans le majorité des players vidéos disponible sur internet. Il se prête bien pour des vidéos de taille connues et relativement petites. Dans le cas de diffusion de flux live, la taille du slider ne représente pas toujours la même durée. Au bout d’une heure d’émission, amener le slider au milieu reculera d’une demi heure alors qu’au bout d’une journée on reculera d’une demi journée.
- Un “slider inversé”
Le marqueur au milieu indique la position de lecture. La barre orange indique la durée de la vidéo. Cette barre grandit au fur et à mesure que le flux live est émis. Quand l’utilisateur regarde en live, la barre orange s’agrandit vers la gauche et rien ne dépasse à droite du marqueur.
Si l’utilisateur met en pause, le barre s’agrandit à droite du marqueur montrant qu’on est plus en live et indique la durée du décalage par rapport au live.
Quand la lecture reprend, la barre regrandit à gauche.
Contrairement au slider simple, ce type de contrôle permet d’avoir une échelle de temps constante tout au long de la diffusion. Lorsqu’on déplace la timeline, c’est la barre orange qui se déplace, le marqueur reste toujours au milieu. Peut être un peu déroutant au départ, un utilisateur en comprendra vite la logique
- Un slider sur une ligne de temps
C’est ce qui a été utilisé pour Roland Garros, une barre de temps représente la journée entière. La taille du slider grandit au fur et à mesure du live. Le slider garde une échelle constante mais est quasi invisible au départ. Le principe de déplacement du curseur est préservé.
N’oubliez pas de télécharger les sources de SLMedia sur le site de slextensions.
SLMedia le player Photo / Video de SLExtensions
Les librairies SLMedia regroupent tout ce qu'il vous faut pour gérer vos galeries photos et vidéo. Ces librairies sont séparées en plusieurs dlls gérant chacune un type de média.
- SLMedia.Core : Librairie centrale prenant en charge les fonctions communes.
- SLMedia.Picture : Librairie vous permettant de réaliser des slideshows d’images.
- SLMedia.Deepzoom : Librairie vous permettant d’afficher des galeries deepzoom de vos images.
- SLMedia.Video : Librairie gérant les galeries vidéos.
- SLMedia.SmoothStreaming : Gestion des vidéos en Smooth Streaming
Création d’un player video avec SLMedia
La réalisation d’un player avec SLMedia repose sur les concepts de Binding et de Behavior. Les Bindings vont vous servir à lier les informations provenant du controller (ici un VideoController) et les différents éléments graphiques qui vont vous permettre d’afficher et d’agir sur votre vidéo.
MediaController
Cette classe appartenant à SLMedia.Core sert de base au VideoController. Elle regroupe les fonctionnalités communes aux différents controllers.
- Gestion de la playlist (Next, Previous, IsPlaying, SelectedItem)
- Gestion du fullscreen (IsFullscreen)
- Gestion des progressions pour les slideshow / videos (Position, Duration)
- Etc..
VideoController
Cette classe hérite du MediaController et lui rajoute des fonctions spécifiques pour la vidéo.
- MediaElement
- Buffering
- Support de différentes source par les VideoAdapter
Etape 1 : Affichage d’une video de la playlist
Dans votre page xaml, référencez le namespace correspondant à SLMedia.Video et ajouter le VideoController aux ressources.
<UserControl x:Class="VideoPlayer1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:slvideo="clr-namespace:SLMedia.Video;assembly=SLMedia.Video"
>
<UserControl.Resources>
<slvideo:VideoController x:Name="controller" />
</UserControl.Resources>
Ensuite posez un MediaElement et associez sa source avec la propriété CurrentItem de notre controller. Le CurrentItem sera du type IMediaItem et possèdera donc une propriété SourceUri contenant l’url de la vidéo à afficher.
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource controller}}" >
<MediaElement x:Name="mediaelement"
Source="{Binding Path=CurrentItem.SourceUri}"
/>
</Grid>
Il ne reste plus qu’à renseigner un nouvel item dans la playlist et de le sélectionner pour que la video se lancer.
using SLMedia.Video;
using SLMedia.Core;
namespace VideoPlayer1
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
controller = (VideoController)Resources["controller"];
this.Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
controller.Playlist.Add("http://labs.ucaya.com/lake.wmv");
controller.Next();
}
}
}
Pour rendre complétement opérationnel notre videocontroller, nous devons lui indiquer quel est le MediaElement qui affiche la vidéo. De cette manière il pourra récupérer les états de lecture, de progression et de buffering. Nous allons également lier quelques propriétés supplémentaires qui nous permettrons de tout contrôler depuis le VideoController.
<MediaElement x:Name="mediaelement"
Source="{Binding Path=CurrentItem.SourceUri}"
slvideo:VideoController.MediaElement="{StaticResource controller}"
IsMuted="{Binding Path=IsMuted}"
Volume="{Binding Path=Volume}"
AutoPlay="{Binding Path=AutoPlay}"
/>
Etape 2 : Lecture / Pause
L’état lecture / pause est géré dans notre controller par la propriété IsPlaying. Un binding entre une checkbox et cette propriété permetra de contrôler facilement l’état de lecture.
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" Background="#50000000" Margin="30">
<CheckBox IsChecked="{Binding Path=IsPlaying, Mode=TwoWay}" />
</StackPanel>
Etape 3 : Position / Durée
Un slider Bindé sur les propriétés Duration et Position fera l’affaire. Les propriétés Duration et Position étant du type TimeSpan, nous allons utiliser un IValueConverter pour transformer la valeur en Double.
<UserControl x:Class="VideoPlayer1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:slvideo="clr-namespace:SLMedia.Video;assembly=SLMedia.Video"
xmlns:slec="clr-namespace:SLExtensions.Controls;assembly=SLExtensions.Controls"
xmlns:sled="clr-namespace:SLExtensions.Data;assembly=SLExtensions.Data"
Foreground="White"
>
<UserControl.Resources>
<slvideo:VideoController x:Name="controller" />
<sled:TimeSpanConverter x:Name="timeSpanConverter"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource controller}}">
<MediaElement x:Name="mediaelement"
Source="{Binding Path=CurrentItem.SourceUri}"
slvideo:VideoController.MediaElement="{StaticResource controller}"
IsMuted="{Binding Path=IsMuted}"
Volume="{Binding Path=Volume}"
AutoPlay="{Binding Path=AutoPlay}"
/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" Background="#50000000" Margin="30">
<CheckBox IsChecked="{Binding Path=IsPlaying, Mode=TwoWay}" />
<slec:Slider Maximum="{Binding Converter={StaticResource timeSpanConverter}, Path=Duration}"
MoveValue="{Binding Converter={StaticResource timeSpanConverter}, Mode=TwoWay, Path=Position}"
Width="200" Margin="20,0,0,0"
/>
<TextBlock Text="{Binding Converter={StaticResource timeSpanConverter}, Path=Position}"
Margin="20,0,0,0"/>
<TextBlock Text=" / " />
<TextBlock Text="{Binding Converter={StaticResource timeSpanConverter}, Path=Duration}" />
</StackPanel>
</Grid>
</UserControl>
Si vous utilisez le slider de base de silverlight, vous noterez des problèmes lorsque vous déplacez le curseur. Pendant que vous déplacez le curseur, la propriété Value peut être remise à jour par le binding et le curseur donne l’impression de sauter.
Etape 4 : Volume
De même que pour la position, nous allons utiliser un slider pour le volume et une checkbox pour le mute
<CheckBox IsChecked="{Binding Path=IsMuted, Mode=TwoWay}" Margin="20,0,0,0"/>
<Slider Maximum="1" SmallChange="0.1" LargeChange="0.1"
Value="{Binding Path=Volume, Mode=TwoWay}" Width="50" />
Etape 5 : Buffering - Downloading
Encore du binding
<TextBlock Text="Buffering:" />
<TextBlock Text="{Binding Path=BufferingProgress, Mode=TwoWay, Converter={StaticResource strConverter}, ConverterParameter='{0:p}'}" />
<Slider Maximum="1"
Value="{Binding Path=BufferingProgress, Mode=TwoWay}" Width="50" IsHitTestVisible="False" Margin="5,0,0,0"/>
<TextBlock Text="Downloading:" Margin="20,0,0,0"/>
<TextBlock Text="{Binding Path=DownloadProgress, Mode=TwoWay, Converter={StaticResource strConverter}, ConverterParameter='{0:p}'}" />
<Slider Maximum="1"
Value="{Binding Path=DownloadProgress, Mode=TwoWay}" Width="50" IsHitTestVisible="False"
Margin="5,0,0,0"/>
Etape 6 : Gestion des états visuels
Le VideoController centralise les propriétés de notre player video. Il est possible de changer l’état visuel de nos contrôles en fonction des propriétés du VideoController. Nous allons utiliser le behaviors fournis par SLExtensions pour mapper ces propriétés à nos VisualStates.
<UserControl x:Class="VideoPlayer1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:slvideo="clr-namespace:SLMedia.Video;assembly=SLMedia.Video"
xmlns:slec="clr-namespace:SLExtensions.Controls;assembly=SLExtensions.Controls"
xmlns:sled="clr-namespace:SLExtensions.Data;assembly=SLExtensions.Data"
xmlns:inter="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity"
xmlns:slint="clr-namespace:SLExtensions.Interactivity;assembly=SLExtensions.Interactivity"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
Foreground="White"
>
<UserControl.Resources>
<slvideo:VideoController x:Name="controller" />
<sled:TimeSpanConverter x:Name="timeSpanConverter"/>
<sled:StringFormatConverter x:Name="strConverter"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource controller}}">
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="PlayingStates">
<vsm:VisualStateGroup.Transitions>
<vsm:VisualTransition GeneratedDuration="00:00:00.3000000"/>
</vsm:VisualStateGroup.Transitions>
<vsm:VisualState x:Name="Playing">
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="stackPanel" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#5039FF00"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Paused">
<Storyboard>
<ColorAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="stackPanel" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<SplineColorKeyFrame KeyTime="00:00:00" Value="#50D56700"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<inter:Interaction.Behaviors>
<slint:MapState Source="{StaticResource controller}" Property="IsPlaying" >
<slint:MapStateMapping StateName="Playing" Value="true"/>
<slint:MapStateMapping StateName="Paused" Value="false"/>
</slint:MapState>
</inter:Interaction.Behaviors>
<MediaElement x:Name="mediaelement"
Source="{Binding Path=CurrentItem.SourceUri}"
slvideo:VideoController.MediaElement="{StaticResource controller}"
IsMuted="{Binding Path=IsMuted}"
Volume="{Binding Path=Volume}"
AutoPlay="{Binding Path=AutoPlay}"
/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" Background="#50000000" Margin="30" x:Name="stackPanel">
<CheckBox IsChecked="{Binding Path=IsPlaying, Mode=TwoWay}" />
<slec:Slider Maximum="{Binding Converter={StaticResource timeSpanConverter}, Path=Duration}"
MoveValue="{Binding Converter={StaticResource timeSpanConverter}, Mode=TwoWay, Path=Position}"
Width="200" Margin="20,0,0,0"
/>
<TextBlock Text="{Binding Converter={StaticResource timeSpanConverter}, Path=Position}"
Margin="20,0,0,0"/>
<TextBlock Text=" / " />
<TextBlock Text="{Binding Converter={StaticResource timeSpanConverter}, Path=Duration}" />
<CheckBox IsChecked="{Binding Path=IsMuted, Mode=TwoWay}" Margin="20,0,0,0"/>
<Slider Maximum="1" SmallChange="0.1" LargeChange="0.1"
Value="{Binding Path=Volume, Mode=TwoWay}" Width="50" />
<TextBlock Text="Buffering:" />
<TextBlock Text="{Binding Path=BufferingProgress, Mode=TwoWay, Converter={StaticResource strConverter}, ConverterParameter='{0:p}'}" />
<Slider Maximum="1"
Value="{Binding Path=BufferingProgress, Mode=TwoWay}" Width="50" IsHitTestVisible="False" Margin="5,0,0,0"/>
<TextBlock Text="Downloading:" Margin="20,0,0,0"/>
<TextBlock Text="{Binding Path=DownloadProgress, Mode=TwoWay, Converter={StaticResource strConverter}, ConverterParameter='{0:p}'}" />
<Slider Maximum="1"
Value="{Binding Path=DownloadProgress, Mode=TwoWay}" Width="50" IsHitTestVisible="False"
Margin="5,0,0,0"/>
<CheckBox IsChecked="{Binding Path=IsFullscreen, Mode=TwoWay}" Margin="20,0,0,0" Content="Fullscreen" Foreground="White"/>
</StackPanel>
</Grid>
</UserControl>
Originally posted at
Testez moonlight sous Windows
Même si au final vous aurez une fenêtre faisant tourner moonlight sur votre windows, ce ne sera pas réellement le cas. L’idée expliquée dans ce post de Chris Cavanagh est d’utiliser Portable Ubuntu. Cette distribution d’Ubuntu tourne en tant que programme windows. Vous pourrez donc installer Moonlight sur le firefox d’ubuntu.
Being the Big Boss
En ce début de semaine www.beingthebigboss.com depasse les 14 000 inscrits !!
Venez nombreux prendre les commandes d’une multinationale !
Et n’oubliez pas si vous êtes étudiant le concours www.bigbosstrophy.com se prépare. Inscrivez-vous !
Originally posted at
France Télévisions – L’info en vidéo
Nouvelle version de l’application Silverlight L’info en vidéo.
Vous pouvez maintenant retrouver les vidéos des différentes émissions !
Originally posted at
France Télévisions – L’info en vidéo
Encore une nouvelle version pour l’application L’info en vidéo.
L’accès aux journaux des régions simplifié par l’affichage de celles-ci sur une carte de France :-)
Et un accès direct aux différents journaux de chaque région :
Gestion de la mémoire sous Silverlight / .Net
On m’a interrogé récemment sur la libération de la mémoire des objets en silverlight. Je vais essayer de synthétiser ici ce que j’ai répondu.
Silverlight réagi exactement de la même manière que le framework .Net. La mémoire est gérée par un garbage collector (ramasse-miettes) qui libère les allocations mémoires des objets une fois qu’ils ne sont plus référencés.
L’application de test
Voila le principe de mon application de test. Au clic sur un bouton, mon application ajoute un usercontrol dans l’arbre visuel et au clic sur un autre bouton, je l’enlève. La seule référence vers mon contrôle est faite par l’arbre graphique. Dans le constructeur de mon usercontrol, j’incrémente un compteur qui m’indique le nombre d’instances en cours et dans le destructeur je le décrémente. Ce compteur est affiché par binding dans l’interface graphique.
Petite parenthèse pour le fonctionnement de l’application, la destruction des objets ne se passe pas dans le thread graphique, pour que la mise a jour du binding fonctionne, je lève l’événement PropertyChanged de AppController par le SynchronizationContext récupéré du thread graphique.
Contrôle sans références
A l’ajout de mon usercontrol, on note bien l’incrémentation du compteur.
Je clic sur le bouton Remove et je vois que mon compteur est toujours à 1. Le garbage collector ne passe pas tout de suite. Il y a un algorithme qui détermine quand le garbage collector doit passer sur les objets. Pour le besoin de mon application, je vais forcer l’appel au garbage collector au clic sur le bouton Force GC. Il faut noter que c’est une mauvaise pratique que de vouloir appeler soit même de garbage collector. GC.Collect() ne doit être appelée que si vous en avez réellement l’utilité. Un appel trop fréquent peut dégrader les performances de votre application.
Le clic sur Force GC ramène bien le compte de référence à 0.
Contrôle s’abonnant à un événement
Comme je vous le précisait dans le fonctionnement de mon application, je ne maintient aucune référence vers mes usercontrol, je n’ai théoriquement rien qui va empêcher le garbage collector de passer. Cependant le contrôle lui même peut faire qu’il ne sera pas libéré. Ici ma classe AppController fourni un événement auquel mes contrôles peuvent s’abonner. MyControl2 va s’abonner à cet événement. Au clic sur le bouton Raise Test event, vous verez que la couleur du usercontrol change.
Si vous ajoutez le usercontrol MyControl2, que vous l’enlevez et que vous forcez le garbage collector, vous verrez que l’instance ne se libère pas. Que se passe-t-il ?
Lorsque le contrôle s’est abonné à l’événement de AppController, il a créé un EventHandler (une référence vers une fonction) et l’a ajouté à la liste des fonctions à appeler lorsque l’événement est levé. Un événement n’est rien d’autre qu’une liste de références qui pointe vers les fonctions à appeler. Pour que notre contrôle se libère, il faut donc supprimer cette référence. Un clic sur Clear Test event list demande à AppController de vider sa liste d’événement, un clic sur Force GC remettra les compteurs à 0.
IDisposable
Une bonne pratique pour gérer la libération de ressources internes et le désabonnement d’évenement est d’implémenter l’interface IDisposable. Cette interface vous demande d’implémenter la fonction Dispose dans laquelle vous pouvez remettre à null les références externes, vous désabonnez d’événements, etc…
Le usercontrol MyControl3 implémente cette interface. Lorsque le bouton Remove First Item est cliqué, mon application vérifie si le contrôle à enlever implémente IDisposable et l’appel le cas échéant.
Les clics successifs sur Add MyControl3, Remove First Item et Force GC incrémentent et décrémentent bien notre compteur de références.
WeakReference et proxy d’événement
Si vous ne maitrisez pas la logique d’ajout suppression des contrôles, vous ne pouvez pas toujours appeler la IDisposable.Dispose. (A noter que ce n’est pas parce qu’un contrôle n’est plus dans l’arbre d’affichage qu’il faut nécessairement le disposer. Un tabcontrol ajoute et enlève chaque tab de l’abre graphique.)
La classe WeakReference permet de maintenir une référence “qui ne compte pas” du point de vue du garbage collector. La propriété Target de la WeakReference vaut null quand le garbage collector est passé et renvoi l’instance de l’objet sinon.
Ici pour gérer notre abonnement à l’événement, nous allons utiliser ces WeakReference pour pouvoir quand même être libéré. La fonction à appeler en retour de l’événement est : MyControl4.Instance_TestEvent. Cette fonction est privée. WeakListener est une classe interne à MyControl4 et va donc pouvoir appeler cette fonction privée. C’est WeakListener.Instance_TestEvent que nous allons abonner à l’événement de AppController. Lors de l’appel de l’événement, elle va vérifier que sa WeakReference vers MyControl4 est toujours valide, si tel est le cas, cette fonction va transmettre l’appel vers la fonction MyControl4.Instance_TestEvent, sinon elle se désabonne de l’appelant et pourra donc aussi être collecté par le garbage collector.
public partial class MyControl4 : UserControl
{
private class WeakListener
{
public WeakListener(MyControl4 target)
{
weakReference = new WeakReference(target);
}
private readonly WeakReference weakReference;
public void Instance_TestEvent(object sender, EventArgs e)
{
MyControl4 ctrl = (MyControl4)weakReference.Target;
if (ctrl != null)
{
ctrl.Instance_TestEvent(sender, e);
}
else
{
((AppController)sender).TestEvent -= this.Instance_TestEvent;
}
}
}
void Instance_TestEvent(object sender, EventArgs e)
{
this.LayoutRoot.Background = new SolidColorBrush(GetRandomColor());
}
…
}
Un clic sur Add MyControl4, Remove et Force GC vous donnera un compte de référence de MyControl4 à 0. Il faut noter qu’à ce stade, MyControl4 a été libéré mais le WeakListener est toujours présent en mémoire. Sa libération ne sera effectué que lorsque l’événement sera appelé, la classe s’autodésabonnera toute seule de l’événement et pourra est collectée.
WeakEventList
J’ai ajouté dans slextensions une WeakEventList permettant de gérer une liste d’événements basés sur des WeakReference. A la place de stocker les événements par le processus standard de .Net, nous créons une liste de WeakReference directement sur les delegates abonnés.
Voila la déclaration de l’événement de ma class AppController.
private WeakEventList<EventArgs> weakTestEvent;
private event EventHandler<EventArgs> testEvent;
public event EventHandler<EventArgs> TestEvent
{
add
{
if(UseWeakEvent)
{
weakTestEvent.Event += value;
}
else
{
testEvent += value;
}
}
remove
{
if (UseWeakEvent)
{
weakTestEvent.Event -= value;
}
else
{
testEvent -= value;
}
}
}
En cochant la case UseWeakEvent et en reproduisant le cas qui posait problème au début de ce post ( Add MyControl2, Remove, Force GC), on voit que la référence est bien libérée.
Vous trouvez plusieurs autres manières de créer des “weakevent” sur internet. Celle-ci est la seule qui marche en silverlight pour des appels à des fonctions privées. De nombreuses utilisent la réflexion pour pouvoir accéder à la fonction finale à appeler par l’événement, ceci n’est pas permit en silverlight si la déclaration est privée. Le mode MediumTrust de silverlight nous en empêche.
Originally posted at
Silverlight Extensions
Comme vous le savez les 2 projets Silverlight Extensions et Silverlight Contrib sont en cours de fusion :
- Silverlight Extensions et Silverlight Contrib
- The Future of Silverlight Contrib
- MVPs Shine Silverlight Together at MVP Global Summit!
Pour l’occasion nous avons mis en place le nouveau site de Silverlight Extensions sous N2 CMS et le futur Blog de Silverlight Extensions sous BlogEngine.NET.
* En attendant que la fusion avance, n’oubliez pas d’aller sur les sites des deux projets Silverlight Extensions et Silverlight Contrib.
Originally posted at
UCAYA Tour Next 7
Originally posted at
Projet Silverlight "Being the Big Boss"
Depuis le 8 décembre la version 1 de www.beingthebigboss.com est en ligne !
Il s’agit d’un jeu de simulation économique et d’entreprise, à “But récréatif et pédagogique : Mettez vous dans la peau d'un PDG d'une multinationale et construisez un empire reconnu et respecté !”.
Being the Big Boss est réalisé pour la partie client en Silverlight 2. Pour la partie serveur SQL Server 2008, LinqToEntities et WCF pour la communication Client/Serveur.
Quelques infos supplémentaires :
- Côté Silverlight il s’appuie sur la librairie SLExtensions ;-)
- Le modèle UML a été créé avec ArgoUML
- La génération du mapping est réalisé avec EF.Wizard
Pour vous donner un avant goût :
Silverlight Extensions et Silverlight Contrib
Comme nous vous le disions dans un précédent post et comme vous avez pu le voir sur les sites respectifs des deux projets, les 2 projets sont en cours de fusion. Le nom du projet résultant de cette fusion a été publié par Page Brooks sur un son blog. Un vote était organisé et c’est le nom silverlight extensions qui a remporté la majorité des voix. Retrouvez le post de Page Brooks
En attendant que la fusion avance, n’oubliez pas d’aller sur le site web des deux projets. slextensions et silverlight contrib
Utilisation des behaviors pour localiser une application silverlight ou WPF
Il y a quelques temps déjà, j’ai ajouté à slextensions une classe permettant de localiser simplement une application à partir d’attachedproperty. La mode étant aux behaviors, j’ai rajouté une classe permettant cette localisation à la branche SL3 de slextensions. Cette classe possède 4 propriétés :
- Key : la clé de la ressource provenant de votre ResourceManager
- PropertyName : Le nom de la propriété à localiser
- ResourceManagerKey : La clé avec laquelle retrouver le ResourceManager dans les ressources de l’application
- ConvertXaml : Si la resource contient du Xaml, permet d’instancier ce xaml par le XamlReader.
L’utilisation est très simple voici un exemple de localisation d’un texte
<TextBlock >
<i:Interaction.Behaviors>
<SLExtensions_Interactivity:Localize Key="WelcomeMessage" PropertyName="Text" ResourceManagerKey="MyResource" />
</i:Interaction.Behaviors>
</TextBlock>
et d’un ContentControl
<ContentControl Height="121" Width="244" Canvas.Left="25" Canvas.Top="206" >
<i:Interaction.Behaviors>
<SLExtensions_Interactivity:Localize Key="Welcome" PropertyName="Content" ResourceManagerKey="MyResource" ConvertXaml="True"/>
</i:Interaction.Behaviors>
</ContentControl>
Le code de la classe :
using System;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Expression.Interactivity;
using System.Resources;
using System.Globalization;
using System.Windows.Markup;
using SLExtensions.Globalization;
namespace SLExtensions.Interactivity
{
public class Localize : Behavior<FrameworkElement>
{
private string key;
public string Key
{
get
{
return this.key;
}
set
{
if (this.key != value)
{
this.key = value;
Refresh();
}
}
}
private bool convertXaml;
public bool ConvertXaml
{
get
{
return this.convertXaml;
}
set
{
if (this.convertXaml != value)
{
this.convertXaml = value;
Refresh();
}
}
}
private DependencyProperty dependencyProperty;
private string propertyName;
public string PropertyName
{
get
{
return this.propertyName;
}
set
{
if (this.propertyName != value)
{
this.propertyName = value;
this.dependencyProperty = null;
Refresh();
}
}
}
private string resourceManagerKey;
public string ResourceManagerKey
{
get
{
return this.resourceManagerKey;
}
set
{
if (this.resourceManagerKey != value)
{
this.resourceManagerKey = value;
}
}
}
private void Refresh()
{
if (AssociatedObject == null
|| string.IsNullOrEmpty(propertyName)
|| string.IsNullOrEmpty(Key)
|| string.IsNullOrEmpty(ResourceManagerKey))
return;
if (dependencyProperty == null)
{
Type t = AssociatedObject.GetType();
var field = t.GetField(propertyName + "Property", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (field != null)
{
dependencyProperty = field.GetValue(null) as DependencyProperty;
}
}
if (dependencyProperty != null
&& Application.Current != null)
{
ResourceManager resourceManager = null;
var obj = Application.Current.Resources[ResourceManagerKey];
if (obj is ResourceLoader)
{
resourceManager = ((ResourceLoader)obj).GetResourceManager();
}
else
{
resourceManager = obj as ResourceManager;
}
var resourceValue = resourceManager.GetObject(Key, CultureInfo.CurrentCulture);
if (convertXaml && resourceValue is string)
{
resourceValue = XamlReader.Load((string)resourceValue);
}
AssociatedObject.SetValue(dependencyProperty, resourceValue);
}
}
protected override void OnAttached()
{
Refresh();
base.OnAttached();
}
protected override void OnDetaching()
{
base.OnDetaching();
}
}
}
Originally posted at
Concours du jeu des caméléons
Le concours du jeu des caméléons de bonne année 2009 s’est achevé le 31 mars.
Notre grand vainqueur est originaire des Caraïbes. Pour l’occasion j’ai tenu à lui remettre son lot en main propre :-)
Il a nettement plus la classe maintenant non ?! ;-)
Merci d’avoir participé !
Les behavior en Silverlight 3 - MapState behavior
Exemple
Les behaviors introduits par blend 3 servent à étendre les fonctionnalités d’un contrôle à l’aide d’une nouvel élément que l’on peut poser graphiquement dans l’interface de blend. L’ajout de comportement n’est pas nouveau, c’est utilisé dans de nombreux projets (par exemple, la navigation deepzoom de slextensions est géré de cette manière), mais ici, l’intégration à blend est plutôt pratique et permet à un intégrateur de gérer du comportement sans écrire de code.
La classe MapState me permet de mapper l’état d’une propriété d’un contrôleur à un VisualState posé sur mon contrôle.
Ma classe contrôleur de test n’a rien de particulier. Voici sa définition.
public class ApplicationController : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private TestStates state;
public TestStates State
{
get { return state; }
set
{
if (state != value)
{
state = value;
OnPropertyChanged(StatePropertyName);
}
}
}
public const string StatePropertyName = "State";
}
J’instancie ce contrôleur en l’injectant dans les ressources de l’application
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="TestBehavior.App"
xmlns:controllers="clr-namespace:TestBehavior.Controllers"
>
<Application.Resources>
<controllers:ApplicationController x:Key="appController" />
</Application.Resources>
</Application>
Je rajoute les références qui vont bien à mon projet
Je peux maintenant rajoute mon behavior à l’aide de blend
Je remplie le mapping et la source de mon behavior avec blend
Le morceau de code qui change l’état du contrôleur.
private void Button_Click(object sender, RoutedEventArgs e)
{
ApplicationController appController = Application.Current.Resources["appController"] as ApplicationController;
appController.State = (TestStates) ((1 + (int)appController.State) % 3);
}
Le code
Ma classe MapState hérite d’une classe ancêtre Behavior que vous trouverez dans l’assembly Microsoft.Expression.Interactivity.dll qui se trouve dans le dossier Libraries/Silverlight de Blend 3.
J’ai déclaré une propriété Source qui me sert à récupérer l’objet métier qui va me donner mon état et une propriété Property me donnant le nom de la propriété d’état. Ma source doit bien sur implémenter INotifyPropertyChanged pour que MapState soit notifiée du changement de propriété.
J’ai également une collection de MapStateMapping qui me fournisse un couple Valeur / VisualState.
Au changement de valeur sur ma source, je n’ai plus qu’à comparer par rapport aux valeurs de mapping est à déclancher le visualstate sur mon élément associé.
Le code complet de la classe :
namespace SLExtensions.Interactivity
{
[ContentProperty("Mappings")]
public class MapState : Behavior<FrameworkElement>
{
public MapState()
{
Mappings = new List<MapStateMapping>();
}
#region Source
private PropertyInfo propertyInfo = null;
public object Source
{
get
{
return (object)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
/// <summary>
/// Source depedency property.
/// </summary>
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register(
"Source",
typeof(object),
typeof(MapState),
new PropertyMetadata((d, e) => ((MapState)d).OnSourceChanged((object)e.OldValue, (object)e.NewValue)));
/// <summary>
/// handles the SourceProperty changes.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
private void OnSourceChanged(object oldValue, object newValue)
{
propertyInfo = null;
INotifyPropertyChanged notifyingObject = oldValue as INotifyPropertyChanged;
if (notifyingObject != null)
{
notifyingObject.PropertyChanged -= new PropertyChangedEventHandler(notifyingObject_PropertyChanged);
}
notifyingObject = newValue as INotifyPropertyChanged;
if (notifyingObject != null)
{
notifyingObject.PropertyChanged += new PropertyChangedEventHandler(notifyingObject_PropertyChanged);
}
if (newValue != null && !string.IsNullOrEmpty(Property))
{
propertyInfo = newValue.GetType().GetProperty(Property);
}
RefreshState();
}
#endregion Source
#region Property
public string Property
{
get
{
return (string)GetValue(PropertyProperty);
}
set
{
SetValue(PropertyProperty, value);
}
}
/// <summary>
/// Property depedency property.
/// </summary>
public static readonly DependencyProperty PropertyProperty =
DependencyProperty.Register(
"Property",
typeof(string),
typeof(MapState),
new PropertyMetadata((d, e) => ((MapState)d).OnPropertyChanged((string)e.OldValue, (string)e.NewValue)));
/// <summary>
/// handles the PropertyProperty changes.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
private void OnPropertyChanged(string oldValue, string newValue)
{
if (Source == null)
propertyInfo = null;
else
propertyInfo = Source.GetType().GetProperty(newValue);
RefreshState();
}
#endregion Property
public List<MapStateMapping> Mappings { get; private set; }
void notifyingObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == Property)
RefreshState();
}
private void RefreshState()
{
// Do not refresh at desing time
if (!HtmlPage.IsEnabled)
return;
if (Source != null && propertyInfo != null )
{
object propValue = propertyInfo.GetValue(Source, null);
if (propValue == null)
{
var nullMappings = from m in Mappings
where m.Value == null
|| ((m.Value is string) && string.Empty.Equals(m.Value))
select m.StateName;
try
{
SLExtensions.Controls.Animation.VisualState.GoToState(AssociatedObject, UseTransitions, nullMappings.ToArray());
}
catch
{
}
}
else
{
MapStateMapping mapping = null;
foreach (var item in Mappings)
{
var value = item.Value;
if (value != null)
{
if (object.Equals(value, propValue))
{
mapping = item;
break;
}
if (propValue is IConvertible && value is IConvertible)
{
try
{
var val2 = Convert.ChangeType(propValue, value.GetType(), CultureInfo.InvariantCulture);
if (object.Equals(val2, value))
{
mapping = item;
break;
}
}
catch
{
}
try
{
var val2 = Convert.ChangeType(value, propValue.GetType(), CultureInfo.InvariantCulture);
if (object.Equals(val2, propValue))
{
mapping = item;
break;
}
}
catch
{
}
}
}
}
if (mapping != null)
{
try
{
SLExtensions.Controls.Animation.VisualState.GoToState(AssociatedObject, UseTransitions, mapping.StateName);
}
catch
{
}
}
}
}
}
/// <summary>
///
/// </summary>
public static readonly DependencyProperty UseTransitionsProperty = DependencyProperty.Register("UseTransitions", typeof(bool), typeof(MapState), new PropertyMetadata(true));
/// <summary>
/// True if transitions should be used for the state change.
/// </summary>
public bool UseTransitions
{
get { return (bool)this.GetValue(MapState.UseTransitionsProperty); }
set { this.SetValue(MapState.UseTransitionsProperty, value); }
}
/// <summary>
/// Hooks up necessary handlers for the state changes.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
FrameworkElement element = this.AssociatedObject;
if (element != null)
{
element.Loaded += new RoutedEventHandler(element_Loaded);
RefreshState();
}
else
{
Dispatcher.BeginInvoke(delegate
{
this.OnAttached();
});
}
}
void element_Loaded(object sender, RoutedEventArgs e)
{
RefreshState();
}
}
}
Les sources
En attendant de publier tout ça sur slextensions, vous pouvez télécharger le projet ici:
Vous pouvez retrouver d’autres behavior sur le site de la gallery expression : http://gallery.expression.microsoft.com/
silverlight 3 beta et silverlight 2 sur la même machine de dev
Travailler avec des versions beta de logiciels est parfois compliqué. Que faire avec les outils ? Est-ce que je les installe sur ma machine de développement, est-ce que je les installe sur une machine virtuelle ?
Vous trouverez ici : http://blogs.msdn.com/amyd/archive/2009/03/18/switching-from-silverlight-3-tools-to-silverlight-2-tools.aspx un petit script permettant de changer simplement la version des Silverlight tools utilisés par visual studio.
Originally posted at
MIX09
MIX09 à Las Vegas, nous y étions !
De retour en france, voici une petite liste de liens en français dont les blogs des frenchies avec qui nous avons eu la chance de vivre cette magnifique expérience :
Pierre Lagarde : http://blogs.msdn.com/pierlag/
Dick Lantim : http://blogs.msdn.com/dicklant/
Christophe Lauer : http://blogs.msdn.com/clauer/
David Rousset : http://blogs.msdn.com/davrous/
LeMixer : http://lemixer.fr/
Originally posted at
Silverlight Contrib et Silverlight Extensions fusionnent
Nous sommes fières d’annoncer aujourd’hui la fusion de Silverlight Contrib et Silverlight Extensions en un seul et unique projet ! Nous pensons que cette fusion apportera beaucoup de bénéfic es à la communauté. Cette combinaison permettra aux développeurs d’avoir un point unique pour rechercher des contrôles à intégrer dans leurs applications ainsi que pour en soumettre de nouveaux. Cette action réduira le temps passé dans chaque projet pour vous fournir des fonctionnalités équivalentes et nous permettra d’accélérer la cadence des releases.
Pendant la phase de consolidation du code, nous vous encourageons à nous soumettre vos retours ou problèmes à chaque projets. Nous fusionnerons les informations des deux projets lorsque le site commun sera ouvert et le nom final déterminé.
Quel nom allons-nous garder ? Cette décision vous appartient ! Vos votes détermineront le plus populaire. Assurez-vous que votre préférence soit prise en compte, votez ! Les votes seront acceptés jusqu’au 29 mars 2009 23:00 (GMT+1)
Nous vous tiendrons informé des évolutions sur chacun des blogs des 2 projets Silverlight Contrib et Silverlight Extensions.
Retrouvez ce post sur le site de Silverlight Contrib
Les équipes de slextensions et silverlight contrib.