[WP7] Globalisez vos applications Windows Phone 7

Le 8 août 2011 à 09:34

Afin de toucher un beaucoup plus de monde sur Windows Phone 7, traduire vos applications en plusieurs langues est un véritable plus. Un peu à la manière de WPF, il existe plusieurs méthodes pour globaliser son application (fichiers de ressources, fichiers dictionnaires XAML, frameworks… etc).

Ici on va utiliser la méthode que Microsoft recommande sur MSDN. C’est-à-dire l’utilisation des fichiers de ressources (un fichier par langue).

Tout d’abord, on va définir une langue par défaut dans notre application, en tout bon français que je suis, je vais lui dire que mon projet est français :) :

image1

Maintenant, on va lui ajouter les fichiers qui vont contenir les ressources de notre application tout d’abord pour commencer (qui seront les valeurs traduites que l’on va lui définir), ajoutez un fichier de ressource dans votre projet :

image2

Comme on a défini la langue par défaut de notre application sur « Français », le fichier Labels.resx contiendra les valeurs pour le français. Pour ajouter un fichier pour l’anglais par exemple, il faut de nouveau ajouter un fichier de ressource sur la syntaxe suivante : [Nomdufichier].[Culture].resx.

Pour la liste des cultures disponibles, allez faire un tour sur msdn.

Pour notre exemple, on va ajouter le fichier de ressource anglais :

image3

Pour valider les cultures supportées par notre application, il faut ouvrir le fichier csproj de l’application et rechercher la balise <SupportedCultures>, et lui indiquer la liste des culture que l’on supporte (à séparer par des ; entre chaque culture), ici :

<SupportedCultures>fr-FR;en-US</SupportedCultures>

On va à présent ajouter dans les fichiers de ressources les valeurs pour la traduction.
Tout simplement, on va ajouter cette valeur :

image4

Et faire de même pour le fichier anglais :

image5

On en profite pour passer le modificateur d’accès en public

Petite remarque, comme vous le pouvez le remarquer, si on ajoute une valeur dans le fichier principal « Labels.resx », rien n’est répliqué sur le fichier de ressources anglais. Alors quand on a un projet de quelques dizaines de lignes, ça peut passer encore, mais quand le fichier est trop important, ça devient vite une usine à gaz à gérer.

Pour cela, je vous conseille d’utiliser l’excellent logiciel Zeta Resource Editor. Il est gratuit, écrit en .NET et open source. Il va vous permettre de calquer facilement vos ressources sans vous prendre la tête J.

Maintenant, pour pouvoir facilement binder vos ressources à votre interface, ou à votre code, on va créer une classe statique qui va nous permettre de le faire facilement, ici je la nomme LocalizedStrings :

class LocalizedStrings
{
    private static Resources.Labels resource = new Resources.Labels();

    public Resources.Labels Localizedresources { get { return resource; } }

    public LocalizedStrings()
    {
    }
}

Ensuite, déclarer la classe dans un dictionnaire de ressource, pour qu’on puisse l’appeler directement via XAML (ici directement dans App.xaml) :

<Application.Resources>
    <WP7Lang:LocalizedStrings xmlns:WP7Lang="clr-namespace:WP7Lang"
                                        x:Key="LocalizedStrings" />
</Application.Resources>

Voilà, notre application est disponible en plusieurs langues ! Il suffit de binder les valeurs dans notre interface ou dans le code maintenant :) :


Côté XAML :

<TextBlock Name="txtHello" VerticalAlignment="Center" HorizontalAlignment="Center" 
  Text="{Binding Path=Localizedresources.Hello, Source={StaticResource LocalizedLabels}}" />

Côté code :

CultureInfo newCulture = new CultureInfo("fr-FR");

Thread.CurrentThread.CurrentCulture = newCulture;
Thread.CurrentThread.CurrentUICulture = newCulture;

 

Par contre, vous l’avez surement remarqué mais lorsque vous changez de Culture, les valeurs ne sont pas automatiquement changé dans votre UI (juste dans votre code si vous appelez les ressources vous-même).

C’est assez logique car en effet, on ne notifie pas l’interface des changements qui sont intervenus, pour cela il faut passer par INotifyPropertyChanged, donc nous allons modifier LocalizedStrings comme ceci :

