Surface : Contrôle Stick Analogique

by Nicolas Calvi 9. octobre 2010 18:54

Je vous propose aujourd'hui un petit contrôle Microsoft Surface (WPF) pour vos projets. C'est un stick analogique tactile, vous pouvez télécharger les sources ci-dessous :

Black.StickAnalogique.rar (9,24 kb)

Le contrôle est très simple à utiliser, il possède deux propriétés de dépendances : ValueX et ValueY qui représentent une valeur entre -1 et 1. Ces valeurs correspondent à la distance parcouru par le stick analogique, respectivement sur l'axe X et sur l'axe Y par rapport au centre. Il faut noter par contre que les axes sont inversés, à savoir :

- Sur l'axe X, -1 correspond à la position gauche et 1 à la position droite.

- Sur l'axe Y, -1 correspond à la position haute et 1 a la position basse.

Il y a aussi deux événements, ValueXChange et ValueYChange qui se déclenchent à chaque modification de ValueX pour le premier et ValueY pour le second.

J'espère que ce composant vous sera utile, je le rajouterais dans le toolkit bientôt.

SurfaceItemsControl comment ça marche ?

by Nicolas Calvi 4. octobre 2010 10:00
Il arrive souvent lors d'un développement Microsoft Surface, de devoir créer des contrôles personnalisés pour affiner l'expérience NUI (Natural User Interface) de notre application. La plupart du temps on va créer un SurfaceUserControl ou un CustomControl, mais d'expérience il arrive souvent de devoir créer un contrôle de contenu qui fonctionne à la manière d'une ListBox, ComboBox ou ScatterView. Je vais donc expliquer comment créer ce genre de contrôle en héritant d'un SurfaceItemsControl (ItemsControl en WPF, la démarche est identique).

Concept de base :

Un SurfaceItemsControl n'est qu'un container qui permet d'afficher et de gérer des éléments de diverses nature et de les afficher et les traiter d'une façon particulière. Comme pour tout container Surface (ou WPF), la propriété ItemsSource permet, par Binding notammentde lui adresser la liste des éléments à traiter. Au moment de traiter la liste de ses éléments, l'ItemsControl va encapsuler chaque élément de la liste (chaque élément de l'arbre logique) dans un container de type ContentControl afin de les traiter de façon unifiée dans son arbre visuel, cependant il restera accessible en l'état dans l'arbre logique. Là ou ça deviens intéréssant, c'est que l'on peut redéfinir ce container et lui donner des comportements que nous jugeons utiles.

Voici les différentes étapes pour implémenter correctement ce type de contrôle.

Créer un CustomControl :

Commencez par créer un CustomControl et faites le hériter de SurfaceItemsControl. A ce moment précis vous avez déjà les mécanismes de base, à savoir tout le comportement lié à la gestion des objets (ItemsSource) et vous avez un affichage basique. Ce que l'on aimerai bien, c'est pouvoir redéfinir toute la gestion du SurfaceItemsControl. La première étape est de redéfinir le contrôle lui même, pour cela il faut redéfinir son style. Modifier le fichier generic.xaml afin de lui donner l'aspect que vous souhaitez.
using Microsoft.Surface.Presentation;
using Microsoft.Surface.Presentation.Controls;

namespace MonAssembly
{
  public class MonControle: SurfaceItemsControl
  {
    public MonControle()
    {
    }
  }
}
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    xmlns:local="clr-namespace:MonAssembly">
<Style TargetType="{x:Type local:MonControle}">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type local:MonControle}">
        <Canvas IsItemsHost="True"
          Width="{TemplateBinding Width}" 
          Height="{TemplateBinding Height}"
          Background="{TemplateBinding Background}"
          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
        </Canvas>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>
Ensuite il faut créer un ContentControl pour jouer le rôle du container utilisé dans le SurfaceItemsControl. Pour cela il suffit de refaire un CustomControl et d'y mettre un ContentPresenter.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:controls="clr-namespace:MonAssembly">
    <Style TargetType="{x:Type controls:MonContainer}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type controls:MonContainer}">
                    <ContentPresenter />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>
Une fois la base créée il reste à surcharger les différentes méthodes qui permettront au contrôle de bien fonctionner.

