Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Kévin Gosse

Clair, .NET, et précis
[WP7] Inject a file in a xap using post-build event

Let’s say that your application is dynamically loading resources, and you have to constantly add/remove new resources during the development. In this scenario, it could be a huge-time saver to just tell Visual Studio to take the contents of a folder and inject it into your application. I don’t think there’s an out-of-the-box way to do that, but you can inject the files yourself using a post-build event.

How? The output of every WP7 project is a .xap file, which is just a zip file with a different extension. So you can edit it using whichever zip extractor you like. In our case, let’s use 7zip.

First, create a basic application, and put an image control in the xaml:

   1: <Grid x:Name="ContentPanel"
   2:       Grid.Row="1"
   3:       Margin="12,0,12,0">
   4:     <Image x:Name="Image"
   5:            Width="300"
   6:            Height="300"
   7:            Source="Someimage.png" />
   8: </Grid>

Make sure that the ‘someimage.png’ file does not exist in your project. Compile, run, and surely enough nothing is displayed.

Now, add the following line to the post-build event of your project: (right-click on the project, properties, “Build Events”)

   1: "C:\Program Files (x86)\7-Zip\7z.exe" a -tzip $(ProjectDir)$(OutDir)Test.xap E:\someimage.png
  • "C:\Program Files (x86)\7-Zip\7z.exe" is self explanatory: this is the path where you installed 7zip


  • the “a” switch tells 7zip to add a file to the archive


  • the “-tzip” switch forces the archive format to zip


  • “$(ProjectDir)$(OutDir)” is automatically replaced by the output path of your project


  • “Test.xap” is the name of the generated xap. Change it with the name of the file generated by your project


  • “E:\someimage.png” is the path of the file(s) to inject

 

Rebuild the solution (to force Visual Studio to re-deploy the xap), then run the application, and your ‘someimage.png’ image should be displayed, even though it never had been added to the Visual Studio solution.

Hope that helps!

[WP7] Cryptic error message in XAML

Unexpected NONE in parse rule ElementBody ::= ATTRIBUTE* ( PropertyElement | Content )* . ENDTAG..

Best. Error message. Ever. Thanks Silverlight.

So, what’s going on?

For the sake of all the devs who’ll come to this page using Google, let’s describe one of the causes of the error message.

   1: <shell:ApplicationBar.MenuItems />

Is that it?

Yes, pretty much. I don’t know if it’s the only possible cause, but it seems that this error message is triggered when an empty element is used in the XAML for a collection. In this case it’s the MenuItems of an ApplicationBar, but you can also have the problem with an empty row or column definition of a grid:

   1: <Grid.ColumnDefinitions />

How to solve it? Either remove the element, if you can, or replace it by an opening tag and the corresponding closing tag:

   1: <Grid.ColumnDefinitions></Grid.ColumnDefinitions>

I’m not sure why the compiler chokes on this, but it’s quite a cryptic message for a simple problem.

[WP7] How to press the mouse on a control, and detect MouseLeftButtonUp on another

Another issue I found on StackOverflow, which is way more tricky that it seems.

Let’s say we have a Silverlight WP7 application, and we want to add a drag&drop scenario. The user first taps an element, drags his finger to another, and raises his finger on another element. Easy enough! Just handle the MouseLeftButtonDown on each element, store which element triggered the event in a property, handle the MouseLeftButtonUp on each element, and then we have the originator and the destination!

… right?

Well, so I would have thought.

Unfortunately, the MouseLeftButtonUp event will only be triggered if the ‘mouse’ left button (or your finger, sir Claus Jørgensen ;o) )1 is released on the very same control that it was pressed on.

So, if we use this XAML:

   1: <Grid x:Name="ContentPanel"
   2:       Grid.Row="1"
   3:       Margin="12,0,12,0"
   4:       MouseLeftButtonUp="ContentPanel_MouseLeftButtonUp">
   5:     <Grid.RowDefinitions>
   6:         <RowDefinition />
   7:         <RowDefinition />
   8:     </Grid.RowDefinitions>
   9:     <Grid x:Name="g1"
  10:           Background="Green"
  11:           MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  12:           MouseLeftButtonUp="Grid_MouseLeftButtonUp"
  13:           Tag="dragdrop" />
  14:     <Grid x:Name="g2"
  15:           Grid.Row="1"
  16:           Background="Blue"
  17:           MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  18:           MouseLeftButtonUp="Grid_MouseLeftButtonUp"
  19:           Tag="dragdrop" />
  20:  
  21: </Grid>

The layout looks like:

image

In the MouseLeftButtonUp event handler, we want to paint the destination grid:

   1: private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   2: {
   3:     var grid = (Grid)sender;
   4:     
   5:     grid.Background = new SolidColorBrush(Colors.Red);
   6: }

Unfortunately, it doesn’t work. Press your finger on the upper green grid, MouseLeftButtonDown is triggered. Drag your finger to the lower blue greed, lift your finger, MouseLeftButtonUp isn’t triggered.

I already encountered a similar issue with Silverlight a few years ago, so I knew about the CaptureMouse method. What is it? Basically it tells a control to keep track of the mouse even if events are triggered outside of the control bounds. Let’s try to use it.

In the MouseLeftButtonDown, simply capture the mouse:

   1: private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
   2: {
   3:     ((UIElement)sender).CaptureMouse();
   4: }

Now the MouseLeftButtonUp event is triggered! Unfortunately, it’s triggered on the control we called CaptureMouse on, so we still don’t know on which control the finger was when released. But we have the mouse coordinates, so we should be able to find it somehow.

And that ‘somehow’ is the ‘VisualTreeHelper.FindElementsInHostCoordinates’ method. It takes a point and a control, and enumerates all the control’s child that are located at the specified coordinates. Sounds good enough.

So now let’s rewrite our MouseLeftButtonUp event handler, without forgetting to release the mouse capture:

   1: private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   2: {
   3:     var grid = (Grid)sender;
   4:     
   5:     grid.ReleaseMouseCapture();
   6:  
   7:     var mouseUpGrid = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
   8:         .OfType<Grid>()
   9:         .FirstOrDefault();
  10:  
  11:     if (mouseUpGrid != null)
  12:     {
  13:         Debug.WriteLine("MouseUp in " + mouseUpGrid.Name);
  14:         mouseUpGrid.Background = new SolidColorBrush(Colors.Red);
  15:     }
  16: }

Test on the emulator, and… it works!

Well, sure it does, but now let’s imagine a more complex scenario:

   1: <Grid x:Name="ContentPanel"
   2:       Grid.Row="1"
   3:       Margin="12,0,12,0">
   4:     <Grid.RowDefinitions>
   5:         <RowDefinition />
   6:         <RowDefinition />
   7:     </Grid.RowDefinitions>
   8:     <Grid x:Name="g1"
   9:           Background="Green"
  10:           MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  11:           MouseLeftButtonUp="Grid_MouseLeftButtonUp" />
  12:     <Grid x:Name="DummyGrid"
  13:           Grid.Row="1"
  14:           Background="Gray">
  15:         <Grid x:Name="g2"
  16:               Margin="100 5 5 5"
  17:               Background="Blue"
  18:               MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  19:               MouseLeftButtonUp="Grid_MouseLeftButtonUp" />
  20:     </Grid>
  21: </Grid>

The layout looks like:

image

We only want to be able to drag from the green grid to the blue one (and the other way around). Unfortunately, using the previous code, the drag&drop will be detected even if we lift our finger on the gray grid. So we need a way to opt-in to the drag&drop detection, rather than detect it on all the grids.

The dirty way would be to store the name of the blue and green grids, and check the name in the MouseLeftButtonDown event. But we can make something more generic using the Tag property.

