Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

[WP7] Todo List avec Mango, partie 1 : Linq to SQL et Alternative Tiles

Je me propose de présenter un aperçu des nouveautés apportées par Mango au travers de l’écriture d’une application de gestion de tâches. Celle-ci permettra de stocker une liste de tâches à réaliser, avec une échéance, de les épingler sur l’écran d’accueil, et de les colorer en fonction du temps restant. Ceci permettra de couvrir successivement l’utilisation de Linq to SQL, des tiles alternatives, des background agent, et des reminder.

Commençons par créer la solution, que nous appellerons “MangoTaskList”. Le projet principal est de type “Windows Phone Application”. A la création du projet, Visual Studio demande quelle version de WP7 vous voulez ciblez. Veillez bien à sélectionner “Windows Phone 7.1” pour profiter des nouveautés de Mango.

image

Une fois la solution créée, ajoutons tout de suite un nouveau projet, de type “Windows Phone Class Library”, que nous appellerons “MangoTaskList.DataModel”. Celui-ci nous simplifiera l’implémentation des background agent. Nous référençons bien entendu ce projet depuis le projet principal.

Pour le stockage des données, nous allons utiliser une base de données locale SQL CE, et y accéder via Linq to SQL. Pour cela, commençons par ajouter une classe “Task” à notre projet DataModel. Cette classe aura l’attribut “[Table]”, indiquant qu’il s’agit… d’une table. La classe implémentera les interfaces INotifyPropertyChanging et INotifyProperyChanged. L’implémentation de la propriété INotifyPropertyChanging est recommandée par la documentation MSDN, et permettrait apparemment de réduire la quantité de mémoire utilisée pour le tracking des modifications sur les objets.

