Le premier site francophone dédié au développement Pocket PC

Création d'un chronomètre au 1/1000ème de seconde pour le Compact Framework et VB.NET
Auteur
Stéphane Sibué
Date 23 février 2004 / Ajout de la version Smartphone le 28 août 2004
 
   


Introduction

Le développement .NET pour Pocket PC et Smartphone est globalement très sympa. Mais il y a parfois des limitations qui font un peu grincer des dents. Si par exemple vous souhaitez utiliser les objets de gestion de temps pour gérer le temps qui passe, vous allez très vite vous rendre compte que la résolution est égale à la seconde ! Alors pour faire un chrono au 1/1000ème de seconde c'est pas gagné !

Il faut garder les bonnes habitudes et fouiller dans les API du système

J'ai eu beau faire le tour de tous les objets proposés par le Compact Framework, rien ne permet de récupérer le temps passé avec une résolution correcte. Avec des machines à 400 Mhz, se limiter à la seconde me semble un gros foutage de gueule ! Bon, espérons qu'avec la version 2 du CF on aura droit à quelque chose de plus pro (on peut rêver non ?).

Donc, j'ai ressorti ma documentations sur les API de Windows CE à la recherche d'une fonction qui me retourne le temps passé, avec au moins une résolution au 1/1000ème de seconde. Je n'ai pas cherché longtemps et j'ai trouvé l'API GetTickCount qui semble réponde toute à fait à mes besoins. Voici d'ailleur le texte de l'aide (eVC++) que j'ai trouvé :

GetTickCount
This function retrieves the number of milliseconds that have elapsed since Windows CE was started.

DWORD GetTickCount( void );

Return Values :
The number of milliseconds that have elapsed since the system was started indicates success.

Remarks :
The the resolution of the system timer is based on the OEMs setting. Check with the OEM for details.

The elapsed time is stored as a DWORD value. Therefore, the time will wrap around to zero if the system is run continuously for 49.7 days.

Cette fonction nous retourne le nombre de millisecondes écoulées depuis le démarrage de Windows CE. La résolution réelle dépend en fait du constructeur, et enfin, vue que la valeur est codée dans un DWORD, elle retombe à zéro tous les 49.7 jours.

Bon, et bien nous allons utiliser cette API pour réaliser notre chronomètre au 1/1000ème de seconde faute de mieux.

Déclaration en VB.NET de cette API

Plus simple tu meurs ! Pas de paramètre, juste une valeur de retour. Un DWORD correspond à un Int32 en VB.NET, donc ça nous donne :

Les principes de base du chronomètre en utilisant GetTickCount

Lorsque nous allons mettre en marche notre chrono nous allons prendre note du point de départ, c'est à dire la valeur retounée par GetTickCount à ce moment précis. Dans le programme c'est la variable CheckPoint de type Int32 qui stocke cette valeur. Lorsqu'on arrête le chrono il faut noter le temps qui s'écoule avant la prochaine remise en marche pour être capable de reprendre le comptage à la bonne valeur. Dans le programme c'est la variable TempsInter de type Int32 qui stocke cette valeur.

A la mise en marche du chrono nous avons donc la mémorisation du point de départ et la mise en route du timer chargé d'afficher à intervalles réguliers la valeur du chronomètre (le timer s'appelle tout simplement TimerChrono) :

CheckPoint = GetTickCount()
TimerChrono.Enabled = True

A l'arrêt du chrono il faut arrêter le timer d'affichage et mémoriser le temps intermédiaire :

TimerChrono.Enabled = False
TempsInter = GetTickCount() - CheckPoint + TempsInter

L'objet TimeSpan permet un affichage simple des infos

En ce qui concerne l'affichage du temps passé par lui même je suis passé par les services d'un objet de type TimeSpan. Ce type d'objet permet d'effectuer des opérations sur les durée. Idéal pour notre affaire.