What is Tag? It’s an object property available on every control. What is stored in this property? Nothing. It’s meant to be used by you, and only by you, to store whichever object you want to personalize the control.

In our XAML, let’s add the “dragdrop” string in the Tag of the green and blue grids:

   1: <Grid x:Name="ContentPanel"
   2:       Grid.Row="1"
   3:       Margin="12,0,12,0">
   4:     <Grid.RowDefinitions>
   5:         <RowDefinition />
   6:         <RowDefinition />
   7:     </Grid.RowDefinitions>
   8:     <Grid x:Name="g1"
   9:           Background="Green"
  10:           MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  11:           MouseLeftButtonUp="Grid_MouseLeftButtonUp"
  12:           Tag="dragdrop" />
  13:     <Grid x:Name="DummyGrid"
  14:           Grid.Row="1"
  15:           Background="Gray">
  16:         <Grid x:Name="g2"
  17:               Margin="100 5 5 5"
  18:               Background="Blue"
  19:               MouseLeftButtonDown="Grid_MouseLeftButtonDown"
  20:               MouseLeftButtonUp="Grid_MouseLeftButtonUp"
  21:               Tag="dragdrop" />
  22:     </Grid>
  23: </Grid>

Then, in the MouseLeftButtonUp event handler, it’s only a matter of filtering which controls have the appropriate tag:

   1: private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
   2: {
   3:     var grid = (Grid)sender;
   4:     
   5:     grid.ReleaseMouseCapture();
   6:  
   7:     var mouseUpGrid = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
   8:         .OfType<Grid>()
   9:         .FirstOrDefault(element => element.Tag is string && (string)element.Tag == "dragdrop");
  10:  
  11:     if (mouseUpGrid != null)
  12:     {
  13:         Debug.WriteLine("MouseUp in " + mouseUpGrid.Name);
  14:         mouseUpGrid.Background = new SolidColorBrush(Colors.Red);
  15:     }
  16: }

Now the gray grid is excluded, as expected.

And that’s how a seemingly easy problem turns out on a solution requiring the use of Tag, VisualTreeHelper.FindElementsInHostCoordinates, and CaptureMouse. Quite instructive if you ask me.

 

 

 


1: Ok, now I’m putting private jokes on my blog articles. We’re doomed.

[WP7] ApplicationBar flickering when the phone theme is white

It’s an interesting issue I found on StackOverflow. Interesting because I often forget to test my applications using the Windows Phone’s white theme, so this kind of problem usually goes unnoticed until a user reports it.

Create a simple WP7 application with a black background and an ApplicationBar. Add a button to toggle the bar’s visibility.

The XAML should look like:

   1: <phone:PhoneApplicationPage x:Class="WP7ForumTest.MainPage"
   2:                             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:                             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:                             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   5:                             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
   6:                             xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
   7:                             xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
   8:                             d:DesignHeight="696"
   9:                             d:DesignWidth="480"
  10:                             FontFamily="{StaticResource PhoneFontFamilyNormal}"
  11:                             FontSize="{StaticResource PhoneFontSizeNormal}"
  12:                             Foreground="{StaticResource PhoneForegroundBrush}"
  13:                             Orientation="Portrait"
  14:                             shell:SystemTray.IsVisible="True"
  15:                             SupportedOrientations="Portrait"
  16:                             mc:Ignorable="d">
  17:     <Grid Background="Black">
  18:         <Button VerticalAlignment="Top"
  19:                 Background="Black"
  20:                 BorderBrush="White"
  21:                 Click="ButtonClick"
  22:                 Content="Toggle Application Bar"
  23:                 Foreground="White" />
  24:     </Grid>
  25:     <phone:PhoneApplicationPage.ApplicationBar>
  26:         <shell:ApplicationBar BackgroundColor="Black"
  27:                               ForegroundColor="White">
  28:             <shell:ApplicationBarIconButton IconUri="/icon.png" Text="Menu" />
  29:         </shell:ApplicationBar>
  30:     </phone:PhoneApplicationPage.ApplicationBar>
  31: </phone:PhoneApplicationPage>
And the ButtonClick method:
   1: private void ButtonClick(object sender, RoutedEventArgs e)
   2: {
   3:     ApplicationBar.IsVisible = !ApplicationBar.IsVisible;
   4: }

Now launch the WP7 emulator, go in the settings, and set the phone theme to ‘light’. Then start the application, and try pressing the button: the application bar disappears as expected, but you may notice a quick white flickering. The same occurs when showing back the bar.

Of course, the problem is also reproducible with a white background and the ‘dark’ phone theme, only slightly less noticeable.

So, what’s happening? Unfortunately, the ApplicationBar control is unmanaged, I can’t dig in it using Reflector. Therefore, I can only make an hypothesis: the background part under the application bar isn’t painted when the bar is visible. When the hiding animation starts, the bar seems to move but the control still occupies the same space, and the background still isn’t painted. Thus it is shown with the phone’s default color instead of the grid’s background. When the animation is over, the ApplicationBar sets its own visibility to ‘collapsed’. The runtime now knows that it have to draw the background, and the white artifact disappears.

How to fix it? We have to find a way to force WP7 to paint the grid’s background under the ApplicationBar. For this, we have just the property we need: Opacity. Just set the opacity of the bar to 0.99: the value is so high that the transparency effect will be invisible, but the runtime will have to draw the background.

   1: <phone:PhoneApplicationPage.ApplicationBar>
   2:     <shell:ApplicationBar BackgroundColor="Black" 
   3:                           ForegroundColor="White"
   4:                           Opacity=".99">
   5:         <shell:ApplicationBarIconButton IconUri="/icon.png" Text="Button 1" />
   6:     </shell:ApplicationBar>
   7: </phone:PhoneApplicationPage.ApplicationBar>

Compile, run, and the flickering effect should be gone.