Implémenter les surcharges :

De base le SurfaceItemsControl gère de façon automatique l'encapsulation des Items dans un ContentControl, il faut donc lui indiquer que vous voulez changer ce ContentControl de base pour le remplacer par le vôtre. Pour ce faire il fait surcharger une série de fonction.

> protected override void ClearContainerForItemOverride(DependencyObject element, object item)

Permet d'exécuter des actions au moment ou un objet dans la source est supprimée de celle-ci.

> protected override DependencyObject GetContainerForItemOverride()

Permet d'instancier votre propre container, pour remplacer celui par défaut.
protected override DependencyObject GetContainerForItemOverride()
{
  return (new MonContainer());
}
> protected override bool IsItemItsOwnContainerOverride(object item)

Permet à au SurfaceItemsControl de vérifier si une instance est du même type que votre container.
protected override bool IsItemItsOwnContainerOverride(object item)
{
  return (item is MonContainer);
}
> protected override void PrepareContainerForItemOverride(DependencyObject p_oElement, object p_oItem)

Permet d'effectuer des traitements après que notre objet est été associé à notre container personnalisé. Il est à noter qu'il est important de bien faire le base.PrepareContainerForItemOverride avant d'effectuer ses traitements. En effet, c'est dans l'exécution du code en amont que le SurfaceItemsControl fait son association, si vous ne l'appelez pas, l'association ne sera pas faite.
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
  // Appel au parent
  base.PrepareContainerForItemOverride(element, item);
  
  // Mettre ici votre code de préparation
}

Conclusion :

Si vous suivez bien ces étapes il est très facile d'implémenter ce type de contrôle, entraînez-vous sur des exemples simples et tester divers possibilités, au final ce sera toujours à peu près la même chose.
Ce type de contrôle a vraiment une réelle valeur ajoutée et permet de multiple représentations (verticale, surface plane, liste, etc.)

Lire du son avec DirectX

by Nicolas Calvi 30. septembre 2010 12:19

Aujourd'hui je vous propose une petite classe que j'ai écrite et qui permet de jouer un son en WPF (et donc par extension sur Microsoft Surface). En effet, par expérience je n'était pas satisfait des objets (MediaElement, SoundPlayer, etc.) fournis par défaut dans le Framework .Net car soit ils leurs manquent des fonctionnalités (par exemple la durée du média pour le SoundPlayer), soit ils sont compliqués a mettre en place (MediaElement qui doit forcément être dans l'arbre visuel). J'ai donc pris le problème a bras le corps et décidé d'écrire un petit bout de code avec les assemblies DirectX Managées.

La classe est simple et fait des opérations basiques, a savoir lire un fichier audio sur le disque dur et nous notifie quand elle commence la lecture ou l'arrête. On peut bien sûr allez plus loin, mais je donne ici plus un squelette qu'une classe ultra complète. Pour l'utiliser, il faut installer le SDK DirectX pour pouvoir utiliser l'assembly Microsoft.DirectX.AudioVideoPlayback. Si vous ne désirez pas installer le SDK je vous la fournis ci-dessous.

DirectAudioPlayer.cs (3,95 kb)

AudioVideoPlayback.rar (20,98 kb)

L'avantage de cette petite classe c'est qu'en passant par DirectX, on bénéficie des codecs installés sur son poste car cela passe par DirectShow. En espérant que cela vous sera utile.

Surface : Affine2DInertiaProcessor

by Nicolas Calvi 28. septembre 2010 00:25

Pour faire suite à mon dernier billet, je vais maintenant expliquer une autre classe du SDK Microsoft Surface, l'Affine2DInertiaProcessor. Cette classe permet de gérer de l'inertie sur des objets. Comme pour l'Affine2DManipulationProcessor, cette classe s'utilise quasiment pareil.

La principale utilisation de cette classe réside dans l'ajout d'une inertie sur un objet suite à une manipulation (le plus souvent grâce à un Affine2DManipulationProcessor) pour ajouter du réalisme dans son interface NUI (Natural User Interface).

Le code ci-dessous vous montre comment utiliser l'Affine2DInertiaProcessor après une manipulation.

