Este artigo foi traduzido por máquina.

Aux frontières de l’interface utilisateur

Défilement en couleur pour XNA

Charles Petzold

Télécharger l'exemple de code

L'un des premiers programmes de Windows que j'ai écrit pour la publication a été appelé COLORSCR (“ couleur défilement ”), et il est apparu dans le numéro de mai 1987 de Microsoft Systems Journal , le prédécesseur de ce magazine.

Au fil des années, j'ai souvent trouvé il instructif de réécrire ce programme pour d'autres API et les structures. Bien que le programme est simple, vous manipulez des trois barres de défilement ou les curseurs pour les valeurs rouges, verts et bleus créer une couleur personnalisée, elle implique les tâches cruciales telles que la mise en page et de gestion des événements. Fonctionnalités, de même, le programme n'est pas strictement une démonstration simple, n'est pas significative. Si vous avez déjà trouvé vous-même créer une boîte de dialogue Sélection de couleurs, ce programme est un bon point de départ.

Dans cet article, je veux vous montrer comment écrire un programme de défilement de la couleur à l'aide de XNA Framework tel qu'implémenté dans Windows 7 de téléphone. Le résultat est illustré dans les de la figure 1 .

Figure 1 Le programme Xnacolorscroll

XNA est le code de Microsoft basée sur .NET Framework managé pour les jeux de programmation, mais si vous démarrez la recherche dans les espaces de noms Microsoft.XNA.Framework, vous ne trouverez pas des barres de défilement ou les curseurs ou les boutons même parmi les classes ! XNA est tout simplement pas ce type de cadre. Si vous souhaitez que d'un curseur de votre programme, il doit être créé spécialement pour ce travail.

Vous montrant comment écrire un programme de défilement de la couleur XNA est simplement un exercice académique, cependant. Mon intention est en fait beaucoup plus large.

Comme vous le savez, Windows téléphone 7 prend en charge de Silverlight et XNA et généralement pour une application particulière, le choix entre les deux est évident. Toutefois, vous pouvez trouver que les demandes de graphiques de votre application sont tout simplement trop pour que Silverlight, mais en même temps que vous pourriez hésiter à propos à l'aide de XNA au lieu de cela, car il manque des équipements familiers, tels que des boutons et des curseurs.

Ma réponse est : Ne soyez pas peur. L'écriture de contrôles simples de XNA est plus simple que vous pensez probablement.

Vous pouvez également envisager de cet article en tant qu'une introduction à XNA que les programmeurs de programmation pour Windows Presentation Foundation (WPF) et Silverlight. J'ai trouvé moi-même appliquer certains concepts à partir de WPF et Silverlight à ce travail, afin que le programme de défilement de la couleur XNA est une fête de synergie également.

Tout le code source téléchargeable est en une seule solution Visual Studio appelée XnaColorScroll. Cette solution inclut un projet de XnaColorScroll pour la téléphonie de Windows 7 et une bibliothèque nommée Petzold.Xna.Controls contenant les quatre XNA “ contrôles ” que j'ai créé pour ce programme. (À l'évidence, chargement de la solution dans Visual Studio requiert que les outils de développement de téléphonie de Windows installé.) Vous pouvez voler ces contrôles et de les compléter par vos propres de votre propre bibliothèque de contrôles XNA.

Jeux et des composants

Tout comme la classe principale d'un programme WPF, Silverlight ou des Windows Forms est une classe qui dérive de la fenêtre, page ou formulaire, la classe principale d'un programme XNA est dérivée de jeu. Un programme XNA utilise dérivé de ce jeu pour dessiner des bitmaps, texte et graphiques 3D à l'écran.

Pour vous aider à organiser votre programme en entités fonctionnelles discrètes, prend en charge XNA que le concept d'un composant , qui est peut-être la plus proche que XNA est fourni à un contrôle. Il existe deux types de composants : GameComponent dérivés et DrawableGameComponent dérivés. DrawableGameComponent lui-même dérive de GameComponent, et les noms de suggèrent la différence. Dans cet exercice, je vais utiliser les deux classes de base.

La classe de jeu définit une propriété nommée de type GameComponentCollection des composants. Tous les composants qui crée un jeu doivent être ajoutés à cette collection. Cette précaution garantit que le composant reçoit les mêmes appels de diverses méthodes substituées qui obtient le jeu en lui-même. Il dessine un DrawableGameComponent au-dessus de tout ce qui dessine la partie.