[C#/WPF] BindingList

On va s’écarter un peu de WP7 pour un article qui s’inscrit dans la lignée du “Après 8 ans de .NET, j’en découvre encore”.

Au programme, un problème assez frustrant que j’ai pu rencontrer à plusieurs reprise, dans des applications WPF. Commençons par poser les bases : nous avons une liste d’éléments, avec une propriété numérique (ici, “Count”). Nous voulons afficher ces éléments ainsi que la somme de leur “Count”, et permettre à l’utilisateur de les modifier individuellement (ce qui doit bien entendu mettre à jour la somme).

Pas de grande surprise au niveau du XAML (notez toutefois l’utilisation du fort pratique StringFormat pour le binding) :

   1: <Window x:Class="WpfApplication1.MainWindow"
   2:         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:         Title="MainWindow" Height="900" Width="500">
   5:     <StackPanel>
   6:         <TextBlock Text="{Binding Path=Total, StringFormat=Total: \{0\}}" />
   7:         
   8:         <ItemsControl ItemsSource="{Binding Path=Items}">
   9:             <ItemsControl.ItemTemplate>
  10:                 <DataTemplate>
  11:                     <TextBox Text="{Binding Path=Count}" />
  12:                 </DataTemplate>
  13:             </ItemsControl.ItemTemplate>
  14:         </ItemsControl>
  15:     </StackPanel>
  16: </Window>

Et la déclaration de la classe “Item” :

   1: public class Item : INotifyPropertyChanged
   2: {
   3:     private int count;
   4:  
   5:     public event PropertyChangedEventHandler PropertyChanged;
   6:  
   7:     public int Count
   8:     {
   9:         get
  10:         {
  11:             return this.count;
  12:         }
  13:  
  14:         set
  15:         {
  16:             this.count = value;
  17:             this.NotifyPropertyChanged("Count");
  18:         }
  19:     }
  20:  
  21:     protected void NotifyPropertyChanged(string propertyName)
  22:     {
  23:         var eventHandler = this.PropertyChanged;
  24:  
  25:         if (eventHandler != null)
  26:         {
  27:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  28:         }
  29:     }
  30: }

Dans le ViewModel, nous stockons les objets dans une ObservableCollection, afin que la vue soit automatiquement notifiée quand un item est ajouté ou retiré :

   1: public class ViewModel : INotifyPropertyChanged
   2: {
   3:     public ViewModel()
   4:     {
   5:         this.Items = new ObservableCollection<Item>();
   6:  
   7:         this.Items.Add(new Item { Count = 1 });
   8:         this.Items.Add(new Item { Count = 2 });
   9:         this.Items.Add(new Item { Count = 3 });
  10:         this.Items.Add(new Item { Count = 4 });
  11:  
  12:         this.ComputeSum();
  13:     }
  14:  
  15:     public event PropertyChangedEventHandler PropertyChanged;
  16:  
  17:     public ObservableCollection<Item> Items { get; set; }
  18:  
  19:     public int Total { get; protected set; }
  20:  
  21:     protected void ComputeSum()
  22:     {
  23:         this.Total = this.Items.Sum(i => i.Count);
  24:         this.NotifyPropertyChanged("Total");
  25:     }
  26:  
  27:     protected void NotifyPropertyChanged(string propertyName)
  28:     {
  29:         var eventHandler = this.PropertyChanged;
  30:  
  31:         if (eventHandler != null)
  32:         {
  33:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  34:         }
  35:     }
  36: }

A l’exécution, nous avons bien notre liste d’items qui s’affiche, ainsi que le total. Maintenant, comment faire pour qu’il se mette à jour ?

C’est là que la frustration commence. En effet, ObservableCollection permet d’être notifié lorsqu’un élément est ajouté ou supprimé, mais pas lorsqu’une des propriétés des éléments est modifiée, même si ceux-ci implémentent INotifyPropertChanged. Damned, comment ont-ils pu oublier un besoin aussi élémentaire ?

Du coup, comment faire ? Soit créer une nouvelle collection héritée d’ObservableCollection pour combler ce manque, soit faire toute la tambouille d’abonnement aux évènements “PropertyChanged” directement dans le ViewModel. Dans ce cas-ci, par souci de simplicité, optons pour la seconde solution :

   1: public class ViewModel : INotifyPropertyChanged
   2: {
   3:     public ViewModel()
   4:     {
   5:         this.Items = new ObservableCollection<Item>();
   6:  
   7:         this.Items.CollectionChanged += this.Items_CollectionChanged;
   8:  
   9:         this.Items.Add(new Item { Count = 1 });
  10:         this.Items.Add(new Item { Count = 2 });
  11:         this.Items.Add(new Item { Count = 3 });
  12:         this.Items.Add(new Item { Count = 4 });
  13:  
  14:         this.ComputeSum();
  15:     }
  16:  
  17:     public event PropertyChangedEventHandler PropertyChanged;
  18:  
  19:     public ObservableCollection<Item> Items { get; set; }
  20:  
  21:     public int Total { get; protected set; }
  22:  
  23:     protected void ComputeSum()
  24:     {
  25:         this.Total = this.Items.Sum(i => i.Count);
  26:         this.NotifyPropertyChanged("Total");
  27:     }
  28:  
  29:     protected void NotifyPropertyChanged(string propertyName)
  30:     {
  31:         var eventHandler = this.PropertyChanged;
  32:  
  33:         if (eventHandler != null)
  34:         {
  35:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  36:         }
  37:     }
  38:  
  39:     private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  40:     {
  41:         if (e.OldItems != null)
  42:         {
  43:             foreach (INotifyPropertyChanged oldItem in e.OldItems)
  44:             {
  45:                 oldItem.PropertyChanged -= this.ItemPropertyChanged;
  46:             }
  47:         }
  48:  
  49:         if (e.NewItems != null)
  50:         {
  51:             foreach (INotifyPropertyChanged newItem in e.NewItems)
  52:             {
  53:                 newItem.PropertyChanged += this.ItemPropertyChanged;
  54:             }
  55:         }
  56:  
  57:         this.ComputeSum();
  58:     }    
  59:  
  60:     private void ItemPropertyChanged(object sender, PropertyChangedEventArgs e)
  61:     {
  62:         if (e.PropertyName == "Count")
  63:         {
  64:             this.ComputeSum();
  65:         }
  66:     }
  67: }

Ca marche, mais laisse quand même sur sa faim : ce code manque clairement d’élégance.

Mais pas plus tard qu’aujourd’hui, je suis tombé, complètement par hasard, sur une discussion sur StackOverflow décrivant la classe “BindingList”. Il s’agit d’une ObservableCollection en plus puissant, puisqu’elle permet entre autres d’être notifié quand une propriété d’un des éléments est modifiée !

Adaptons donc notre code pour l’utiliser :

   1: public class ViewModel : INotifyPropertyChanged
   2: {
   3:     public ViewModel()
   4:     {
   5:         this.Items = new BindingList<Item>();
   6:             
   7:         this.Items.Add(new Item { Count = 1 });
   8:         this.Items.Add(new Item { Count = 2 });
   9:         this.Items.Add(new Item { Count = 3 });
  10:         this.Items.Add(new Item { Count = 4 });
  11:  
  12:         this.ComputeSum();
  13:  
  14:         this.Items.ListChanged += this.Items_ListChanged;
  15:     }
  16:  
  17:     public event PropertyChangedEventHandler PropertyChanged;
  18:  
  19:     public BindingList<Item> Items { get; set; }
  20:  
  21:     public int Total { get; protected set; }
  22:  
  23:     protected void ComputeSum()
  24:     {
  25:         this.Total = this.Items.Sum(i => i.Count);
  26:         this.NotifyPropertyChanged("Total");
  27:     }
  28:  
  29:     protected void NotifyPropertyChanged(string propertyName)
  30:     {
  31:         var eventHandler = this.PropertyChanged;
  32:  
  33:         if (eventHandler != null)
  34:         {
  35:             eventHandler(this, new PropertyChangedEventArgs(propertyName));
  36:         }
  37:     }
  38:  
  39:     private void Items_ListChanged(object sender, ListChangedEventArgs e)
  40:     {
  41:         if (e.ListChangedType != ListChangedType.ItemChanged
  42:             || (e.ListChangedType == ListChangedType.ItemChanged && e.PropertyDescriptor.Name == "Count"))
  43:         {
  44:             this.ComputeSum();
  45:         }
  46:     }
  47: }

Ca marche, et c’est autrement plus classe ;o)

Après vérification, cette classe existe depuis .NET 2.0, rien de neuf donc… Et pourtant !

[WP7] Upload a file to Dropbox using ReactiveOAuth.WP7

As I was helping @lancewmccarthy on its last application, I ran across the need to upload a file to Dropbox. While there is already a few libraries for WP7 handling this task, I couldn’t get any of them to work as expected: either they missed the file upload feature, or they used an older version of the API that new applications can’t use anymore. As such, I had to dive into the Dropbox’s OAuth API to write my own upload code. It was far from being painless, so I think the whole process is worth sharing.

First, what is OAuth? I won’t give much details as I discovered that recently, but basically it’s a protocol used to authenticate yourself and send orders to an http-based API. On the surface, it’s just about calling the appropriate webpage. But to prevent some kinds of attacks, OAuth forces you to add a few parameters to the uri, like a random value and a dynamically generated signature. It may be just me, but I think the official documentation isn’t precise enough on how to generate the signature, so I quickly gave up and searched for general-purpose OAuth libraries. The one I found and used is ReactiveOAuth.WP7. It was originally designed for Twitter, but it can also be used for Dropbox, with a few changes.

