• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 29/04/2014

TP : Une application météo

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Bienvenue dans ce nouveau TP. Nous allons mettre en pratique les derniers éléments que nous avons appris, mais aussi des éléments déjà vus. Eh oui ! Étant donné qu’ils vont faire partie intégrante de beaucoup de vos futures applications, vous vous devez de les maîtriser.

Bref, le but de ce TP sera de réaliser une petite application météo tout à fait fonctionnelle, que vous pourrez exhiber devant vos amis : regardez, c’est moi qui l’ai fait !
Allez, passons sans plus attendre à l’énoncé du TP.

Instructions pour réaliser le TP

Il s’agit donc de réaliser une application météo. Cette application fournira les prévisions d’une ville grâce au service web de météo de worldweatheronline. Pourquoi celui-là ? Parce que je le trouve très facile à utiliser, vous le verrez par vous-même et que cela vous forcera à manipuler du JSON, ce qui ne fait jamais de mal. Il nécessite cependant une petite inscription préalable afin de disposer d’une clé d’API, mais rassurez-vous, tout est gratuit.

Allez sur http://www.worldweatheronline.com/register.aspx et remplissez le formulaire avec votre nom et votre email, ainsi que le captcha (voir la figure suivante).

Le formulaire de création de compte
Le formulaire de création de compte

Après l’inscription, vous recevez un mail pour vérifier votre compte, ainsi qu’une clé d’API une fois le mail vérifié.
Avec cette clé d’API, vous pourrez ensuite construire votre requête. Par exemple pour obtenir la météo de Bordeaux à 5 jours, j’appellerai l’URL suivante :

http://free.worldweatheronline.com/feed/weather.ashx?q=Bordeaux&format=json&num_of_days=5&key=MA_CLE_API

On peut donc préciser le nom de la ville dans le paramètre q, le type de format souhaité et le nombre de jours d’informations météos souhaités (maxi 5).
J’obtiens un JSON en retour, du genre :

{ "data" : { "current_condition" : [ …abrégé… ],
      "request" : [ { "query" : "Bordeaux, France",
            "type" : "City"
          } ],
      "weather" : [ { "date" : "2012-11-14",
            "tempMaxC" : "17",
            "tempMinC" : "8",
            "weatherDesc" : [ { "value" : "Sunny" } ],
            "weatherIconUrl" : [ { "value" : "http://www.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0001_sunny.png" } ],
            […épuré…]
          },
          { "date" : "2012-11-15",
            […abrégé…]
          },
          { "date" : "2012-11-16",
            […abrégé…]
          },
          { "date" : "2012-11-17",
            […abrégé…]
          },
          { "date" : "2012-11-18",
            […abrégé…]
          }
        ]
    } }

Nous pouvons voir quelques informations intéressantes, comme la température mini, la température maxi, une image qui illustre le temps prévu, la description du temps, etc. Ah oui tiens, la description du temps est en anglais, il pourra être judicieux de se faire une petite matrice de traduction grâce à la liste que l’on trouve ici : http://www.worldweatheronline.com/feed [...] tionCodes.xml.

Vous avez l’habitude maintenant du format JSON. Nous allons donc devoir exploiter ces informations.
L’application sera composée de 3 pages. La première page présentera les différentes conditions météos dans un Pivot qui nous permettra de consulter les conditions météo du jour et des jours suivants. Vous afficherez le nom de la ville, et dans le pivot toutes les informations que nous possédons, de la façon que vous le souhaitez.
Pendant le chargement des informations de météo, vous mettrez une barre de progression indéterminée afin que l’utilisateur ne soit pas perturbé et ne croie l'application inactive.
Cette page contiendra une barre d’application contenant deux icônes qui renverront vers une page permettant d’ajouter une ville et vers une autre page permettant de sélectionner une ville avec le ListPicker parmi la liste de toutes les villes précédemment ajoutées.
Bien sûr, l’application retiendra la liste de toutes les villes ajoutées ainsi que la dernière ville sélectionnée afin d’afficher directement les conditions météo de cette ville lors de la prochaine ouverture de l’application.