public class LocalizedStrings : INotifyPropertyChanged
{
    private static readonly Resources.Labels Resource = new Resources.Labels();
    public Resources.Labels Localizedresources { get { return Resource; } }

    public LocalizedStrings()
    {
        
    }

    public void ChangeLanguage(String codeLang)
    {
        CultureInfo newCulture = new CultureInfo(codeLang);
        Resources.Labels.Culture = newCulture;

        Thread.CurrentThread.CurrentCulture = newCulture;
        Thread.CurrentThread.CurrentUICulture = newCulture;

        OnPropertyChanged("Localizedresources");
    }


    #region INotifyPropertyChanged Members

    PropertyChangedEventHandler propertyChanged;
    public virtual event PropertyChangedEventHandler PropertyChanged
    {
        add { propertyChanged += value; }
        remove { propertyChanged -= value; }
    }

    public virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.propertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion
}


Maintenant dans notre code, pour récupérer l’instance de LocalizedStrings définie dans App.xaml :

private LocalizedStrings _resources = 
        Application.Current.Resources["LocalizedLabels"] 
                                            as LocalizedStrings;

 

Pour changer de langue :

_resources.ChangeLanguage("fr-FR");

 

Pour utiliser les ressources directement dans le code :

MessageBox.Show(Labels.ResourceManager.GetString("Hello"));


Voilà maintenant, lorsqu’on change de Culture, tout est immédiatement bindé dans notre interface.

Vous pouvez télécharger la source exemple ici.

A bientôt !

[WP7] Présentation et utilisation des différentes Tasks

Le 4 mai 2011 à 09:55

Pour les tâches les plus courantes, comme choisir ou prendre des photos, envoyer des e-mails ou des SMS, il existe un ensemble de tâches qui sont disponibles dans l’espace de nom Microsoft.Phone.Tasks.

Les différentes tâches sont :

CameraCaptureTask

Permet à l’application courante de lancer l’application de l’appareil photo.
Cette option permet à l’utilisateur de prendre une photo.

EmailAddressChooserTask

Permet à l’application courante de lancer l’application Contacts.
Cette option permet d’obtenir l’adresse e-mail d’un contact sélectionné par l’utilisateur.

EmailComposeTask

Permet à l’application courante de lancer la création d’un nouvel e-mail dans Outlook.
Cette option permet aux utilisateurs d’envoyer des e-mails.

MarketplaceDetailTask

Permet à l’application courante de lancer l’application Marketplace et d’afficher la page des détails d’un produit spécifié.

MarketplaceHubTask

Permet à l’application courante de lancer l’application Marketplace.

MarketplaceReviewTask

Permet à l’application courante de lancer l’application Marketplace et d’afficher la page des avis d’un produit spécifié.

MarketplaceSearchTask

Permet à l’application courante de lancer l’application Marketplace et d’afficher les résultats d’une recherche spécifiée.

MediaPlayerLauncher

Permet à l’application courante de lancer Media Player.

PhoneCallTask

Permet à l’application de lancer l’application Téléphone.
Cette option permet de téléphoner à partir de l’application courante.

PhoneNumberChooserTask

Permet à l’application courante de lancer l’application Contacts.
Cette option permet d’obtenir le numéro de téléphone d’un contact sélectionné par l’utilisateur.

PhotoChooserTask

Permet à l’application courante de lancer la sélection photo.
Cette option permet d’obtenir une photo sélectionnée par l’utilisateur.

SaveEmailAddressTask

Permet à l’application courante de lancer l’application Contacts.
Cette option permet à l’utilisateur d’enregistrer une adresse e-mail de votre application sur un nouveau ou un contact existant.

SavePhoneNumberTask

Permet à l’application courante de lancer l’application Contacts.
Cette option permet à l’utilisateur d’enregistrer un numéro de téléphone de votre application à un nouveau ou un contact existant.

SearchTask

Permet à l’application courante de lancer la recherche web.

SmsComposeTask

Lance l’application SMS pour créer un nouveau message.

WebBrowserTask

Permet à l’application courante de lancer l’application Internet Explorer.

 

Maintenant on va mettre en pratique l’utilisation des Tasks !