Let’s commencer l'analyse du programme XnaColorScroll — pas avec les dérivés du jeu, mais avec les dérivés GameComponent. La figure 2 indique le code source pour un composant, j'appelle TextBlock, et il est utilisé dans de la même façon que WPF ou TextBlock de Silverlight.

La figure 2 des composants de la Game TextBlock

public class TextBlock : DrawableGameComponent
{
  SpriteBatch spriteBatch;
  Vector2 textOrigin;

  public TextBlock(Game game) : base (game)
  {
    // Initialize properties
    this.Color = Color.Black;
  }

  public string Text { set; get; }
  public SpriteFont Font { set; get; }
  public Color Color { set; get; }
  public Vector2 Position { set; get; }
  public HorizontalAlignment HorizontalAlignment { set; get; }
  public VerticalAlignment VerticalAlignment { set; get; }

  public Vector2 Size
  {
    get
    {
      if (String.IsNullOrEmpty(this.Text) || this.Font == null)
          return Vector2.Zero;

      return this.Font.MeasureString(this.Text);
    }
  }

  protected override void LoadContent()
  {
    spriteBatch = new SpriteBatch(this.GraphicsDevice);
    base.LoadContent();
  }

  public override void Update(GameTime gameTime)
  {
    if (!String.IsNullOrEmpty(Text) && Font != null)
    {
      Vector2 textSize = this.Size;
      float xOrigin = (int)this.HorizontalAlignment * textSize.X / 2;
      float yOrigin = (int)this.VerticalAlignment * textSize.Y / 2;
      textOrigin = new Vector2((int)xOrigin, (int)yOrigin);
    }
    base.Update(gameTime);
  }

  public override void Draw(GameTime gameTime)
  {
    if (!String.IsNullOrEmpty(Text) && Font != null)
    {
      spriteBatch.Begin();
      spriteBatch.DrawString(this.Font, this.Text, this.Position, this.Color,
                            0, textOrigin, 1, SpriteEffects.None, 0);
      spriteBatch.End();
    }
    base.Draw(gameTime);
  }
}

Notez les propriétés publiques suivant au constructeur dans de la figure 2 . Ces erreurs indiquent le texte proprement dit et la police, couleur et la position du texte par rapport à l'écran. Deux autres propriétés HorizontalAlignment et VerticalAlignment, permettent à cette position vers le côté ou de la charge de la chaîne de texte de référence. Dans XnaColorScroll, ces propriétés sont pratiques pour centrer le texte par rapport aux curseurs.

La classe inclut également une propriété de taille de get-only, qui retourne la taille du texte rendu avec cette police particulière. (La structure de XNA Vector2 est souvent utilisée pour représenter les positions à deux dimensions et tailles, ainsi que des vecteurs.)

Le composant de TextBlock substitue les méthodes de la mise à jour et le dessin, qui est normal pour un composant drawable et normal que également dérivés de jeu. Ces deux méthodes constituent du programme “ boucle jeu ”, et ils sont appelés au même rythme que l'affichage vidéo — 30 fois par seconde pour la téléphonie de Windows 7. Vous pouvez considérer la méthode de dessin comme un message WM_PAINT ou d'une substitution de OnPaint, sauf qu'elle est appelée pour chaque actualisation de l'écran. La mise à jour de méthode est appelée avant l'appel de chaque dessin et est destinée à être utilisé pour effectuer tous les calculs nécessaires et préparation de la méthode de dessin.

TextBlock lors de l'écriture a suffisamment d'espace pour améliorer les performances. Par exemple, il peut mettre en cache la valeur de la propriété Size ou recalculer uniquement le champ textOrigin chaque fois que la modification d'une propriété qui contribue à elle. Ces améliorations peuvent avoir la priorité Si vous découvrez jamais que le programme était devenu lent parce que tous les appels de dessin et de mise à jour dans le programme sont nécessite plus de 0 secondes à s'exécuter.

Composants et composants enfants

Comme vous pouvez le voir dans les de la figure 1 , XnaColorScroll comporte six éléments TextBlock, mais également des trois curseurs vont probablement être un peu plus complexe.

Il y a de très nombreuses années je pourrais avez essayé le curseur dans une classe unique entière de codage. Mon expérience dans les modèles de contrôle WPF et Silverlight, cependant, je m'apercevoir que le curseur de contrôle peut le mieux être construite par l'assemblage d'un contrôle Thumb et RepeatButton contrôles. Il semble raisonnable essayer de créer un composant de XNA Slider à partir de composants enfants similaire.