Notre objet Task aura des propriétés pour stocker le titre, une description, et l’échéance. Nous ajoutons également une propriété “Id” qui servira d’identifiant interne.
Au final, la classe ressemblera à ça :

   1: [Table]
   2: public class Task : INotifyPropertyChanging, INotifyPropertyChanged
   3: {
   4:     private int id;
   5:     private string title;
   6:     private string description;
   7:     private DateTime dueDate;
   8:  
   9:     [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
  10:     public int Id
  11:     {
  12:         get
  13:         {
  14:             return this.id;
  15:         }
  16:  
  17:         set
  18:         {
  19:             this.NotifyPropertyChanging("Id");
  20:             this.id = value;
  21:             this.NotifyPropertyChanged("Id");
  22:         }
  23:     }
  24:  
  25:     [Column]
  26:     public string Title
  27:     {
  28:         get
  29:         {
  30:             return this.title;
  31:         }
  32:  
  33:         set
  34:         {
  35:             this.NotifyPropertyChanging("Title");
  36:             this.title = value;
  37:             this.NotifyPropertyChanged("Title");
  38:         }
  39:     }
  40:  
  41:     [Column]
  42:     public string Description
  43:     {
  44:         get
  45:         {
  46:             return this.description;
  47:         }
  48:  
  49:         set
  50:         {
  51:             this.NotifyPropertyChanging("Description");
  52:             this.description = value;
  53:             this.NotifyPropertyChanged("Description");
  54:         }
  55:     }
  56:  
  57:     [Column]
  58:     public DateTime DueDate
  59:     {
  60:         get
  61:         {
  62:             return this.dueDate;
  63:         }
  64:  
  65:         set
  66:         {
  67:             this.NotifyPropertyChanging("DueDate");
  68:             this.dueDate = value;
  69:             this.NotifyPropertyChanged("DueDate");
  70:         }
  71:     }
  72:  
  73:     public event PropertyChangingEventHandler PropertyChanging;
  74:     public event PropertyChangedEventHandler PropertyChanged;
  75:     
  76:     protected void NotifyPropertyChanging(string propertyName)
  77:     {
  78:         var eventHandler = this.PropertyChanging;
  79:  
  80:         if (eventHandler != null)
  81:         {
  82:             eventHandler(this, new PropertyChangingEventArgs(propertyName));
  83:         }
  84:     }
  85:  
  86:     protected void NotifyPropertyChanged(string propertyName)
  87:     {
  88:         var eventHandler = this.PropertyChanged;
  89:  
  90:         if (eventHandler != null)
  91:         {
  92:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  93:         }
  94:     }
  95: }
  96:  

L’attribut [Column] permet d’indiquer quels champs doivent être stockés. Nous ajoutons quelques paramètres à cet attribut sur la propriété Id, pour indiquer qu’il s’agit d’une clé primaire, stockée dans un champ Identity (pour avoir un id incrémenté automatiquement), avec une valeur automatiquement générée à l’insertion.

Ceci fait, nous pouvons ajouter dans le projet DataModel une classe “TaskDataContext”. Celle-ci va hériter de System.Data.Linq.DataContext, stocker la chaine de connexion, et exposer une liste d’objets Task qui sera automatiquement mappée sur la table correspondante par Linq to SQL.

   1: public class TaskDataContext : DataContext
   2: {
   3:     protected const string ConnectionString = "Data Source=isostore:/ToDo.sdf";
   4:  
   5:     public TaskDataContext()
   6:         : base(ConnectionString)
   7:     { 
   8:     }
   9:  
  10:     public Table<Task> Tasks;
  11: }

Comme la chaine de connexion ne changera pas, et par souci de simplicité, nous la passons directement au constructeur.

Maintenant, il n’y a plus qu’à s’assurer que la base de données soit créée au lancement de l’application. Pour cela, nous ajoutons ces quelques lignes de code à la fin du constructeur dans le fichier App.xaml.cs :

   1: using (var dataContext = new TaskDataContext())
   2: {
   3:     if (!dataContext.DatabaseExists())
   4:     {
   5:         dataContext.CreateDatabase();
   6:     }
   7: }

Maintenant que nous avons fait le nécessaire pour pouvoir lire et stocker des données, il n’y a plus qu’à s’occuper de l’application proprement dite. Le fonctionnement sera simple : si l’utilisateur lance directement l’application, il se retrouve sur une page lui permettant de créer une nouvelle tâche. Celle-ci est sauvegardée en base et une tile est créée sur l’écran d’accueil. Un clic sur cette tile permet d’afficher le détail de la tâche correspondante, d’en modifier les données, et éventuellement de la supprimer. Pour associer une tile à une tâche, nous ajouterons le paramètre “TileId” à l’uri de la page.

Nous allons donc créer les champs correspondant dans MainPage.xaml :

   1: <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
   2:     <Grid>
   3:         <Grid.ColumnDefinitions>
   4:             <ColumnDefinition Width="120" />
   5:             <ColumnDefinition />
   6:         </Grid.ColumnDefinitions>
   7:         <Grid.RowDefinitions>
   8:             <RowDefinition Height="90" />
   9:             <RowDefinition Height="150"/>
  10:             <RowDefinition Height="200"/>
  11:             <RowDefinition />
  12:             <RowDefinition />
  13:         </Grid.RowDefinitions>
  14:         
  15:         <TextBlock Grid.Row="0" Grid.Column="0" Text="Title : " HorizontalAlignment="Right" VerticalAlignment="Center" />
  16:         <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=TaskTitle, Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Center" />
  17:  
  18:         <TextBlock Grid.Row="2" Grid.Column="0" Text="Description : " HorizontalAlignment="Right" VerticalAlignment="Center" />
  19:         <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=Description, Mode=TwoWay}" HorizontalAlignment="Stretch" VerticalAlignment="Center" Height="200"/>
  20:        
  21:         
  22:         <TextBlock Grid.Row="1" Grid.Column="0" Text="Due date : " HorizontalAlignment="Right" VerticalAlignment="Center" />
  23:  
  24:         <StackPanel Grid.Row="1" Grid.Column="1" >
  25:             <toolkit:DatePicker x:Name="DatePicker" Value="{Binding Path=DueDate, Mode=TwoWay}" />
  26:             <toolkit:TimePicker x:Name="TimePicker" Value="{Binding Path=DueTime, Mode=TwoWay}" />
  27:         </StackPanel>
  28:             
  29:         <StackPanel x:Name="PanelCreateTask" Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
  30:             <Button x:Name="ButtonCreate" Content="Create" HorizontalAlignment="Left" VerticalAlignment="Top" Click="ButtonCreate_Click" />                    
  31:         </StackPanel>
  32:  
  33:         <StackPanel x:Name="PanelEditTask" Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
  34:             <Button x:Name="ButtonSave" Content="Save" HorizontalAlignment="Left" VerticalAlignment="Top" Click="ButtonSave_Click" />
  35:             <Button x:Name="ButtonDelete" Content="Delete" HorizontalAlignment="Left" VerticalAlignment="Top" Click="ButtonDelete_Click" />
  36:         </StackPanel>
  37:     </Grid>
  38: </Grid>