Vous vous sentez prêt ? Vous avez tout ce qu’il faut ? Alors, allez-y et créez une belle application météo.
Bon courage

Correction

Ah voilà une petite application qu’elle est sympathique. Et utile en plus ! C’est toujours pratique de pouvoir savoir s’il vaut mieux prendre son parapluie ou ses tongues.
Pour réaliser cette correction, nous allons commencer par créer la page qui permet d’ajouter une ville, je l’appelle Ajouter.xaml. Voici le XAML :

<phone:PhoneApplicationPage 
    x:Class="TpApplicationMeteo1.Ajouter"
    …

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Météo en direct" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Ajouter une ville" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle2Style}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel>
                <TextBlock Text="Nom de la ville" />
                <TextBox x:Name="NomVille" />
                <Button Content="Ajouter" Tap="Button_Tap" />
            </StackPanel>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

Elle n’est pas très compliquée, nous avons une zone de saisie permettant d’indiquer une ville et un bouton permettant d’ajouter la ville. Le code-behind est :

public partial class Ajouter : PhoneApplicationPage
{
    public Ajouter()
    {
        InitializeComponent();
    }

    private void Button_Tap(object sender, System.Windows.Input.GestureEventArgs e)
    {
        if (string.IsNullOrEmpty(NomVille.Text))
        {
            MessageBox.Show("Veuillez saisir un nom de ville");
        }
        else
        {
            IsolatedStorageSettings.ApplicationSettings["DerniereVille"] = NomVille.Text;
            List<string> nomVilles;
            if (IsolatedStorageSettings.ApplicationSettings.Contains("ListeVilles"))
                nomVilles = (List<string>)IsolatedStorageSettings.ApplicationSettings["ListeVilles"];
            else
                nomVilles = new List<string>();
            nomVilles.Add(NomVille.Text);
            IsolatedStorageSettings.ApplicationSettings["ListeVilles"] = nomVilles;

            if (NavigationService.CanGoBack)
                NavigationService.GoBack();
        }
    }
}

Après un test pour vérifier qu’il y a bien un élément dans la zone de saisie, j’enregistre la ville dans le répertoire local, en tant que dernière ville consultée et dans la liste totale des villes déjà enregistrées. Bien sûr, si cette liste n’existe pas, je la crée.
Je m’autorise même une petite navigation arrière après l’enregistrement, fainéant comme je suis, pour éviter d’avoir à appuyer sur le bouton de retour arrière (voir la figure suivante).

L'écran d'ajout de ville
L'écran d'ajout de ville

Bon, cette page est plutôt simple à faire. Passons à la page qui permet de choisir une ville déjà enregistrée, je l’appelle ChoisirVille.xaml. Le XAML sera :

<phone:PhoneApplicationPage 
    x:Class="TpApplicationMeteo1.ChoisirVille"
    …
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit">

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Météo en direct" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Choisir une ville" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle2Style}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <toolkit:ListPicker x:Name="Liste" ItemsSource="{Binding ListeVilles}" 
                                Header="Ville choisie :" 
                                CacheMode="BitmapCache">
            </toolkit:ListPicker>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

Nous notons l’utilisation du ListPicker et sa liaison à la propriété ListeVilles. Il a donc fallu importer l’espace de nom du toolkit ainsi que référencer l’assembly du toolkit. Le code-behind sera :