CameraCaptureTask, cette tâche va nous permettre de récupérer la photo que l’utilisateur vient de prendre pour notre application :

public partial class MainPage : PhoneApplicationPage
{
    private CameraCaptureTask _cameraCaptureTask;

    public MainPage()
    {
        InitializeComponent();

        _cameraCaptureTask = new CameraCaptureTask();
        _cameraCaptureTask.Completed += 
                             new EventHandler<PhotoResult>(_cameraCaptureTask_Completed);
    }

    private void btnCamera_Click(object sender, RoutedEventArgs e)
    {
        _cameraCaptureTask.Show();
    }

    void _cameraCaptureTask_Completed(object sender, PhotoResult e)
    {
        if (e.TaskResult == TaskResult.OK)
        {
            BitmapImage bmp = new BitmapImage();
            bmp.SetSource(e.ChosenPhoto);
            // Affichage, enregistrement... etc
        }
    }
}

 

EmailChooserAddressTask, cette tâche va nous permettre de récupérer le mail que l’utilisateur va sélectionner :

public partial class MainPage : PhoneApplicationPage
{
    private EmailAddressChooserTask _emailAddressChooserTask;

    public MainPage()
    {
        InitializeComponent();

        _emailAddressChooserTask = new EmailAddressChooserTask();
        _emailAddressChooserTask.Completed += 
                        new EventHandler<EmailResult>(emailAddressChooserTask_Completed);
    }

    private void btnEmailChooser_Click(object sender, RoutedEventArgs e)
    {
         _emailAddressChooserTask.Show();
    }

    void emailAddressChooserTask_Completed(object sender, EmailResult e)
    {
        if (e.TaskResult == TaskResult.OK)
        {
            MessageBox.Show(e.Email);
        }
    }
}

 

image


EmailComposeTask, pour envoyer un nouveau mail via Outlook :

private void btnEmail_Click(object sender, RoutedEventArgs e)
{
    EmailComposeTask emailComposeTask = new EmailComposeTask();

    emailComposeTask.To = "destinataire@wp7";
    emailComposeTask.Cc = "encopie@wp7";
    emailComposeTask.Subject = "Object : Test mail";
    emailComposeTask.Body = "Hello... world";

    emailComposeTask.Show();
}

 

MarketplaceDetailTask, permet d’afficher la page d’une application spécifiée par un GUID :

MarketplaceDetailTask marketDetailTask = new MarketplaceDetailTask();
marketDetailTask.ContentType = MarketplaceContentType.Applications;
marketDetailTask.ContentIdentifier = "{votre_guid}";
marketDetailTask.Show();

 

Il est bien sur possible d’afficher de la musique avec MarketplaceContentType.Music;

 

image

 

MarketplaceHubTask, lance l’application Marketplace sur le téléphone. On peut spécifie le type de contenu à afficher avec la propriété ContentType avec une valeur de l’énumération MarketplaceContentType :

MarketplaceHubTask marketplaceHubTask = new MarketplaceHubTask();
marketplaceHubTask.ContentType = MarketplaceContentType.Music;
marketplaceHubTask.Show();

 

image

 

MarketplaceReviewTask lance l’application Marketplace et affiche les avis de l’application en cours :

MarketplaceReviewTask marketplaceReviewTask = new MarketplaceReviewTask();
marketplaceReviewTask.Show();

 

MarketplaceSearchTask lance l’application Marketplace et affiche le résultat d’une recherche basée sur les mots-clefs qu’on a définis :

MarketplaceSearchTask marketplaceSearchTask = new MarketplaceSearchTask();
marketplaceSearchTask.SearchTerms = "microsoft";
marketplaceSearchTask.Show();

 

image

 

MediaPlayerLauncher nous permet de lancer une vidéo :

MediaPlayerLauncher mediaPlayerLauncher = new MediaPlayerLauncher();
// La vidéo se trouve dans le répertoire de l'application 
mediaPlayerLauncher.Location = MediaLocationType.Install; 
mediaPlayerLauncher.Media = new Uri("lavideo.wmv", UriKind.Relative);
mediaPlayerLauncher.Controls = MediaPlaybackControls.Pause | 
                               MediaPlaybackControls.Rewind | 
                               MediaPlaybackControls.Stop; // boutons dispos