The documentation for Dropbox REST API can be found here. To use it, you must first register your application on the website, to get a unique code that you will use every time you call a Dropbox function. The application registration is done on this page and is almost instant. Note that your application is first registered with ‘testing’ status. It means you can only use your own Dropbox account with it. To remove this limitation, you have to apply for production status, which requires a validation from Dropbox administrators. You just have to fill a description for your app and explain why you need to use Dropbox, then you should receive a confirmation e-mail after a few hours. Anyway, on the application page you’ll find two codes: “App key” and “App secret”. We need these to use the API.

image

Now, let’s start Visual Studio and create a new WP7 project. Add the ReactiveOAuth.WP7 project to your solution. The whole project, not just the binaries, as we’ll have to make a few changes in the code.

In your main project, add a reference to the ReactiveOAuth project. Also add a reference to the Microsoft.Phone.Reactive and System.Observable assemblies.

image

The OAuth authentication and authorization process works as follows: first call the “request_token” page, giving your app key and secret (the two values that were provided on the Dropbox application page). The server answers with a token which will identify your session. Then, the user must allow your application to use his Dropbox account. For security reasons, you cannot automate this part, it requires user interaction. You’ll have to redirect the user to a specific webpage, where he can type his credentials and explicitly allow your application. If the user do theses steps correctly, then a call to the “access_token” page will return a token that you can use to call any API function you want.

For our tests, we need to upload something to Dropbox. For that purpose, we’ll create a test file when the page is loaded, and store it in the isolated storage. Therefore, we create a “CreateDummyFile” function and call it in the “OnNavigatedTo” event:

   1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     this.CreateDummyFile();
   6: }
   7:  
   8: private void CreateDummyFile()
   9: {
  10:     using (var isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
  11:     {
  12:         if (isolatedStorage.FileExists("test.txt"))
  13:         {
  14:             isolatedStorage.DeleteFile("test.txt");
  15:         }
  16:  
  17:         using (var stream = isolatedStorage.CreateFile("test.txt"))
  18:         {
  19:             var data = Encoding.UTF8.GetBytes("Hello world! - " + DateTime.Now.ToLongTimeString());
  20:  
  21:             stream.Write(data, 0, data.Length);
  22:         }
  23:     }
  24: }

Then we use the “OAuthAutorizer” class to request a token. “Key” and “Secret” are constants added to the page, containing respectively the app key and app secret.

   1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     var authorizer = new OAuthAuthorizer(Key, Secret);
   6:  
   7:     authorizer.GetRequestToken("https://api.dropbox.com/1/oauth/request_token");
   8: }

Once we have the token, we must proceed with the authorization. We need to display a webpage to the user, so we add a few UI elements in the XAML:

   1: <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
   2:     <StackPanel>
   3:         <phone:WebBrowser x:Name="WebBrowser" Height="700"  IsScriptEnabled="True"
   4:                       Visibility="Collapsed" 
   5:                           Navigating="WebBrowser_Navigating" />
   6:         <StackPanel x:Name="UploadingPanel">
   7:             <TextBlock Text="Uploading file..." Height="50" TextAlignment="Center" />
   8:             <ProgressBar x:Name="ProgressBar" Height="20" IsIndeterminate="True" Visibility="Visible"/>
   9:         </StackPanel>
  10:     </StackPanel>
  11: </Grid>

In the code, we use the callback of the “GetRequestToken” method to construct the right uri and navigate to this page. The “authorize” function accepts a “oauth_callback” parameter. This parameter tells Dropbox to redirect to a specific page when the authorization process is done. This way we can detect the redirection to know when the user has finished. So our “OnNavigatedTo” event now looks like:

   1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     this.CreateDummyFile();
   6:  
   7:     var authorizer = new OAuthAuthorizer(Key, Secret);
   8:  
   9:     authorizer.GetRequestToken("https://api.dropbox.com/1/oauth/request_token")
  10:         .ObserveOnDispatcher()
  11:         .Subscribe(token =>
  12:         {
  13:             this.RequestToken = token.Token;
  14:             var url = authorizer.BuildAuthorizeUrl("https://www.dropbox.com/1/oauth/authorize", token.Token);
  15:  
  16:             url += "&oauth_callback=http://dummywebsite/dummy";
  17:  
  18:             this.WebBrowser.Navigate(new Uri(url));
  19:         });
  20: }

Note the call to “ObserveOnDispatcher”. It tells the Reactive extensions to call the callback on the dispatcher thread. It’s required as we need to interact with the UI, to tell the WebBrowser control which page it should display. Without “ObserveOnDispatcher”, we would have to call “Dispatcher.BeginInvoke” to avoid a cross-thread exception.

In the XAML code I’ve provided, the WebBrowser is hidden by default. Now we need to set it to visible, and detect the end of the authorization process to hide it back and request the access token. To do that, we’re going to use the “Navigating” event of the WebBrowser control:

   1: private void WebBrowser_Navigating(object sender, NavigatingEventArgs e)
   2: {
   3:     if (e.Uri.AbsolutePath == "/dummy")
   4:     {
   5:         // Authorization done, cancel the navigation, hide the browser control, and proceed.
   6:         e.Cancel = true;
   7:         this.WebBrowser.Visibility = Visibility.Collapsed;
   8:         this.UploadingPanel.Visibility = Visibility.Visible;
   9:         this.GetAccessToken();
  10:     }
  11:     else
  12:     {
  13:         this.WebBrowser.Visibility = Visibility.Visible;
  14:         this.UploadingPanel.Visibility = Visibility.Collapsed;
  15:     }
  16: }

The “GetAccessToken” method simply request the access token, store it in a property, and call the file uploading code:

   1: private void GetAccessToken()
   2: {
   3:     var authorizer = new OAuthAuthorizer(Key, Secret);
   4:     authorizer.GetAccessToken("https://api.dropbox.com/1/oauth/access_token", this.RequestToken, this.RequestToken.Secret)
   5:         .Subscribe(token =>
   6:         {
   7:             this.AccessToken = token.Token;
   8:  
   9:             this.SendFile();
  10:         });
  11: }

Now comes the tricky part. Out of the box, the ReactiveOAuth library is limited to HTTP GET and POST requests. To upload the file, we’ll need to use HTTP PUT. Therefore, there’s two modifications to do in the library.

First, open the “MethodType.cs” file, and add the PUT method:

   1: using System;
   2:  
   3: namespace Codeplex.OAuth
   4: {
   5:     /// <summary>WebRequest HttpMethodType</summary>
   6:     public enum MethodType
   7:     {
   8:         Get, Post, Put
   9:     }
  10:  
  11:     public static class MethodTypeExtensions
  12:     {
  13:         /// <summary>convert to UPPERCASE string</summary>
  14:         public static string ToUpperString(this MethodType methodType)
  15:         {
  16:             switch (methodType)
  17:             {
  18:                 case MethodType.Get:
  19:                     return "GET";
  20:                 case MethodType.Post:
  21:                     return "POST";
  22:                 case MethodType.Put:
  23:                     return "PUT";
  24:                 default:
  25:                     throw new ArgumentException();
  26:             }
  27:         }
  28:     }
  29: }

Then open the “OAuthClient.cs” file and find the “CreateWebRequest” method. Set its visibility to “Public”, and change the first line from:

   1: var requestUrl = (MethodType == OAuth.MethodType.Get) ? Url + "?" + Parameters.ToQueryParameter() : Url;