public partial class ChoisirVille : PhoneApplicationPage, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (null != handler)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
    {
        if (object.Equals(variable, valeur)) return false;

        variable = valeur;
        NotifyPropertyChanged(nomPropriete);
        return true;
    }

    private List<string> listeVilles;
    public List<string> ListeVilles
    {
        get { return listeVilles; }
        set { NotifyPropertyChanged(ref listeVilles, value); }
    }

    public ChoisirVille()
    {
        InitializeComponent();
        DataContext = this;
    }

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
        if (!IsolatedStorageSettings.ApplicationSettings.Contains("ListeVilles"))
        {
            MessageBox.Show("Vous devez ajouter des villes");
            if (NavigationService.CanGoBack)
                NavigationService.GoBack();
        }
        else
        {
            ListeVilles = (List<string>)IsolatedStorageSettings.ApplicationSettings["ListeVilles"];
            if (ListeVilles.Count == 0)
            {
                MessageBox.Show("Vous devez ajouter des villes");
                if (NavigationService.CanGoBack)
                    NavigationService.GoBack();
            }

            if (IsolatedStorageSettings.ApplicationSettings.Contains("DerniereVille"))
            {
                string ville = (string)IsolatedStorageSettings.ApplicationSettings["DerniereVille"];
                int index = ListeVilles.IndexOf(ville);
                if (index >= 0)
                    Liste.SelectedIndex = index;
            }
            Liste.SelectionChanged += Liste_SelectionChanged;
        }
        base.OnNavigatedTo(e);
    }

    protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
    {
        Liste.SelectionChanged -= Liste_SelectionChanged;
        base.OnNavigatedFrom(e);
    }

    private void Liste_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (Liste.SelectedItem != null)
        {
            IsolatedStorageSettings.ApplicationSettings["DerniereVille"] = (string)Liste.SelectedItem;
        }
    }
}

On commence par un petit test, s’il n’y a pas de ville à choisir alors, nous n’avons rien à faire ici. Ensuite nous associons la propriété ListeVilles au contenu du répertoire local et nous récupérons la dernière ville afin de présélectionner le ListPicker avec la ville déjà choisie. Attention, lorsque nous sélectionnons un élément ainsi, l’événement de changement de sélection est levé. Ce qui ne m’intéresse pas. C’est pour cela que je me suis abonné à cet événement après avoir modifié la propriété SelectedIndex. Pour la propreté du code, ceci implique que je me désabonne de ce même événement lorsque je quitte la page. Enfin, en cas de changement de sélection, j’enregistre la ville sélectionnée dans le répertoire local.
Pas très compliqué non plus, à part peut-être la petite astuce pour éviter que l’événement de sélection ne soit levé. De toute façon, ce genre de chose se voit très rapidement lorsque nous testons notre application, comme vous pouvez le constater sur la figure suivante.

L'écran de choix d'une ville
L'écran de choix d'une ville

Enfin, il reste la page affichant les conditions météo. Voici ma classe Meteo utilisée, ainsi que les classes générées pour le mapping des données JSON :

public class Meteo
{
    public string Date { get; set; }
    public string TemperatureMin { get; set; }
    public string TemperatureMax { get; set; }
    public Uri Url { get; set; }
    public string Temps { get; set; }
}

public class WeatherDesc
{
    public string value { get; set; }
}

public class WeatherIconUrl
{
    public string value { get; set; }
}

public class CurrentCondition
{
    public string cloudcover { get; set; }
    public string humidity { get; set; }
    public string observation_time { get; set; }
    public string precipMM { get; set; }
    public string pressure { get; set; }
    public string temp_C { get; set; }
    public string temp_F { get; set; }
    public string visibility { get; set; }
    public string weatherCode { get; set; }
    public List<WeatherDesc> weatherDesc { get; set; }
    public List<WeatherIconUrl> weatherIconUrl { get; set; }
    public string winddir16Point { get; set; }
    public string winddirDegree { get; set; }
    public string windspeedKmph { get; set; }
    public string windspeedMiles { get; set; }
}

public class Request
{
    public string query { get; set; }
    public string type { get; set; }
}

public class WeatherDesc2
{
    public string value { get; set; }
}

public class WeatherIconUrl2
{
    public string value { get; set; }
}

public class Weather
{
    public string date { get; set; }
    public string precipMM { get; set; }
    public string tempMaxC { get; set; }
    public string tempMaxF { get; set; }
    public string tempMinC { get; set; }
    public string tempMinF { get; set; }
    public string weatherCode { get; set; }
    public List<WeatherDesc2> weatherDesc { get; set; }
    public List<WeatherIconUrl2> weatherIconUrl { get; set; }
    public string winddir16Point { get; set; }
    public string winddirDegree { get; set; }
    public string winddirection { get; set; }
    public string windspeedKmph { get; set; }
    public string windspeedMiles { get; set; }
}