La première étape consiste à récupérer le nombre de millisecondes écoulées. En utilisant notre API GetTickCount et nos variables CheckPoint et TempsInter il est simple de le récupérer :

Dim t As Int32

t = GetTickCount() - CheckPoint + TempsInter

Puis il faut donner cette valeur à un objet de type TimeSpan qui va se charger d'en faire une durée compréhensible. Pour cela on "charge" un objet de type TimeSpan avec la valeur retournée par la méthode "FromMilliseconds" de l'objet "global" TimeSpan (je sais, il ne faut pas confondre de type TimeSpan et l'objet automatiquement mis à disposition par le Framework TimeSpan, mais avec un peu d'habitude on y arrive, je vous assure) :

Dim w As TimeSpan

w = TimeSpan.FromMilliseconds(t)


A partir de là nous avons à notre disposition la durée écoulée directement exploitable sous la forme heures, minutes, secondes et millisecondes, il ne reste plus qu'à préparer son affichage. Pour cela nous allons stocker dans deux chaînes séparée cette durée sous la forme 00:00:00 (heures, minutes, secondes) pour la première et millisecondes (000) pour la seconde afin de pouvoir afficher la durée comme sur l'image de gauche.

Voici le code pour créer nos deux chaines :

Dim s1 As String
Dim s2 As String

s1 = String.Format("{0:00}:{1:00}:{2:00}", w.Hours, w.Minutes, w.Seconds)
s2 = String.Format("{0:000}", w.Milliseconds)

Afficher directement sur le fond de la fenêtre

Si pour l'affichage vous voulez utiliser les services de 2 labels vous ne serez pas du tout satisfait du résultat à cause de très désagréables clignotements lors de leur raffraichissement. Pour éviter cela il est préférable d'afficher les 2 chaînes dans un bitmap (donc en mémoire hors écran) et de "coller" ce bitmap sur le fond de la fenêtre. Cette méthode évite les clignotements.

Nos chaînes vont être affichées dans 2 polices de taille différentes. Il convient de préparer ces polices. Pour plus d'efficacités ces polices sont stockées dans des variables globales et elles ne sont créées qu'une seul fois au démarrage de l'application. Il en est de même pour le bitmap de travail et la brosse (le trait) utilisée pour dessiner sur lui. Voici leurs déclarations (au niveau de la fenêtre) :

Private LaFontGrande As Font
Private LaFontPetite As Font
Private LeBitmap As Bitmap
Private LaBrush As SolidBrush

Et voici leur initialisation depuis l'événement Load de la fenêtre :

LaFontGrande = New Font("Tahoma", 36, FontStyle.Bold)
LaFontPetite = New Font("Tahoma", 12, FontStyle.Bold)
LeBitmap = New Bitmap(240, 80)
LaBrush = New SolidBrush(Color.Black)

Vous pouvez constater que la grande fonte est de type "Tahoma", de taille 36 en gras, que la petite fonte (pour les millisecondes) est aussi de type "Tahoma", de taille 12 et en gras. Le bitmap de travail fait 240x80 pixels et la brosse est noire. Cette initialisation une bonne fois pour toute nous évite de tout refaire à chaque affichage, ce qui pourrait pénaliser pour rien notre chrono (non mais).

Pour afficher nos chaînes dans le bitmap de travail nous devons récupérer son contexte graphique et utiliser la méthode "DrawText" aux bonnes coordonnées :

Rem On récupère le contexte graphique du bitmap de travail
Dim g As Graphics =Graphics.FromImage(LeBitmap)

Rem On récupère la taille (hauteur, largeur) des chaînes
Rem une fois écrites avec leur police respective dans Mesure1 et Mesure2
Dim Mesure1 As SizeF = g.MeasureString(s1, LaFontGrande)
Dim Mesure2 As SizeF = g.MeasureString(s2, LaFontPetite)