using System;
using Microsoft.Surface.Presentation;
using Microsoft.Surface.Presentation.Controls;
using Microsoft.Surface.Presentation.Manipulations;

namespace MonApplication
{
  public partial class MaSurfaceWindow: SurfaceWindow
  {
    private Affine2DManipulationProcessor traker;
    private Affine2DInertiaProcessor traker;

    public MaSurfaceWindow()
    {
      this.InitializeComponent();

      // On défini ce que la classe doit scruter
      traker = new Affine2DManipulationProcessor(Affine2DManipulations.Scale | Affine2DManipulations.Rotate | Affine2DManipulations.TranslateX | Affine2DManipulations.TranslateY, this);

      // On s'abonne aux handlers pour connaitre l'état des contacts
      // et les gestuelles qui en ressortent
      traker.Affine2DManipulationStarted += new EventHandler<Affine2DOperationStartedEventArgs>(this.traker_Affine2DManipulationStarted);
      traker.Affine2DManipulationDelta += new EventHandler<Affine2DOperationDeltaEventArgs>(this.traker_Affine2DManipulationDelta);
      traker.Affine2DManipulationCompleted += new EventHandler<Affine2DOperationCompletedEventArgs>(this.traker_Affine2DManipulationCompleted);

      // Création de la classe pour la gestion de l'inertie
      inertia = new Affine2DInertiaProcessor();

      // On s'abonne aux handlers pour connaitre l'état de l'inertie
      inertia.Affine2DInertiaCompleted += new EventHandler<Affine2DOperationCompletedEventArgs>(this.Inertia_Affine2DInertiaCompleted);
      inertia.Affine2DInertiaDelta += new System.EventHandler<Affine2DOperationDeltaEventArgs>(this.Inertia_Affine2DInertiaDelta);
    }

    private void Grid_ContactDown(object sender, ContactEventArgs e)
    {
      // On indique a l'Affine2DManipulationProcessor qu'il
      // doit scruter le contact passé en paramètre
      traker.BeginTrack(e.Contact);
    }

    private void traker_Affine2DManipulationStarted(object sender, Affine2DOperationStartedEventArgs e)
    {
      // Ici le code quand la manipulation commence
    }

    private void traker_Affine2DManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
    {
      // Ici le code a chaque changement d'état d'un contact
    }

    private void traker_Affine2DManipulationCompleted(object sender, Affine2DOperationCompletedEventArgs e)
    {
      // Ici le code a la fin de la manipulation

      // On renseigne l'objet d'inertie avec les informations de fin de manipulation
      inertia.InitialAngularVelocity = e.AngularVelocity;
      inertia.InitialExpansionVelocity = e.ExpansionVelocity;
      inertia.InitialOrigin = e.ManipulationOrigin;
      inertia.InitialVelocity = e.Velocity;

      // On démarre l'inertie
      inertia.Begin();
    }

    private void Inertia_Affine2DInertiaCompleted(object sender, Affine2DOperationCompletedEventArgs e)
    {
      // Ici le code pour traiter la fin de l'inertie
    }

    private void Inertia_Affine2DInertiaDelta(object sender, Affine2DOperationDeltaEventArgs e)
    {
      // Ici le code a chaque changement d'état de l'objet pendant l'inertie
      // Les informations sont les mêmes que ceux du Delta de la manipulation
    }
  }
}

On remarquera que cette classe fonctionne comme la manipulation, a savoir deux événements pour la fin et la modification de l'inertie. L'événement de démarrage de l'inertie n'existe pas car cela est fait de façon programmatique. De plus, la majorité des informations que demande l'Affine2DInertiaProcessor sont fournis par l'argument de fin de manipulation.

C'est une façon simple de gérer un mouvement d'inertie. 

Projet CodePlex : Surface Toolkit

by Nicolas Calvi 21. septembre 2010 00:30

Je vous présente mon premier projet CodePlex, un Toolkit pour Microsoft Surface. Il contient une série de contrôles, behaviors, effects et utilitaires pour vos projets Surface. Vous avez les sources, c'est libre de droit, j'attend vos retours avec impatience, enjoy :)

Ca se télécharge ici : http://blacksurfacetoolkit.codeplex.com/