to:

   1: var requestUrl = (MethodType == OAuth.MethodType.Get || MethodType == OAuth.MethodType.Put) ? Url + "?" + Parameters.ToQueryParameter() : Url;

What are we doing here? The “files_put” API of Dropbox requires parameters to be provided as for a GET request, and the file data to be provided as POST data. ReactiveOAuth.WP7 doesn’t seem to be able to handle this hybrid query, so we’re bypassing it and exposing the internal WebRequest object to do the upload ourselves. Basically, we only use ReactiveOAuth to create the request and fill all the parameters required by the OAuth protocol, like the signature, then we take car of the uploading.

Now we can write our “SendFile” method, with the necessary upload code:

   1: private void SendFile()
   2: {
   3:     var client = new OAuthClient(Key, Secret, this.AccessToken);
   4:  
   5:     client.Url = "https://api-content.dropbox.com/1/files_put/sandbox/test.txt";
   6:  
   7:     client.Parameters.Add("overwrite", "true");
   8:  
   9:     client.MethodType = MethodType.Put;
  10:  
  11:     var webRequest = client.CreateWebRequest();
  12:  
  13:     webRequest.BeginGetRequestStream(this.StartUpload, webRequest);
  14: }
  15:  
  16: private void StartUpload(IAsyncResult asyncResult)
  17: {
  18:     var request = (HttpWebRequest)asyncResult.AsyncState;
  19:     var postStream = request.EndGetRequestStream(asyncResult);
  20:  
  21:     using (var isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
  22:     {
  23:         using (var stream = isolatedStorage.OpenFile("test.txt", FileMode.Open))
  24:         {
  25:             stream.CopyTo(postStream);
  26:  
  27:             postStream.Close();
  28:         }
  29:     }
  30:  
  31:     request.BeginGetResponse(this.EndUpload, request);
  32: }
  33:  
  34: private void EndUpload(IAsyncResult asyncResult)
  35: {
  36:     var request = (HttpWebRequest)asyncResult.AsyncState;
  37:  
  38:     try
  39:     {
  40:         var response = (HttpWebResponse)request.EndGetResponse(asyncResult);
  41:  
  42:         response.Dispose();
  43:  
  44:         this.Dispatcher.BeginInvoke(() =>
  45:         {
  46:             this.ProgressBar.Visibility = Visibility.Collapsed;
  47:             MessageBox.Show("Your file has been sucessfully uploaded to Dropbox!");
  48:         });
  49:     }
  50:     catch (Exception ex)
  51:     {
  52:         this.Dispatcher.BeginInvoke(() => MessageBox.Show("An error occured: " + ex.Message));
  53:     }
  54: }

The “StartUpload” method simply opens the file from the isolated storage, then copies the contents inside the web request. The “EndUpload” callback is called when the upload is finished. There, we display a confirmation message.

And we’re done! After executing this program, the Dropbox webpage should appear, asking for your credentials. Then, when you’re done allowing the app, the upload will begin and a message box will confirm you that the file has been uploaded.

image

Note: my code modification inside the ReactiveOAuth.WP7 library is really ugly, as I don’t even bother to use Reactive extensions. That’s because I didn’t really need Reactive extensions in first place, and any ‘general purpose’ OAuth library would do the trick. Still, if you know an elegant way to use Dropbox’s ‘files_put’ function with ReactiveOAuth without modifying the library, please share it in the comments!

[WP7] ScheduledActionService.Replace: Bug or documentation error?

Let’s consider this simple code:

   1: var beginDate = new DateTime(2011, 12, 01);
   2: var endDate = new DateTime(2011, 12, 02);
   3:  
   4: var reminder = new Reminder("Test");
   5: reminder.BeginTime = beginDate;
   6: reminder.ExpirationTime = endDate;
   7:  
   8: ScheduledActionService.Add(reminder);

With this, a reminder is created and scheduled. Each reminder is associated with an unique name, provided in the constructor. Here, we use “Test” as name.

Now, what if I want to update my Reminder? If I create a new Reminder with the same name, and call ScheduledActionService.Add, an exception is thrown: ‘Test’ already exists. It makes sense, since the name is supposed to be an unique identifier. So to update the Reminder, you have first to call the ScheduledActionService.Remove method, then you can call the ‘Add’ method:

   1:  
   2:             var beginDate = new DateTime(2011, 12, 01);
   3:             var endDate = new DateTime(2011, 12, 02);
   4:  
   5:             var reminder = new Reminder("Test");
   6:             reminder.BeginTime = beginDate;
   7:             reminder.ExpirationTime = endDate;
   8:             
   9:             if (ScheduledActionService.Find("Test") != null)
  10:             {
  11:                 ScheduledActionService.Remove("Test");
  12:             }
  13:  
  14:             ScheduledActionService.Add(reminder);

No problem here, it works perfectly. Is there a better way to do it? A look at MSDN documentation shows the ScheduledActionService.Replace method:

“Replaces an existing ScheduledAction with the one provided. The Name property uniquely identifies ScheduledAction objects and is used to determine which existing object, if any, is replaced.”

It sounds exactly like what we need! Let’s try to use it:

   1: var beginDate = new DateTime(2011, 12, 01);
   2: var endDate = new DateTime(2011, 12, 02);
   3:  
   4: var reminder = new Reminder("Test");
   5: reminder.BeginTime = beginDate;
   6: reminder.ExpirationTime = endDate;
   7:  
   8:  
   9: ScheduledActionService.Replace(reminder);

Compile, run, and… An exception is thrown! “BNS Error: The item doesn't exist”

Why this exception? My Reminder was created previously, with the same name… So it should exist, right?

Let’s investigate. What happens if we retrieve the old Reminder, update it, then call replace with this very same Reminder instance?

   1: var beginDate = new DateTime(2011, 12, 01);
   2: var endDate = new DateTime(2011, 12, 02);
   3:  
   4: var reminder = ScheduledActionService.Find("Test");
   5: reminder.BeginTime = beginDate.AddDays(1);
   6: reminder.ExpirationTime = endDate.AddDays(1);
   7:  
   8: ScheduledActionService.Replace(reminder);

It works! So basically, the ScheduledActionService.Replace method can only be used if we provide the same Reminder instance that was added previously. This clearly contradicts the MSDN documentation, which states that the method replaces the previous Reminder only based on the Name property.

By the way, if it’s not the name, what is used to recognize the Reminder? Let’s dig in using Reflector.

First, the Replace method:

   1: public static void Replace(ScheduledAction action)
   2: {
   3:     lock (m_syncRoot)
   4:     {
   5:         if (!(action is ScheduledNotification))
   6:         {
   7:             throw new NotSupportedException(string.Format("{0} is not supported", "Replacing a ScheduledTask"));
   8:         }
   9:         if (!ActionDictionary.ContainsKey(action.Name))
  10:         {
  11:             throw new InvalidOperationException(string.Format("'{0}' doesn't exist", action.Name));
  12:         }
  13:         SystemNotificationInterop.UpdateNotification(action);
  14:         ActionDictionary[action.Name] = action.CreateCopy();
  15:     }
  16: }
  17:  

It first checks if the Reminder is in the internal ‘ActionDictionary’ dictionary. Otherwise, it throws an exception. The exception message is different, so it’s not where it crashes. To make sure, let’s check the callstack when the exception is thrown:

Microsoft.Phone.dll!Microsoft.Phone.Scheduler.SystemNotificationInterop.CheckHr
Microsoft.Phone.dll!Microsoft.Phone.Scheduler.SystemNotificationInterop.UpdateNotification
Microsoft.Phone.dll!Microsoft.Phone.Scheduler.ScheduledActionService.Replace

The CheckHr is just a method which checks the result code and throws the appropriate exception:

   1: private static void CheckHr(int hr)
   2: {
   3:     switch (hr)
   4:     {
   5:         case -2147467263:
   6:             throw new NotSupportedException();
   7:  
   8:         case -2147024890:
   9:         case -2147024809:
  10:             throw new ArgumentException("E_INVALIDARG");
  11:  
  12:         case -2147024882:
  13:             throw new OutOfMemoryException();
  14:  
  15:         case 0:
  16:         case 1:
  17:             return;
  18:  
  19:         case -2130444030:
  20:             throw new SchedulerServiceException(hr, GetMessageStringFromHResult(hr));
  21:  
  22:         case -2147023174:
  23:         case -2147023173:
  24:             throw new SchedulerServiceException(hr, "System is not ready");
  25:     }
  26:     string message = null;
  27:     try
  28:     {
  29:         message = GetMessageStringFromHResult(hr);
  30:     }
  31:     catch (Exception)
  32:     {
  33:         message = null;
  34:     }
  35:     if (message == null)
  36:     {
  37:         throw new SchedulerServiceException(hr, hr.ToString("X"));
  38:     }
  39:     throw new InvalidOperationException(message);
  40: }
  41:  
  42:  

No intelligence here, it leaves us only the UpdateNotification method.

   1: internal static void UpdateNotification(ScheduledAction action)
   2: {
   3:     BNS_NOTIFICATION bnsNotification = CreateNativeNotificationFromManaged(action);
   4:     CheckHr(BNSIUpdateNotification(ref bnsNotification));
   5:     action.UpdateStatusFrom(CreateManagedNotificationFromNative(bnsNotification));
   6: }

Unfortunately, BNSIUpdateNotification is a native method, so we can’t decompile it using Reflector. The CreateNativeNotificationFromManaged, as it names implied, create a native notification object, which will be sent to the BNSIUpdateNotification method. I won’t copy the code because it’s too long, and it’s a pretty straightforward copy of properties from one object to another.

So… Is that a dead end? Maybe not, let’s see the other methods of the ScheduledActionService class. Especially the ‘Remove’ method:

   1: public static void Remove(string name)
   2: {
   3:     lock (m_syncRoot)
   4:     {
   5:         if (!ActionDictionary.ContainsKey(name))
   6:         {
   7:             throw new InvalidOperationException(string.Format("'{0}' doesn't exist", name));
   8:         }
   9:         SystemNotificationInterop.DeleteNotification(ActionDictionary{name}.ID);
  10:         ActionDictionary.Remove(name);
  11:     }
  12: } 

(note: due to a bug in the blog engine, I had to replace the [] around Name by {} in line 9. Apologies).

Now that’s interesting. The method retrieves the Reminder in the internal dictionary by using its name, then it sends its id to the ‘DeleteNotification’ method, which will in turn send it to the native BNSIDeleteNotification method. Could it be that’s the Id property is used internally to identify a Reminder, and not the Name property?

When creating a Reminder, we don’t specify the value of the Id property. First, let’s check how it’s provided. We have to go up the inheritance tree until the ‘ScheduledAction’ class to find it:

   1: internal ScheduledAction(string name, ScheduledActionType type)
   2: {
   3:     this.m_id = Guid.NewGuid();
   4:     this.m_content = string.Empty;
   5:     this.Name = name;
   6:     this.m_type = type;
   7:     this.m_status = ScheduledActionStatus.Completed;
   8:     this.m_endTime = DateTime.MaxValue;
   9: }
  10:  

It’s simply a random generated value. So if we create two Reminder, with the same name, they’ll have different id. Have we found our issue? Let’s try to figure by manually changing the Id property.

The Id property isn’t exposed anywhere, but fortunately we can modify it using Visual Studio’s debugger.

First, create a reminder named “Test”, add it to the ScheduledActionService, then execute again our crashing code:

   1: var beginDate = new DateTime(2011, 12, 01);
   2: var endDate = new DateTime(2011, 12, 02);
   3:  
   4: var reminder = new Reminder("Test");
   5: reminder.BeginTime = beginDate;
   6: reminder.ExpirationTime = endDate;
   7:  
   8: ScheduledActionService.Replace(reminder);

This time, set a breakpoint on the '”ScheduledActionService.Replace(reminder);” line. When Visual Studio steps into it, open the QuickWatch window (Shift + F9). Then, call the “ScheduledActionService.Find” method to retrieve the old Reminder, and get the value of its m_id property:

image

Now, replace the m_id property of the new Reminder with this one:

image

Now exit the QuickWatch window and press F10 to continue execution. And… It works! So the problem really is the Id property. The value isn’t based on the Name property, and is randomly generated when a new Reminder is created. So if we create a new Reminder instance, even if we use the same name, we’ll have a different Id. Then, when we call the Replace method, the Id property is used rather than the Name. So, of course, the old Reminder instance cannot be found, and an exception is thrown.

Now let’s just hope for a fix in a WP7 next release, or at the very least an update of the MSDN documentation.

[WP7] Pensez à gérer la désactivation des background agent !

Une erreur que j’ai pu voir sur quelques applications publiées sur le marketplace. Il est possible pour l’utilisateur de désactiver manuellement un background agent créé par une application, en allant dans les paramètres du téléphone, –> applications –> tâches en arrière-plan. Si les background agent ont été désactivés pour votre application, alors une exception sera levée lorsque vous tenterez d’en créer :

System.InvalidOperationException: "BNS Error: The action is disabled"

Cela peut être particulièrement problématique si votre application créé un background agent au chargement, auquel cas le crash sera direct. Pour gérer ce cas, un simple bloc try/catch autour de l’appel à “ScheduledActionService.Add” fera l’affaire, avec éventuellement un message explicatif à destination de l’utilisateur.

Je trouve dommage que ce point ne soit pas vérifié dans le processus de certification pour le marketplace, vu comme l’erreur est simple à faire.

[WP7] Windows Phone SDK 7.1 disponible

Alors que le déploiement de Mango auprès du grand public a commencé hier, Microsoft vient de publier la version finale du SDK WP7.5. Pour le télécharger, il suffit de suivre ce lien : http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27570

[WP7] Utiliser le flash comme lampe torche

Windows Phone 7 ne proposait jusque-là pas d’API permettant de contrôler l’allumage du flash, aussi toutes les applications de prétendues lampe torche se contentaient d’afficher une image blanche sur l’écran. Mango offre un plus grand contrôle sur la camera, mais toujours pas de moyen direct de piloter le flash. Cependant, une solution de contournement existe.

Cette solution de contournement c’est la fonction d’autofocus. En effet, l’application peut paramétrer la caméra pour que le flash soit toujours allumé lors des prises de photos. Dans ce cas-là, la caméra reste également allumée pendant le focus. Du coup, la technique est simple : il suffit de demander au système d’effectuer l’autofocus en boucle.

Tout d’abord, nous initialisons la caméra lors du chargement de la page, dans la méthode “OnNavigatedTo”. L’initialisation de la caméra est asynchrone, nous nous abonnons donc à l’évènement “Initialized” :

   1: protected PhotoCamera Camera { get; set; }
   2:  
   3: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   4: {
   5:     base.OnNavigatedTo(e);
   6:  
   7:     this.Camera = new PhotoCamera(CameraType.Primary);
   8:  
   9:     this.Camera.Initialized += this.Camera_Initialized;
  10: }

Notez que le constructeur de l’objet “PhotoCamera” prend un paramètre permettant de choisir entre la caméra frontale ou la caméra “normale”.

Dans la fonction “Camera_Initialized”, nous vérifions que l’initialisation s’est correctement déroulée, et nous indiquons à la caméra de toujours utiliser le flash, à l’aide de la propriété “FlashMode”. Finalement, nous nous abonnons à l’évènement signalant la fin de l’autofocus, et nous demandons à la caméra de lancer le focus une première fois.

   1: private void Camera_Initialized(object sender, CameraOperationCompletedEventArgs e)
   2: {
   3:     if (!e.Succeeded)
   4:     {
   5:         this.Dispatcher.BeginInvoke(() => MessageBox.Show("Initialization error: " + e.Exception));
   6:         return;
   7:     }
   8:  
   9:     this.Camera.FlashMode = FlashMode.On;
  10:  
  11:     this.Camera.AutoFocusCompleted += this.Camera_AutoFocusCompleted;
  12:  
  13:     this.Camera.Focus();
  14: }

Dans la méthode “Camera_AutoFocusCompleted”, nous nous contentons de relancer le focus pour que celui-ci se fasse en boucle :

   1: private void Camera_AutoFocusCompleted(object sender, CameraOperationCompletedEventArgs e)
   2: {
   3:     this.Camera.Focus();
   4: }

Après test, ça fonctionne ! Le flash reste allumé, avec un léger clignotement de temps à autres signalant l’intervalle entre la fin du focus et le lancement d’un nouveau.

Un petit problème demeure : en quittant l’application, une exception est levée indiquant que la méthode Focus ne peut pas être appelée sur l’objet “PhotoCamera” disposé. Pour y remédier, nous nous désabonnons de l’évènement “AutoFocusCompleted” lorsque l’utilisateur navigue hors de la page :

   1: protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     this.Camera.AutoFocusCompleted -= this.Camera_AutoFocusCompleted;
   4:  
   5:     base.OnNavigatedFrom(e);
   6: }