Notez que nous utilisons les contrôles DatePicker et TimePicker du Silverlight Toolkit, afin de s’occuper respectivement de la saisie de la date et de l’heure.

La page contient deux panels, l’un avec un bouton “Create”, l’autre avec deux boutons “Update” et “Delete”. Le premier panel sera affiché lorsque l’application est lancée directement, le second lorsque l’utilisateur clique sur une tile.

Au niveau du code behind, nous définissons le datacontext et créons les propriétés pour se binder dessus (les puristes opteraient probablement pour un ViewModel dans une classe séparée, mais nous allons ici au plus simple). Nous ajoutons également une propriété qui va contenir le TaskDataContext :

   1: public MainPage()
   2: {
   3:     this.DataContext = this;
   4:  
   5:     this.InitializeComponent();
   6:  
   7:     this.DueDate = DateTime.Now;
   8:     this.DueTime = this.DueDate;
   9: }
  10:  
  11: public Task Task { get; set; }
  12: public string TaskTitle { get; set; }
  13: public string Description { get; set; }
  14:  
  15: public DateTime DueDate { get; set; }
  16:  
  17: public DateTime DueTime { get; set; }
  18:  
  19: public DateTime DueDateTime
  20: {
  21:     get { return this.DueDate.Date.Add(this.DueTime - this.DueTime.Date); }
  22: }
  23:  
  24: protected TaskDataContext TaskDataContext { get; set; }
  25:  

Lorsque la page principale est chargée, nous créons le TaskDataContext si nécessaire. Nous affichons également les panels appropriés, selon que l’utilisateur a cliqué sur une tile ou non. Nous déterminons cela en vérifiant la présence du paramètre TaskId dans l’uri. Si nous le trouvons, nous chargeons également la tâche correspondante. Pour charger la tâche, rien de plus simple : il suffit de faire une requête Linq sur la propriété Tasks du TaskDataContext.

   1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     if (this.TaskDataContext == null)
   6:     {
   7:         this.TaskDataContext = new TaskDataContext();
   8:     }
   9:  
  10:     if (this.NavigationContext.QueryString.ContainsKey("TaskId"))
  11:     {
  12:         this.PanelCreateTask.Visibility = Visibility.Collapsed;
  13:         this.PanelEditTask.Visibility = Visibility.Visible;
  14:  
  15:         int taskId = int.Parse(this.NavigationContext.QueryString["TaskId"]);
  16:  
  17:         if (this.Task == null || this.Task.Id != taskId)
  18:         {
  19:             var task = this.TaskDataContext.Tasks.First(t => t.Id == taskId);
  20:  
  21:             this.TaskTitle = task.Title;
  22:             this.Description = task.Description;
  23:             this.DueDate = task.DueDate;
  24:             this.DueTime = task.DueDate;
  25:  
  26:             this.Task = task;
  27:         }
  28:     }
  29:     else
  30:     {
  31:         this.PanelCreateTask.Visibility = Visibility.Visible;
  32:         this.PanelEditTask.Visibility = Visibility.Collapsed;
  33:     }
  34: }

Nous pouvons maintenant nous occuper des évènements déclenchés lors de clics sur les boutons.

Le bouton Create créé la tâche correspondante, la stocke, puis créé la tile sur l’écran d’accueil. Pour créer la tile, nous instancions un objet “StandardTileData”, et renseignons ses propriétés à notre convenance :

- Title : le titre de la tile
- BackTitle : le titre de l’arrière de la tile
- BackContent : le texte affiché à l’arrière de la tile
- Count : un nombre affiché sur la tile
- BackgroundImage : l’image d’arrière-plan
- BackBackgroundImage : l’image d’arrière-plan de l’arrière de la tile

Nous appelons ensuite la fonction “ShellTile.Create” en lui passant en paramètre l’objet StandardTileData et l’uri de la page vers laquelle elle doit renvoyer.

Pour créer l’objet Task à stocker, nous instancions simplement la classe et renseignons ses propriétés. Ensuite, il n’y a plus qu’à indiquer au TaskDataContext que nous souhaitons insérer l’objet dans la table à l’aide de la méthode InsertOnSubmit. Les modifications sont appliquées après appel à la méthode SubmitChanges.

   1: private void ButtonCreate_Click(object sender, RoutedEventArgs e)
   2: {
   3:     var task = new Task { Title = this.TaskTitle, DueDate = this.DueDateTime, Description = this.Description };
   4:  
   5:     this.TaskDataContext.Tasks.InsertOnSubmit(task);
   6:     this.TaskDataContext.SubmitChanges();
   7:  
   8:     var tile = new StandardTileData
   9:     {
  10:         Title = task.Title,
  11:         BackTitle = task.Title,
  12:         BackContent = task.DueDate.ToString()
  13:     };
  14:  
  15:     ShellTile.Create(new Uri(string.Format("/MainPage.xaml?TaskId={0}", task.Id), UriKind.Relative), tile);
  16: }