Une classe de jeu ajoute les composants enfants vers lui-même en les ajoutant à sa propriété Components de type GameComponentCollection. La grande question maintenant devient : GameComponent lui-même possède une propriété de composants pour ses propres composants enfants ?

Malheureusement, la réponse est aucun. Toutefois, un composant a accès à la classe de jeu du parent, et si le composant doit instancier son propre composants enfants, il peut ajouter ces composants supplémentaires à la même collection de composants de la classe de jeu. Pour ce faire, dans l'ordre approprié et vous ne devrez même pas tromper, dont la propriété DrawOrder : définie par DrawableGameComponent — qui peut aider dans l'ordre dans lequel les composants sont restitués. Par défaut, la méthode de dessin dans la classe de jeu est appelée en premier lieu, puis toutes les méthodes Draw dans les composants tels qu'ils apparaissent dans la collection de composants.

Commencez donc let’s. Tels que TextBlock, la classe Thumb dérive DrawableGameComponent. Toute la classe Thumb à l'exception de la transformation de contact s'affiche dans les de la figure 3 . Thumb définit les trois mêmes événements que les contrôles WPF et Silverlight Thumb.

La figure 3 de plus de la classe de la barre

public class Thumb : DrawableGameComponent
{
  SpriteBatch spriteBatch;
  Texture2D tinyTexture;
  int?
touchId;

  public event EventHandler<DragEventArgs> DragStarted;
  public event EventHandler<DragEventArgs> DragDelta;
  public event EventHandler<DragEventArgs> DragCompleted;

  public Thumb(Game game) : base(game)
  {
    // Initialize properties
    this.Color = Color.White;
  }

  public Rectangle Position { set; get; }
  public Color Color { set; get; }

  protected override void LoadContent()
  {
    spriteBatch = new SpriteBatch(this.GraphicsDevice);
    tinyTexture = new Texture2D(this.GraphicsDevice, 1, 1);
    tinyTexture.SetData<Color>(new Color[] { Color.White });

    base.LoadContent();
  }

  ...
public override void Draw(GameTime gameTime)
  {
    spriteBatch.Begin();
    spriteBatch.Draw(tinyTexture, this.Position, this.Color);
    spriteBatch.End();

    base.Draw(gameTime);
  }
}