Et nous voilà avec une application de lampe torche fonctionnelle !

[WP7] Windows Phone Power Tools

Hier, une boite à outil fort pratique pour Windows Phone a été publiée : Windows Phone Power Tools. Cet utilitaire fourni un vaste éventail de fonctionnalités :

- Affichage d’informations au sujet de votre téléphone, telles que la quantité mémoire disponible

- Installation d’applications, ce que le SDK permettait déjà de faire, mais surtout mise à jour

- Parcourir la liste des applications installées, et les lancer/tuer/désinstaller

- Afficher le contenu de l’isolated storage de ces applications, et permet d’en récupérer le contenu ou de déposer de nouveaux fichiers

image

Beaucoup de ces fonctionnalités sont surprenantes, et gageons que la scène homebrew avancera énormément par l’analyse de cet outil.

Pour le télécharger, c’est sur codeplex que ça se passe : http://wptools.codeplex.com/

Note : La connexion au téléphone échouera si Zune n’est pas lancé ou si l’écran de verrouillage est actif.

[WP7] Display a settings screen from a Mango application

This is one pretty sweet feature of Mango, yet it is still widely unknown. You can now display a settings page from an application. How? Simply by using the ConnectionSettingsTask class.

It’s very easy to use: just set the ‘ConnectionSettingsType’ property, call the ‘Show’ method, and you’re done!

   1: var task = new Microsoft.Phone.Tasks.ConnectionSettingsTask();
   2:  
   3: task.ConnectionSettingsType = Microsoft.Phone.Tasks.ConnectionSettingsType.WiFi;
   4:  
   5: task.Show();