Le clic sur update met à jour la tâche ainsi que la tile. Pour la tâche, il suffit de modifier les propriétés correspondantes dans l’objet Task, puis d’appeler la méthode SubmitChanges du TaskDataContext.
Pour modifier la tile, c’est un peu plus complèxe. Il faut d’abord la récupérer dans l’énumération ShellTile.ActiveTiles. Nous nous basons pour cela sur son uri. Une fois la tile récupérée, il faut appeler la méthode Update, à laquelle nous passons un objet StandardTileData, contenant uniquement les propriétés que nous souhaitons modifier.

   1: private void ButtonSave_Click(object sender, RoutedEventArgs e)
   2: {
   3:     this.Task.Title = this.TaskTitle;
   4:     this.Task.Description = this.Description;
   5:     this.Task.DueDate = this.DueDateTime;
   6:  
   7:     this.TaskDataContext.SubmitChanges();
   8:  
   9:     var tile = ShellTile.ActiveTiles.FirstOrDefault(t => t.NavigationUri.OriginalString.Contains("TaskId=" + this.Task.Id));
  10:  
  11:     if (tile != null)
  12:     {
  13:         var tileData = new StandardTileData
  14:         {
  15:             Title = this.Task.Title,
  16:             BackTitle = this.Task.Title,
  17:             BackContent = this.Task.DueDate.ToString()
  18:         };
  19:  
  20:         tile.Update(tileData);
  21:     }
  22:  
  23:     MessageBox.Show("Task updated");
  24: }

Enfin, le clic sur le bouton delete supprime la tâche et la tile correspondante. Pour cela, nous utilisons respectivement les méthodes DeleteOnSubmit du TaskDataContext et Delete de la tile.

   1: private void ButtonDelete_Click(object sender, RoutedEventArgs e)
   2: {
   3:     int taskId = this.Task.Id;
   4:  
   5:     this.TaskDataContext.Tasks.DeleteOnSubmit(this.Task);
   6:  
   7:     this.TaskDataContext.SubmitChanges();
   8:  
   9:     var tile = ShellTile.ActiveTiles.FirstOrDefault(t => t.NavigationUri.OriginalString.Contains("TaskId=" + taskId));
  10:  
  11:     if (tile != null)
  12:     {
  13:         tile.Delete();
  14:     }
  15:  
  16:     this.NavigationService.GoBack();
  17: }

Et voilà le boulot ! Nous verrons dans la prochaine partie comment apporter un peu de vie à ces tiles à l’aide d’un Background Agent.

image

Publié dimanche 29 mai 2011 22:38 par KooKiz
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

Pas de commentaires
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- Etendre le Team Web Access de TFS 2012 – Step 0 par Philippe Didiergeorges Aka Philess le 05-23-2013, 23:48

- Simuler facilement l’envoi de mail par Blog de Jérémy Jeanson le 05-22-2013, 12:52

- ProcDump 6.0 : support du filtrage sur messages d'exceptions .NET, des filtres multiples et du ciblage par nom de service par CoqBlog le 05-20-2013, 14:50

- Votez pour le TOP 10 des influenceurs SharePoint francophones ! par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 12:59

- [Conf’SharePoint] Dernier rappel ! :-) par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 09:09

- [ #SharePoint 2013 ] les modèles de sites standards… par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 09:03

- 10 erreurs de compréhension concernant SharePoint… par Le blog de Patrick [MVP SharePoint] le 05-20-2013, 08:27

- Conf’SharePoint : 10 bonnes raisons pour ne pas la rater par Le petit blog de Pierre / Pierre's little blog le 05-14-2013, 02:24

- [Event] Soirée de lancement Agile .NET France à Lyon par Blog Agile/ALM de Vincent THAVONEKHAM le 05-13-2013, 01:29

- .NET / Debug : inspection de la mémoire d'applications .NET (dump ou processus live) : première livraison d'une librairie .NET par Microsoft par CoqBlog le 05-11-2013, 22:21