mediaPlayerLauncher.Show();

 

PhoneCallTask nous permet de lancer un appel au numéro qu’on va définir dans l’application :

PhoneCallTask phoneCallTask = new PhoneCallTask();
phoneCallTask.DisplayName = "Orange pro";
phoneCallTask.PhoneNumber = "3901";
phoneCallTask.Show();

 

image

 

PhoneNumberChooserTask permet à l’utilisateur de choisir un contact et que son numéro soit envoyé vers l’application courante:

public partial class MainPage : PhoneApplicationPage
{
    private PhoneNumberChooserTask _phoneNumberChooserTask;

    public MainPage()
    {
        InitializeComponent();

        _phoneNumberChooserTask = new PhoneNumberChooserTask();
        _phoneNumberChooserTask.Completed += 
                       new EventHandler<PhoneNumberResult>(phoneNumberChooserTask_Completed);
    }

    private void btnEmailChooser_Click(object sender, RoutedEventArgs e)
    {
        _phoneNumberChooserTask.Show();
    }

    void phoneNumberChooserTask_Completed(object sender, PhoneNumberResult e)
    {
        if (e.TaskResult == TaskResult.OK)
        {
            MessageBox.Show(e.PhoneNumber);
        }
    }
}

 

PhotoChooserTask permet à l’utilisateur de choisir une photo et de la renvoyer à l’application courante :

public partial class MainPage : PhoneApplicationPage
{
    private PhotoChooserTask _photoChooserTask;

    public MainPage()
    {
        InitializeComponent();

        _photoChooserTask = new PhotoChooserTask();
        _photoChooserTask.Completed +=
                        new EventHandler<PhotoResult>(photoChooserTask_Completed);
    }

    private void btnPhotoChooser_Click(object sender, RoutedEventArgs e)
    {
        _photoChooserTask.Show();
    }

    void photoChooserTask_Completed(object sender, PhotoResult e)
    {
        if (e.TaskResult == TaskResult.OK)
        {
            System.Windows.Media.Imaging.BitmapImage bmp =
                                 new System.Windows.Media.Imaging.BitmapImage();
            bmp.SetSource(e.ChosenPhoto);

            //traitement
        }
    }
}

 

image


SaveEmailAddressTask permet de sauvegarder une adresse e-mail :

public partial class MainPage : PhoneApplicationPage
{
    private SaveEmailAddressTask _saveEmailAddressTask;

    public MainPage()
    {
        InitializeComponent();

        _saveEmailAddressTask = new SaveEmailAddressTask();
        _saveEmailAddressTask.Completed += 
                       new EventHandler<TaskEventArgs>(saveEmailAddressTask_Completed);
    }

    private void btnSaveMail_Click(object sender, RoutedEventArgs e)
    {
        _saveEmailAddressTask.Email = "mail@localhost";
        _saveEmailAddressTask.Show();
    }
   
    void saveEmailAddressTask_Completed(object sender, TaskEventArgs e)
    {
        if (e.TaskResult == TaskResult.OK)
        {
            MessageBox.Show("Enregistrement OK");
        }
        else if (e.TaskResult == TaskResult.Cancel)
        {
            MessageBox.Show("Annulé");
        }
    }
}
 

image


SavePhoneNumberTask permet de sauvegarder un numéro de téléphone :

public partial class MainPage : PhoneApplicationPage
{
    private SavePhoneNumberTask _savePhoneNumberTask;

    public MainPage()
    {
        InitializeComponent();

        _savePhoneNumberTask = new SavePhoneNumberTask();
        _savePhoneNumberTask.Completed += 
                   new EventHandler<TaskEventArgs>(savePhoneNumberTask_Completed);
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        _savePhoneNumberTask.PhoneNumber = "3901";
        _savePhoneNumberTask.Show();

    }

    void savePhoneNumberTask_Completed(object sender, TaskEventArgs e)
    {
        if (e.TaskResult == TaskResult.OK)
        {
            MessageBox.Show("Enregistrement OK");
        }
        else if (e.TaskResult == TaskResult.Cancel)
        {
            MessageBox.Show("Annulé");
        }
    }
}

 

image