The following values are supported for ConnectionSettingsType:

- Wifi

- Bluetooth

- Cellular (select cellular network + activate/deactivate data connexions)

- AirplaneMode

[WP7] Afficher un écran de paramètres depuis une application

Une nouveauté bien sympathique du SDK Mango qui est passée relativement inaperçue. Il est désormais possible d’afficher certains écrans de réglalges depuis une application, à l’aide de la classe ConnectionSettingsTask.

Son utilisation est très simple: instancier la classe, indiquer quel écran de réglages doit être affiché, et appeler la méthode ‘Show()’. Par exemple, pour afficher l’écran de configuration du Wifi:

   1: var task = new Microsoft.Phone.Tasks.ConnectionSettingsTask();
   2:  
   3: task.ConnectionSettingsType = Microsoft.Phone.Tasks.ConnectionSettingsType.WiFi;
   4:  
   5: task.Show();

Les valeurs supportées par ‘ConnectionSettingsType’ sont :

- Wifi

- Bluetooth

- Cellular (choix du réseau de téléphonie + activation/désactivation des connexions data)

- AirplaneMode (actication/désactivation du mode avion)

[WP7] NavigationService.RemoveBackEntry

Parmi toutes les nouveautés, Mango apporte une nouvelle fonction bien sympathique pour palier à certains problèmes de navigation.

Prenons un exemple simple : une application avec deux pages. Lorsque l’utilisateur clique sur un bouton de la première page, il est amené sur la seconde pour saisir des informations. En validant les informations, il est ramené à la première page.

La manière la plus “immédiate” d’implémenter ce scénario est de placer sur le bouton de la première page :

   1: private void button1_Click(object sender, RoutedEventArgs e)
   2: {
   3:     this.NavigationService.Navigate(new Uri("/Page2.xaml", UriKind.Relative));
   4: }

Et sur le bouton de la seconde page :

   1: private void button1_Click(object sender, RoutedEventArgs e)
   2: {
   3:     this.NavigationService.Navigate(new Uri("/Page1.xaml", UriKind.Relative));
   4: }

En testant, cela semble marcher : on navigue bien de la première page à la seconde, puis de la seconde à la première. Problème cependant, après cet enchainement, l’historique de navigation est le suivant :

Page 1 –> Page 2 –> Page 1

Conséquence : si à partir de là on appuie sur le bouton back du téléphone, on est ramené à la seconde page, puis à nouveau la première, pour enfin quitter l’application ! Peu commode pour l’utilisateur.

Dans ce cas-ci, la solution est simple, il suffit de remplacer l’appel à “Navigate” dans la seconde page par “GoBack” :

   1: private void button1_Click(object sender, RoutedEventArgs e)
   2: {
   3:     this.NavigationService.GoBack();
   4: }

Maintenant, en appuyant sur le bouton de la seconde page, on est ramené en arrière dans l’historique de navigation. On atterit donc bien sur la première page, et un nouvel appui sur le bouton back fermera l’application.

Tout cela est bien joli, mais que se passe t’il si on a trois pages ? Le bouton de la première page renvoie à la seconde page pour que l’utilisateur saisisse des informations, le bouton de la seconde page renvoie sur une troisième pour saisir encore d’autres informations, et enfin le bouton de la troisième valide et ramène à la première page.

En utilisant la fonction Navigate, même problème, l’historique de navigation sera :

Page 1 –> Page 2 –> Page 3 –> Page 1

Et cette fois-ci, nous ne pouvons pas utiliser la fonction GoBack depuis la troisième page, car cela nous ramenerait sur la seconde page alors que l’on veut retourner à la première ! Jusqu’à présent, un des seuls moyens pour se sortir de cette situation était de détecter dans la seconde page le cas où l’on revient de la troisième page, et d’appeler à nouveau GoBack pour renvoyer l’utilisateur sur la première page. Cela fonctionne, mais c’est peu élégant.

C’est ici que Mango entre en scène, avec la fonction NavigationService.RemoveBackEntry. Celle-ci va supprimer la dernière entrée de l’historique de navigation. A partir de là, il est possible d’appeler Navigate depuis la troisième page pour revenir sur la première page, et dans la première page de nettoyer l’historique de navigation dans l’évènement OnNavigatedTo :

   1: protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
   2: {
   3:     base.OnNavigatedTo(e);
   4:  
   5:     while (this.NavigationService.BackStack.Any())
   6:     {
   7:         this.NavigationService.RemoveBackEntry();
   8:     }
   9: }
  10:  