public class Data
{
    public List<CurrentCondition> current_condition { get; set; }
    public List<Request> request { get; set; }
    public List<Weather> weather { get; set; }
}

public class RootObject
{
    public Data data { get; set; }
}

Voyons à présent le XAML de la page, qui sera donc MainPage.xaml :

<phone:PhoneApplicationPage
    x:Class="TpApplicationMeteo1.MainPage"
    …>

    <phone:PhoneApplicationPage.Resources>
        <converter:VisibilityConverter x:Key="VisibilityConverter" />
    </phone:PhoneApplicationPage.Resources>

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Météo en direct" Style="{StaticResource PhoneTextNormalStyle}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <ProgressBar IsIndeterminate="{Binding ChargementEnCours}" Visibility="{Binding ChargementEnCours,Converter={StaticResource VisibilityConverter}}" />
            <TextBlock Text="{Binding NomVille}" Style="{StaticResource PhoneTextTitle2Style}"/>
            <phone:Pivot Grid.Row="1" ItemsSource="{Binding ListeMeteo}">
                <phone:Pivot.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Date}" />
                    </DataTemplate>
                </phone:Pivot.HeaderTemplate>
                <phone:Pivot.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding TemperatureMin}" />
                            <TextBlock Text="{Binding TemperatureMax}" />
                            <TextBlock Text="{Binding Temps}" />
                            <Image Source="{Binding Url}" Width="200" Height="200" Margin="0 50 0 0"  HorizontalAlignment="Center" />
                        </StackPanel>
                    </DataTemplate>
                </phone:Pivot.ItemTemplate>
            </phone:Pivot>
            <TextBlock Text="Ajoutez une ville avec les boutons en bas" Visibility="Collapsed" x:Name="Information" />
        </Grid>
    </Grid>
    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True">
            <shell:ApplicationBarIconButton IconUri="/Assets/Icones/add.png" Text="Ajouter" Click="Ajouter_Click"/>
            <shell:ApplicationBarIconButton IconUri="/Assets/Icones/feature.settings.png" Text="Choisir" Click="Choisir_Click"/>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

</phone:PhoneApplicationPage>

Tout d’abord, regardons la barre d’application tout en bas. Elle possède deux boutons avec deux icônes. Il faudra bien sûr rajouter ces icônes dans notre application, en action de génération égale à contenu et en copie si plus récent. Les deux boutons permettent de naviguer vers nos deux pages, créées précédemment. Ensuite, nous avons une barre de progression, liée à la propriété ChargementEnCours, que ce soit sa propriété IsIndeterminate ou sa propriété Visibility. Nous avons également le Pivot, lié à la propriété ListeMeteo. L’entête du Pivot sera le nom du jour et les éléments du corps du pivot sont les diverses propriétés de l’objet Meteo.
Accompagnant le XAML, nous aurons le code-behind suivant :