Rem On calcule le centre du bitmap pour ensuite positionner les 2 chaînes correctement
Dim xf As Integer = (LeBitmap.Width - Mesure1.Width) \ 2

Rem On vide le bitmap en le remplissant par du blanc
g.Clear(Color.White)

Rem On dessine la première chaîne puis la seconde
g.DrawString(s1, LaFontGrande, LaBrush, xf, 0)
g.DrawString(s2, LaFontPetite, LaBrush, xf + (Mesure1.Width - Mesure2.Width), Mesure1.Height)

Rem Ne pas oublier de libérer les ressources du contexte graphique
g.Dispose()

Rem On calcule la position du bitmap sur la fenêtre
Dim x As Integer = (240 - LeBitmap.Width) \ 2

Rem On récupère le contexte graphique de la fenêtre
g = Me.CreateGraphics

Rem On "colle" le bitmap aux bonnes coordonnées (en x,10)
g.DrawImage(LeBitmap, x, 10)

Rem On libère les ressources
g.Dispose()

Vous voyez, ce n'est pas très compliqué. Le mieux est de placer ce code dans une fonction, par exemple "AfficherChrono" qui sera appellée depuis l'événement "Tick" du timer chargé de l'affichage. Ne pas oublié aussi d'appeller cette fonction depuis l'événement "Paint" de la fenêtre pour que quoi qu'il arrive l'affichage soit persistant.

Une petite fonction en plus : La liste des temps intermédiaires

Dans le projet "Chrono" qui vous est fourni dans cet article, vous y trouverez aussi le code qui remet à zéro le chrono (ça peut servir), et aussi le code qui permet d'ajouter dans une listview les temps intermédiaires.

Je vous invite à télécharger les sources de "Chrono" et à le décortiquer tranquillement. Vous y découvrirez surement encore d'autres astuces.

Sources de Chrono en VB.NET

Si vous voulez simplement télécharger le cab d'installation de Chrono, je vous invite à vous rendre dans la section Téléchargements à la page dédiée à Chrono où vous trouverez ces fichiers.

Vous voulez la version pour Smartphone 2003 ?

La version pour Smartphone fonctionne exactement sur le même principe. Par contre c'est à vous de l'installer car je n'ai pas créé de programme d'install pour lui (je sais ce n'est pas bien du tout, mais vous êtes des développeurs et vous savez vous débrouiller comme des grands).

Pour installer Chrono pour Smartphone vous devez :

-1- télécharger le programme "Chrono.exe".

-2- Créer un dossier "Chrono" dans le dossier "Storage\Program Files" de votre Smartphone et y copier le "Chrono.exe" fraichement téléchargé.

-3- Créer un raccourci vers "Chrono.exe" dans le dossier "Storage\Windows\Start Menu\Accessories" de votre Smartphone.

 

Vous pouvez effectuer toutes ces opérations depuis votre PC avec votre Smartphone connecté par ActiveSync. Une fois toutes ces opérations réalisées, vous aurez un accès à Chrono depuis le dossier accessoire de votre téléphone.

Sources de Chrono en VB.NET pour Smartphone.

Conclusion

Il y a tout de même un problème. J'ai au préalable développé Chrono pour mon Smartphone. Le comportement du Smartphone est différent de celui d'un pocket PC dans le sens où quand il se place en veille il fonctionne encore tout de même et l'OS ne se considère pas comme "arrêté". Avec Pocket PC, lorsqu'on l'arrête, GetTickCount ne décompte plus le temps qui passe et de fait notre chrono s'arrête aussi. Il existe une solution à ce problème. Je me ferai un plaisir de vous en faire part dans un prochain article, ce qui me permettra de mettre en ligne la version 2 de Chrono, celle qui continue à "tourner" même le Pocket PC éteint. A bientôt donc.

Stéphane Sibué

 
   

Copyright 2001-2004 - Tous droits réservés
Toutes les autres marques et produits présents dans ces pages sont la propriété exclusive de leurs sociétés respectives.