SearchTask permet de faire une recherche avec la fonction recherche du téléphone :

SearchTask searchTask = new SearchTask();
searchTask.SearchQuery = "Microsoft";
searchTask.Show();

 

image


SmsComposeTask permet d’envoyer des SMS depuis l’application courante :

SmsComposeTask smsComposeTask = new SmsComposeTask();
smsComposeTask.To = "3901";
smsComposeTask.Body = "Mon SMS";
smsComposeTask.Show();

 

image


WebBrowserTask permet de lancer une page internet dans le navigateur internet du téléphone :

WebBrowserTask webBrowserTask = new WebBrowserTask();
webBrowserTask.URL = "http://www.microsoft.com";
webBrowserTask.Show();

 

image

A bientôt !

[WP7] Mon application pour le concours Windows Phone 7 !

Le 23 août 2010 à 10:05

Microsoft organise une compétition pour les développeurs Windows Phone 7 pour rechercher les killers-apps de demain. Les 7 finalistes passeront devant un jury composé de 6 personnes : Steve Ballmer (CEO Microsoft), Pierre Olivier Carles (CEO de Kipost et co-fondateur de Labotec, business angel), Jacques Antoine Granjon (fondateur de Vente-privée.com), Ouril Ohayon (co-fondateur d’AppsFire et co-fondateur du fonds Isai), Marc Simoncini (fondateur et président de Meetic et du fonds Jaïna) et Bruno Vanryb (fondateur et Président d’Avanquest Software).

L’application que je présente pour le concours (avec Lionel Reichert) est un traducteur d’image (avec reconnaissance optique des caractères) et de textes en une vingtaine de langues. On a aussi la possibilité d’écouter le texte traduit.

Vidéo :


Pour voter pour cette application, vous pouvez vous rendre sur la page officiel du concours et cliquer sur “j’aime” ! Merci !

Edit : on ne peut plus cliquer sur “j’aime” sur la vidéo sur Facebook, c’est aussi le cas de nombreuses vidéos sur le groupe “Développeurs.net”, pour pouvoir cliquer sur “j’aime” il faudra se rendre à présent sur cette page ! Merci ! 

A bientôt !

[.NET] Impersonation : changer d’utilisateur (WindowsIdentity) à la volée

Le 6 août 2010 à 15:51

Pour les besoins d’un projet, j’ai besoin d’utiliser temporairement un compte avec d’autres privilèges (par exemple, même si c’est faisable autrement, si vous avez besoin d’accéder à des ressources réseaux ou tout simplement, comme il sera montré dans l’exemple, d’accéder à un répertoire où l’utilisateur courant n’a pas les droits). Pour simplifier l’utilisation, j’ai crée une (simple) classe qui sera présentée ici.

Tout d’abord, voici la classe Impersonation, qui permet de changer d’utilisateur :

public class Impersonation : IDisposable
{
    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool 
        LogonUser(string lpszUsername, 
                  string lpszDomain, 
                  string lpszPassword, 
                  int dwLogonType, 
                  int dwLogonProvider, 
                  out IntPtr phToken);

    private const int LOGON32_PROVIDER_DEFAULT = 0;
    private const int LOGON32_LOGON_INTERACTIVE = 2;

    private WindowsImpersonationContext _impersonationContext;