public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (null != handler)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
    {
        if (object.Equals(variable, valeur)) return false;

        variable = valeur;
        NotifyPropertyChanged(nomPropriete);
        return true;
    }

    private List<Meteo> listeMeteo;
    public List<Meteo> ListeMeteo
    {
        get { return listeMeteo; }
        set { NotifyPropertyChanged(ref listeMeteo, value); }
    }

    private bool chargementEnCours;
    public bool ChargementEnCours
    {
        get { return chargementEnCours; }
        set { NotifyPropertyChanged(ref chargementEnCours, value); }
    }

    private string nomVille;
    public string NomVille
    {
        get { return nomVille; }
        set { NotifyPropertyChanged(ref nomVille, value); }
    }

    public MainPage()
    {
        InitializeComponent();
        DataContext = this;
    }

    protected async override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
        if (IsolatedStorageSettings.ApplicationSettings.Contains("DerniereVille"))
        {
            Information.Visibility = Visibility.Collapsed;
            ChargementEnCours = true;
            NomVille = (string)IsolatedStorageSettings.ApplicationSettings["DerniereVille"];
            WebClient client = new WebClient();
            try
            {
                ChargementEnCours = false;
                string resultatMeteo = await client.DownloadStringTaskAsync(new Uri(string.Format("http://free.worldweatheronline.com/feed/weather.ashx?q={0}&format=json&num_of_days=5&key=MA_CLE_API", NomVille.Replace(' ', '+')), UriKind.Absolute));

                RootObject resultat = JsonConvert.DeserializeObject<RootObject>(resultatMeteo);
                List<Meteo> liste = new List<Meteo>();
                foreach (Weather temps in resultat.data.weather.OrderBy(w => w.date))
                {
                    Meteo meteo = new Meteo { TemperatureMax = temps.tempMaxC + " °C", TemperatureMin = temps.tempMinC + " °C" };
                    DateTime date;
                    if (DateTime.TryParse(temps.date, out date))
                    {
                        meteo.Date = date.ToString("dddd dd MMMM");
                        meteo.Temps = GetTemps(temps.weatherCode);
                        WeatherIconUrl2 url = temps.weatherIconUrl.FirstOrDefault();
                        if (url != null)
                        {
                            meteo.Url = new Uri(url.value, UriKind.Absolute);
                        }
                    }
                    liste.Add(meteo);
                }
                ListeMeteo = liste;
            }
            catch (Exception)
            {
                MessageBox.Show("Impossible de récupérer les informations de météo, vérifiez votre connexion internet");
            }
        }
        else
            Information.Visibility = Visibility.Visible;

        base.OnNavigatedTo(e);
    }

    private string GetTemps(string code)
    {
        // à compléter ...
        switch (code)
        {
            case "113":
                return "Clair / Ensoleillé";
            case "116":
                return "Partiellement nuageux";
            case "119":
                return "Nuageux";
            case "296":
                return "Faible pluie";
            case "353":
                return "Pluie";
            default:
                return "";
        }
    }

    private void Ajouter_Click(object sender, EventArgs e)
    {
        NavigationService.Navigate(new Uri("/Ajouter.xaml", UriKind.Relative));
    }

    private void Choisir_Click(object sender, EventArgs e)
    {
        NavigationService.Navigate(new Uri("/ChoisirVille.xaml", UriKind.Relative));
    }
}

On commence par tester si la dernière ville consultée existe bien. Si ce n’est pas le cas, nous affichons un petit message pour dire quoi faire. Puis nous démarrons le téléchargement des conditions de météo, sans oublier d’animer la barre de progression. Après avoir attendu la fin du téléchargement (mot clé await), nous pouvons utiliser JSON.NET pour extraire les conditions météo et les mettre dans la propriété liée au Pivot. Nous remarquons au passage ma technique hautement élaborée pour obtenir une traduction de la description du temps… Quoi il manque des traductions ? N’hésitez pas à compléter avec les vôtres ...
Enfin, les deux méthodes de clic sur les boutons de la barre d’application appellent simplement le service de navigation (voir la figure suivante).

Affichage de la météo
Affichage de la météo

Et si vous gériez l’orientation multiple maintenant ? :D

Dans cette partie, nous avons étudiés plusieurs contrôles qui font partie intégrante du développement d’applications pour Windows Phone.
Nous avons dans un premier temps étudié le contrôle Panorama et le contrôle Pivot, qui sont des contrôles indispensables pour offrir une expérience utilisateur intéressante lorsque l’on a des données à présenter. Nous avons également vu comment afficher des pages web grâce au contrôle WebBrowser. Puis nous avons appris à gérer les différentes orientations et résolutions qu’un Windows Phone peut prendre.
Nous avons pu voir également comment fonctionnait la barre d’application qui permet d’avoir une espèce de menu accessible n’importe quand. Nous avons aperçu quelques contrôles issus de la bibliothèque gratuite Windows Phone. Enfin, nous avons également découvert la puissance du contrôle Map pour afficher des cartes, des itinéraires et les extensions du toolkit pour afficher des points d’intérêts.

Tous ces contrôles nous permettent d’enrichir nos applications. Vous avez pu le voir lors de la réalisation de notre application météo. Ils nous fournissent tous les éléments dont nous avons besoin pour réaliser de belles applications fonctionnelles. Usez-en, abusez-en, ils sont là pour ça ;) .

Exemple de certificat de réussite
Exemple de certificat de réussite