Dans le composant TextBlock dans de la figure 2 , la propriété position est un point de type Vector2 ;dans la barre, la propriété position est un objet rectangle et définit non seulement l'emplacement de la case de défilement, mais également sa taille rectangulaire. La conception visuelle de Thumb être constitué uniquement d'une bitmap de pixels blancs 1 x 1 s'affiche à l'emplacement et taille indiquée par la propriété position. (À l'aide de bitmaps de pixels simple étirés pour une taille particulière est un de mes préféré XNA techniques de programmation.)

La case de défilement traite de la saisie tactile, mais ne déplace pas lui-même. Le composant de curseur est chargé de gérer des événements DragDelta à partir de la case de défilement et en définissant une nouvelle propriété de position.

En plus de la case de défilement, mon Slider contient également deux éléments RepeatButton un sur chaque côté de la case de défilement. Comme les contrôles WPF et Silverlight RepeatButton, ces composants génèrent une série d'événements Click, si vous maintenez votre doigt sur eux.

Dans mon Slider XNA, les éléments RepeatButton occupent une zone particulière de l'écran mais ne sont pas actuellement visibles. (La piste verticale fine vers le bas au centre est dessinée par le curseur lui-même). Cela signifie que RepeatButton peut dériver de GameComponent plutôt que DrawableGameComponent. La figure 4 affiche l'ensemble du contrôle RepeatButton à l'exception de la transformation tactile.

La figure 4 de plus de la classe RepeatButton

public class RepeatButton : GameComponent
{
  static readonly TimeSpan REPEAT_DELAY = TimeSpan.FromMilliseconds(500);
  static readonly TimeSpan REPEAT_TIME = TimeSpan.FromMilliseconds(100);

  int?
touchId;
  bool delayExceeded;
  TimeSpan clickTime; 

  public event EventHandler Click;

  public RepeatButton(Game game) : base(game)
  {
  }

  public Rectangle Position { set; get; }

  ...
}

À ce stade, nous sommes prêts à construire le composant de curseur. Pour faire raisonnablement simple, j'ai moi-même restreint une orientation verticale. Curseur possède les propriétés de publiques habituelles : minimum, maximum et Value, entre autres et définit également un événement ValueChanged. Lors de sa substitution Initialize, Slider crée les composants de trois enfants (et les enregistre sous forme de champs), définit les gestionnaires d'événements et les ajoute à la collection de composants de la classe de jeu de parent accessible par le biais de sa propriété de jeu :

public override void Initialize()
{
  thumb = new Thumb(this.Game);
  thumb.DragDelta += OnThumbDragDelta;
  this.Game.Components.Add(thumb);

  btnDecrease = new RepeatButton(this.Game);
  btnDecrease.Click += OnRepeatButtonClick;
  this.Game.Components.Add(btnDecrease);

  btnIncrease = new RepeatButton(this.Game);
  btnIncrease.Click += OnRepeatButtonClick;
  this.Game.Components.Add(btnIncrease);

  base.Initialize();
}

XNA Game dérivés et dérivés GameComponent ont trois reprises pour réaliser l'initialisation : le constructeur de classe, une substitution de la méthode Initialize et une substitution de la méthode LoadContent — et il peut être un peu difficile à quel moment utiliser qui. Comme son nom le suggère, LoadContent est très pratique pour charger du contenu (par exemple, les polices ou les images bitmap) et doit être utilisé pour tous les autre d'initialisation qui dépend de l'existence de l'objet GraphicsDevice.

Je préfère remplacent de création de composants et en les ajoutant à la collection de composants pendant l'initialisation, comme illustré dans l'extrait de code précédent. Dans XnaColorScroll, la classe dérivée de jeu crée également des six éléments TextBlock et substituent des trois composants Slider dans son initialisation. Lorsque la méthode Initialize de la classe de base est appelée à la fin, toutes les méthodes Initialize pour les composants enfants sont appelées. La méthode Initialize de chaque curseur crée ensuite le Thumb et deux éléments RepeatButton. Ce schéma permet de s'assurer que tous ces composants sont ajoutés à la collection de composants dans un ordre correct pour le rendu.

Traitement d'entrée de contact

Le principal moyen d'entrées d'utilisateur dans la plupart des programmes de téléphonie de Windows 7 est multi-effleurement et XNA ne fait pas exception. (Entrée au clavier est également disponible pour les programmes XNA et programmes permet également monté de téléphone comme périphérique d'entrée.)

Un programme XNA Obtient la saisie tactile lors de la substitution de la mise à jour. Il n'y a aucun événement de touche. Touche toutes les entrées est interrogé et est accessible via la classe de l'écran tactile. La méthode statique TouchPanel.GetState fournit la saisie tactile de bas niveau qui se compose d'objets TouchLocation indiquant l'activité Pressed, déplacés et diffusé avec position numéros d'identification entier et les informations pour faire la distinction entre plusieurs doigts. La méthode TouchPanel.ReadGesture (je n'utilise pas ce programme va) fournit la prise en charge du mouvement de niveau supérieur.

Lorsque j'ai commencé à travailler avec les composants de jeu, j'ai pensé que chaque composant peut obtenir la saisie tactile sur sa propre, prendre cela nécessaire et ignorer le reste. Cela n'a pas été semble fonctionnent très bien ;Il n'était que si la classe de jeu et les composants ont été toutes en concurrence pour la saisie tactile au lieu de partage comme des enfants polis.

J'ai décidé d'utiliser un autre système de traitement de la saisie tactile. Seule la classe de jeu appelle TouchPanel.GetState. Ensuite, chaque TouchLocation objet est passé à des composants enfants via une méthode, j'appelle ProcessTouch. Ainsi, les composants obtenir le premier dibs sur l'entrée. Un composant qui permet d'utiliser une TouchLocation objet retourne la valeur true à partir de la méthode ProcessTouch et le traitement complémentaire ce fins d'objet TouchLocation particulier. (Retourner true à partir de ProcessTouch est comparable à la définition de la propriété Handled de RoutedEventArgs à true dans WPF ou Silverlight).

Dans XnaColorScroll, la méthode de mise à jour dans les appels de classe de jeu principal TouchPanel.GetState pour obtenir tous les touche d'entrée et appelle ensuite la méthode ProcessTouch dans chaque composant de curseur, comme illustré ici :

TouchCollection touches = TouchPanel.GetState();

foreach (TouchLocation touch in touches)
{
  for (int primary = 0; primary < 3; primary++)
    if (sliders[primary].ProcessTouch(gameTime, touch))
      break;
}

Notez la façon dont un traitement supplémentaire de chaque objet de TouchLocation s'arrête chaque fois que ProcessTouch retourne true.

La méthode ProcessTouch dans chaque contrôle Slider appelle les méthodes ProcessTouch dans les deux éléments RepeatButton et le composant Thumb :

public bool ProcessTouch(GameTime gameTime, TouchLocation touch)
{
  if (btnIncrease.ProcessTouch(gameTime, touch))
    return true;

  if (btnDecrease.ProcessTouch(gameTime, touch))
    return true;

  if (thumb.ProcessTouch(gameTime, touch))
    return true;

  return false;
}

Si un de ces composants (ou de la classe de jeu) veut effectuer son propre traitement tactile, il aurait d'abord appeler ProcessTouch au niveau de chaque enfant. N'importe quel objet TouchLocation qui est resté non traité par les enfants pourrait ensuite être examinée pour l'utilisation de la classe. En effet, ce schéma permet des enfants visuellement plus élevés pour que le premier accès à la touche entrée, qui est souvent exactement ce que vous voulez. Il est très semblable à la gestion de l'événement routé implémentée dans WPF et Silverlight.

Le RepeatButton et la case de défilement sont intéressés par la saisie tactile, si un doigt est tout d'abord affectée dans la zone indiquée par le rectangle de position du composant. Le composant puis “ capture ” que finger en enregistrant le code de touche. Tous les autres à celle de la saisie tactile ID appartient à ce composant particulier jusqu'à ce que le doigt.

Propagation d'événements

Le traitement de contact des composants Thumb et RepeatButton est vraiment l'intérêt du contrôle Slider, mais c'est quelque chose que vous pouvez étudier vous-même si vous intéresse. Lorsque le composant Thumb détecte mouvement du doigt sur sa surface, il génère des événements DragDelta ;Lorsque le composant RepeatButton détecte les clics ou les pressions soutenues, il déclenche cliquez sur événements.

À la fois ces événements sont gérés par le composant de Slider parent, qui ajuste sa propriété Value en conséquence, et il déclenche un événement ValueChanged à partir du curseur. Le contrôle Slider est également chargé de définir les propriétés de la position de la case de défilement et les deux éléments RepeatButton en fonction de la nouvelle valeur.

La classe de jeu gère les événements ValueChanged à partir des trois composants de curseur dans un seul gestionnaire d'événements, afin que la méthode définit simplement les nouvelles valeurs de ces trois composants de TextBlock et une nouvelle couleur :

void OnSliderValueChanged(object sender, EventArgs args)
{
  txtblkValues[0].Text = sliders[0].Value.ToString("X2");
  txtblkValues[1].Text = sliders[1].Value.ToString("X2");
  txtblkValues[2].Text = sliders[2].Value.ToString("X2");

  selectedColor = new Color(sliders[0].Value, 
                            sliders[1].Value, 
                            sliders[2].Value);
}

Cela laisse le remplacement de dessin dans la classe de jeu avec les deux tâches : Dessiner le rectangle noir qui est utilisé en tant qu'arrière-plan derrière les composants, puis dessinez le rectangle avec un nouveau selectedColor sur la droite. Les deux tâches impliquent une bitmap de 1 x 1 pixel étirée pour remplir la moitié de l'écran.

Plus de Silverlight ?

J'ai récemment également écrit une version de Silverlight du programme de défilement de la couleur pour la téléphonie de Windows 7 et j'ai découvert que le programme seulement m'a permis de manipuler un curseur à la fois. Toutefois, si vous déployez le programme XnaColorScroll vers un téléphone, vous pouvez manipuler tous les trois contrôles Slider indépendamment. Je trouve cette différence très intéressant. Il illustre les interfaces à nouveau de la manière dont de haut niveau telles que Silverlight peuvent rendre nos vies bien plus simple, mais souvent quelque chose d'autre doit être sacrifiés. Je pense que le concept est appelé TANSTAAFL : Il n'aucun, à proprement parler en tant qu'un déjeuner.

XNA est donc plus bas niveau à certains égards, qu'il peut nous rappeler de la Wild West. Mais avec un peu judicieuse utiliser des composants (y compris les composants créés à partir d'autres composants) et avec la gestion des événements routés, XNA même peut être tamed et à semble plus proche de Silverlight, peut-être même meilleures.

Charles Petzold contribue depuis longtemps à l'élaboration de MSDN Magazine*.*Son nouveau livre, “ programmation Windows téléphone 7 ” (Microsoft Press, 2010), est disponible en téléchargement gratuit à bit.ly/cpebookpdf de .

Grâce à l'expert technique suivante pour la révision de cet article : Shawn Hargreaves