    public Impersonation(string domain, string login, string password)
    {
        try
        {
            IntPtr tokenHdle = IntPtr.Zero;

            bool ret = LogonUser(login, domain, password, LOGON32_LOGON_INTERACTIVE, 
                                 LOGON32_PROVIDER_DEFAULT, out tokenHdle);

            Trace.WriteLine("Current user : " + WindowsIdentity.GetCurrent().Name);

            WindowsIdentity newIdentity = new WindowsIdentity(tokenHdle);
            _impersonationContext = newIdentity.Impersonate();

            Trace.WriteLine("Current user : " + WindowsIdentity.GetCurrent().Name);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    #region IDisposable Membres

    public void Dispose()
    {
         _impersonationContext.Undo();
         Trace.WriteLine("Current user : " + WindowsIdentity.GetCurrent().Name);
    }

    #endregion
}

Dans l’exemple qui va suivre, j’ai crée un dossier Test où j’ai enlever tous les droits pour ne laisser qu’un compte temporaire (juste pour mon exemple) :

image


Donc bien entendu, si avec mon compte par défaut, je souhaite y accéder :


image

Maintenant nous allons essayer de créer un fichier dans ce dossier avec l’utilisateur où je n’ai pas les droits, puis tout de suite derrière on va utiliser la classe Impersonation pour pouvoir basculer vers l’utilisateur “MathieuTest” afin d’avoir l’autorisation de créer le fichier :

static void Main(string[] args)
{
    string userName, password;

    Console.WriteLine("Impersonation Sample");
    Console.WriteLine("--------------------");
    Console.WriteLine(@"Pour cet exemple, 
                        nous allons essayer de créer le fichier 
                        E:\Test\File.txt dont le dossier n'est accessible 
                        que pour un utilisateur");
    Console.Write("Essai n°1 : ");
    Console.WriteLine(Test());
    Console.WriteLine("Donc on va utiliser l'impersonation pour pouvoir 
                       utiliser temporairement \"son compte\" afin de pouvoir 
                       créer le fichier");
    Console.Write("Username : ");
    userName = Console.ReadLine();
    Console.Write("Password : ");
    password = Console.ReadLine();
    Console.Write("Essai n°2 : ");

    // On tente avec l'impersonation
    Impersonation impersonation = new Impersonation(string.Empty, userName, password);

    Console.WriteLine(Test());

    // On revient à l'utilisateur "en cours"
    impersonation.Dispose();



    Console.Read();
}

static string Test()
{
    try
    {
        File.Create(@"E:\Test\File.txt");
        return "Fichier crée";
    }
    catch (Exception e)
    {
        return @"/!\ Impossible de créer le fichier";
    }
}

Voici le résultat dans la console :

image

Pour télécharger les sources, c’est ici.

A bientôt !

[C#] Simplifier l’utilisation de SecureString

Le 29 juillet 2010 à 06:54

Quand vous utilisez un String, celui-ci est stocké en clair en mémoire. Cela peut être problématique surtout quand vous avez besoin de travailler sur des informations sensibles (mot de passe, carte bancaire, licence… etc).

Pour cela, la classe SecureString (System.Security.SecureString), disponible depuis le framework .NET 2.0, permet de mettre en mémoire une chaîne de caractère de manière cryptée (utilisant DPAPI). De plus, avec SecureString vous contrôlez sa disposition en mémoire, ce qui n’est pas forcément le cas d’un “simple” String.

Par contre, son utilisation n’est pas aussi “simple” qu’un String. Pour cela je propose deux méthodes d’extensions :

Convertir un String en SecureString :

public static SecureString ConvertToSecureString(this string value)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }

    char[] chars = value.ToCharArray();
    SecureString secureValue = new SecureString();

    foreach (char c in chars)
    {
        secureValue.AppendChar(c);
    }
    secureValue.MakeReadOnly();

    return secureValue;
}

Convertir un SecureString en String :

public static string ConvertToString(this SecureString value)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }

    IntPtr ptrString = IntPtr.Zero;
    try
    {
        ptrString = Marshal.SecureStringToGlobalAllocUnicode(value);
        return Marshal.PtrToStringUni(ptrString);
    }
    finally
    {
        Marshal.ZeroFreeGlobalAllocUnicode(ptrString);
    }
}

Et l’utilisation (didactique, je ne le conseille pas en cas réel ! Ici c’est en “dur” ; alors que c’est à utiliser par rapport aux données entrées par l’utilisateur par exemple ;-))

String myPassword = "password"; // à ne pas faire ! Juste pour exemple
SecureString securePassword = myPassword.ConvertToSecureString();

Trace.WriteLine(securePassword.ConvertToString());

A bientôt !

A propos de l'auteur

Mathieu Perrein est Software Solutions Architect, Microsoft Student Partner de 2010 à 2012.

 

MSP

 

MSP

MSP

 MSPD

MCT

 

Facebook

 

Ce blog est strictement personnel et les opinions exprimées ici n'engagent donc que moi, et pas mon employeur.

Tags

Vous avez désactivé JavaScript ou bien vous possédez une ancienne version d'Adobe Flash Player. Téléchargez la dernière version de Flash Player.