Du coup, en revenant sur la première page, l’historique de navigation est bien vidé. Un appui sur le bouton back du téléphone quittera alors l’application, comme l’utilisateur s’y attend.

Problème réglé !

[WP7] Advice on Isolated Storage

No code in this blog post, I’d just like to give an advice to fellow Windows Phone 7 developers. Many applications use the isolated storage, be it to save data or to handle tombstoning scenarios. In either case, make sure that you surround your code with as many try/catch blocks as needed. Too many times I’ve broken an app by pressing the home or back button at an unexpected moment (for instance, during a loading or save screen). Due to the unexpected user behavior, the writing in the isolated storage isn’t performed properly, and the data gets corrupted. When trying to start again the application, since the data isn’t exactly as the developer expected it to be, an exception is thrown and the application crashes immediately. In those cases, the only possible step for the user is to uninstall the app and download it once more. Unless your app is *really* useful, you can be sure that the user will be annoyed, and will stop using it and/or drop a bad review in the marketplace.

From there, my recommendations are pretty obvious:

- When reading from the isolated storage, never forget that the data integrity isn’t guaranteed

- If an exception is thrown during loading, it’s often better to handle it silently than to just crash. Sure, the user will lose his data, but he will lose it anyway if the application always crashes at startup, leading to uninstallation.

[WP7] Recommandation générale sur l’IsolatedStorage

Pour une fois on va s’écarter (à peine) du code pour une recommandation d’ordre générale adressée aux développeurs Windows Phone 7. Enormément d’applications utilisent l’isolated storage, que ce soit pour une sauvegarde “ordinaire”, ou pour gérer la persistance lors du tombstoning. Dans un cas comme dans l’autre, veillez à protéger votre code comme il se doit avec des blocs try/catch non bloquants ! Trop souvent j’ai pu constater qu’un arrêt au milieu d’un passage manifestement non prévu (typiquement, en appuyant sur le bouton home du téléphone lors d’un chargement) cassait complétement le fonctionnement de l’application. L’écriture dans l’isolated storage se fait mal, et la lecture du contenu de ce dernier au chargement suivant de l’application déclenche une exception non gérée par le développeur. L’application quitte alors automatiquement lorsqu’on la lance, et le seul remède est de la désinstaller puis de la réinstaller. Autant dire qu’il faut vraiment que l’application soit utile pour se donner cette peine !

En bref, je souhaiterais faire ces deux recommandations d’ordre général :

- Lorsque vous lisez depuis l’isolated storage, n’oubliez pas que l’intégrité des données que vous y lisez n’est pas garantie
- En cas d’erreur au chargement de ces données, il vaut souvent mieux que vous gériez de manière silencieuse l’exception levée et que l’utilisateur perde les données, plutôt que l’application devienne inutilisable et plante systématiquement au démarrage (ce qui entrainera de toutes manières une désinstallation, et donc une perte des données).

A bon entendeur !

[WP7] The NeutralResourceLanguage attribute is missing on the entry assembly

Si vous tentez de soumettre une application WP7 (nouvelle ou simple mise à jour) depuis la dernière version de l’AppHub, il se peut que vous receviez le message d’erreur “The [NeutralResourceLanguage] attribute is missing on the entry assembly”.

image

Pour corriger cela, rien de bien compliqué. Ouvrez la solution dans Visual Studio, cliquez droit sur le projet principal, et allez dans les propriétés. Dans la page qui s’affiche, vérifiez que vous vous trouvez dans l’onglet “Application”, et cliquez sur le bouton “Assembly information”.

image

Dedans, vous constaterez que l’attribut “Neutral Language” est réglé sur “(none)”. Indiquez à la place la langue par défaut de votre application à l’aide de la liste déroulante. Enregistrez, compilez, et vous devriez pouvoir reprendre le processus de soumission sans encombre.

image

[WP7] Mise à jour de l’AppHub

Une mise à jour significative de l’AppHub a été déployée par Microsoft. L’ensemble a gagné en visibilité, notamment le processus de certification et le suivi du nombre de téléchargements. Vous pouvez aussi désormais publier une application de manière privée (elle n’apparaitra pas sur le marketplace) et distribuer le lien de téléchargement à qui vous voulez. Mais la nouveauté qui a le plus attiré mon attention est le nombre de crashs de votre application !

 

image

Si vous cliquez sur le nombre de crash, vous pouvez voir l’évolution en fonction du temps :

image

Et fin du fin, en cliquant sur le lien “stack traces” en haut à gauche, vous pouvez télécharger un fichier Excel contenant, pour chacun des crashs, la version de WP7 utilisée par l’utilisateur et la pile d’appel complète ! Enfin un moyen de réagir efficacement aux commentaires se plaignant de plantages de votre applications, plantages que vous ne parvenez bien entendu jamais à reproduire sur votre propre téléphone.

[WP7] Alternative tiles avec arrière-plan généré dynamiquement (MAJ)

Petite mise à jour concernant un article précédent, qui expliquait comment utiliser une image stockée dans l’isolated storage comme arrière-plan pour des tiles. Cela se faisait à l’aide d’une url de la forme “isostore:/chemin_vers_l_image”.
La dernière version du SDK de Mango a apporté un changement bloquant : la syntaxe de l’url reste la même, mais il faut désormais que l’image se trouve dans le répertoire “Shared/ShellContent” de l’isolated storage pour pouvoir être utilisée. Le lien sera donc de la forme :

isostore:/Shared/ShellContent/nom_de_l_image

C’est un changement mineur, mais qui peut amener quelques surprises lorsque l’on n’est pas au courant.

[WP7] Lancer un BackgroundAgent à la demande

Un changement bien sympathique apporté par la dernière beta de Mango. Auparavant, les BackgroundAgent se lançaient automatiquement une première fois après création, puis il fallait attendre environ 30 minutes pour qu’ils se lancent à nouveau. Ceci rendait le debuggage pénible, tout particulièrement si on testait un comportement lié à l’heure courante.

Afin de simplifier les choses, la fonction “ScheduledActionService.LaunchForTest” a été ajoutée. Elle permet de passer outre la planification interne, et d’indiquer dans combien de temps la tâche doit s’exécuter. Elle prend deux paramètres :

- name : nom du BackgroundAgent
- delay : délai au bout duquel la tâche doit s’exécuter

Simple et efficace !

Plus de Messages Page suivante »


Les 10 derniers blogs postés

- [TechDays2012] Oui j’y serai! par Blog de Jérémy Jeanson le il y a 11 heures et 31 minutes

- TFS Integration Tools – Suivi des synchronisations avec Reporting Services par Vivien Fabing le 02-05-2012, 17:46

- CSS Content State Selectors (Personnal Draft) par Le blog de FremyCompany le 02-04-2012, 15:38

- MBA : Pourquoi faire et comment le choisir ? par Blog Technique de Romelard Fabrice le 02-03-2012, 14:22

- Y'a des erreurs qui peuvent rendre le développeur violent par Aleks's Blog le 02-02-2012, 16:33

- [Hyper-V 3] Présentation des commandlets PowerShell par Blog de SPBrouillet (Pierrick BROUILLET) le 01-31-2012, 16:01

- IIS7 – Compression GZIP par Atteint de JavaScriptite Aiguë [Cyril Durand] le 01-31-2012, 15:52

- SharePoint 15 Technical Preview Managed Object Model Software Development Kit par Matthew le 01-31-2012, 12:34

- Office 15 Technical Preview - Open specification Update par Matthew le 01-31-2012, 10:14

- TFS Integration Tools – Installation par Vivien Fabing le 01-31-2012, 00:06