Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

[wpdev] Memory leak with BitmapImage

There’s a memory leak that has been bothering me for a while in my Imageboard Browser app. The scenario is simple: a slideshow feature, where a new picture is loaded from an url every few seconds. After a while, the app crashes with an OutOfMemory exception. I’ve never been able to find the source of the leak, so I settled for a dirty workaround consisting in loading a fake picture in the BitmapImage instance to force it to release the memory:

   1: private void DisposeImage(BitmapImage image)
   2: {
   3:     if (image != null)
   4:     {
   5:         try
   6:         {
   7:             using (var ms = new MemoryStream(new byte[] { 0x0 }))
   8:             {
   9:                 image.SetSource(ms);
  10:             }
  11:         }
  12:         catch (Exception)
  13:         {
  14:         }
  15:     }
  16: }

It fixed the issue, but I didn’t dig much further. However, over the last few weeks I’ve seen many similar leaks reported on StackOverflow, so I’ve finally decided to get to the bottom of the problem.

Setup

To diagnose a memory issue, the first thing to do is to have a repro case. It must be as simple as possible and have no random components, to be able to focus on the issue.

First, I needed a list of pictures. I browsed the Internet a bit, found a wallpaper website with predictable urls, and set up a small program:

   1: public partial class MainPage : PhoneApplicationPage
   2: {
   3:     public MainPage()
   4:     {
   5:         this.CurrentIndex = 100;
   6:  
   7:         this.InitializeComponent();
   8:     }
   9:  
  10:     protected int CurrentIndex { get; set; }
  11:  
  12:     protected override void OnNavigatedTo(NavigationEventArgs e)
  13:     {
  14:         base.OnNavigatedTo(e);
  15:  
  16:         this.LoadNextPicture();
  17:     }
  18:  
  19:     void LoadNextPicture()
  20:     {
  21:         GC.Collect();
  22:         GC.WaitForPendingFinalizers();
  23:  
  24:         Debug.WriteLine(Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage);
  25:  
  26:         Thread.Sleep(1000);
  27:  
  28:         var bitmap = new BitmapImage();
  29:         bitmap.ImageOpened += bitmap_ImageOpened;
  30:         bitmap.ImageFailed += bitmap_ImageFailed;
  31:  
  32:         bitmap.UriSource = new Uri(string.Format("http://www.maximumwallhd.com/fonds-ecran/3d/abstrait/fond-ecran-3d-abstrait-{0}.jpg", this.CurrentIndex));
  33:  
  34:         this.MainImage.Source = bitmap;
  35:  
  36:         this.CurrentIndex++;
  37:     }
  38:  
  39:     void bitmap_ImageFailed(object sender, ExceptionRoutedEventArgs e)
  40:     {
  41:         Debug.WriteLine("Failed: " + (this.CurrentIndex - 1) + " - " + e.ErrorException);
  42:     }
  43:  
  44:     void bitmap_ImageOpened(object sender, RoutedEventArgs e)
  45:     {
  46:         this.LoadNextPicture();
  47:     }
  48: }


Basically, it calls the garbage collector, display the total memory usage, wait a while (to avoid triggering any anti-leech protection from the website), load the picture, then back to step one. The XAML part just contains an Image control called “MainImage”.

Start the app, and sure enough we’re leaking:

image

 

After only nine pictures, the app crashes with an OutOfMemory exception. Good! Now let’s try to find out what’s going on.

The workarounds

First of all, the workarounds. The two I know are : loading a dummy picture (as explained in the introduction), or setting the UriSource to null. Let’s try both.

Loading a dummy picture:

image

Setting UriSource to null:

image

The curves are really similar, in both case we’ve stopped leaking memory (at least: large amounts of memory). Now the problem is: we obviously shouldn’t have to use workarounds. Is there another way?

Finding the cause

After a bit of trial and error, I found something that I really wasn’t expecting. Removing the ImageOpened and ImageFailed event handler fixes the leak!

By simply changing the ImageOpened method to this:

   1: void bitmap_ImageOpened(object sender, RoutedEventArgs e)
   2: {
   3:     var bitmap = (BitmapImage)sender;
   4:  
   5:     bitmap.ImageOpened -= bitmap_ImageOpened;
   6:     bitmap.ImageFailed -= bitmap_ImageFailed;
   7:  
   8:     this.LoadNextPicture();
   9: }

The memory usage is higher than when using the workaround so I let the test run longer, but it looks stable:

image

Why the increase in memory usage? We’re calling LoadNextPicture from the ImageOpened event handler. Therefore, we’re still in the picture loading callstack, and the garbage collector has nothing to clean yet when it’s called. To test this theory, I replaced the call to LoadNextPicture by “Dispatcher.BeginInvoke(() => this.LoadNextPicture());”. Also, we’re calling GC.Collect before setting the new image source. Thus it isn’t available yet for collection, and we actually have two pictures at the same time in memory. After adding a GC.Collect after the image source assignment, we have the same memory curve as when using the workarounds:

image

 

Digging further

Now, we know that the leak occurs when not removing the ImageOpened and ImageFailed event handlers. The problem is: it shouldn’t happen.

As a quick reminder, the garbage collector works by keeping track of what is called “GC roots”. Those are references that can’t be collected: for instance, static variables. Then, the garbage collectors browses the objects referenced by those roots, and the objects referenced by those references, and so on. After recursively browsing the reference tree, all objects that weren’t found are freed from the memory.

In our case, the PhoneApplicationPage object is used by the application, and therefore won’t be freed. Therefore, when an object is referenced by the page, it won’t be freed either.

On top of that, we’ve got event handlers. Event handlers are a frequent source of memory leaks. Imagine we’re creating an object called “ChildObject”. We assign a method of this object, called “SomeMethod”, to the “Loaded” event of our page. Then, even though we’re not referencing this object any further in the code, the object is still referenced by the page (through the event handler) and will be kept alive as long as the page is alive!

Is that our problem here? No, because we’re assigning a method of our page to the ImageOpened and ImageFailed events. The BitmapImage is keeping a reference to our page, but not the other way around. So the BitmapImage is keeping the page alive, but the page shouldn’t keep the BitmapImage alive. The cause of our leak must be elsewhere.

Just to make sure, I reverted to the leaky version of the program, and created the described “ChildObject” class. It implements a finalizer, to know when it’s freed by the garbage collector, and an empty Bitmap_ImageOpened method:

 

   1: public class ChildObject
   2: {
   3:     ~ChildObject()
   4:     {
   5:         Debug.WriteLine("Finalizer");
   6:     }
   7:  
   8:     public void Bitmap_ImageOpened(object sender, RoutedEventArgs e)
   9:     {
  10:     }
  11: }

Then, I assign this “Bitmap_ImageOpened” method to the ImageOpened handler of the BitmapImage. Our LoadNextPicture method is now:

   1: void LoadNextPicture()
   2: {
   3:     GC.Collect();
   4:     GC.WaitForPendingFinalizers();
   5:  
   6:     Debug.WriteLine(Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage);
   7:  
   8:     Thread.Sleep(1000);
   9:  
  10:     var bitmap = new BitmapImage();
  11:  
  12:     var test = new ChildObject();
  13:  
  14:     bitmap.ImageOpened += test.Bitmap_ImageOpened;
  15:  
  16:     bitmap.UriSource = new Uri(string.Format("http://www.maximumwallhd.com/fonds-ecran/3d/abstrait/fond-ecran-3d-abstrait-{0}.jpg", this.CurrentIndex));
  17:  
  18:     this.MainImage.Source = bitmap;
  19:  
  20:     Debug.WriteLine(Microsoft.Phone.Info.DeviceStatus.ApplicationCurrentMemoryUsage);
  21:  
  22:     this.CurrentIndex++;
  23: }

When executing the application, the console shows:

5070848
26812416
Finalizer
37093376
Finalizer
53968896
Finalizer
70389760
Finalizer
79781888
Finalizer
96452608
Finalizer
113881088
Finalizer
129703936
Finalizer
146341888

The finalizer is called. So even though we’re leaking memory, the ChildObject is freed. Which means that the BitmapImage is freed as well!

To be sure, I tried to voluntarily keep the BitmapImage alive. I’ve done so by creating a property of type “List<object>” and storing the BitmapImage instances inside. Executing the app then provides the following output:

4993024
27533312
37752832
53293056
70389760
79060992
95039488
113156096
129724416
146386944

The finalizer isn’t called anymore. We’ve proven that the BitmapImage is indeed cleaned by the garbage collector. So what are we actually leaking? Maybe the memory profiler can tell us?

Loading the first snapshot, I tried seeing what was consuming the memory:

image

… Nothing. Which means we’re probably leaking native memory (the profiler only shows managed memory, .NET objects that is).

One more step

There’s nothing more we can do with Visual Studio. It’s time to start Reflector and decompile the framework’s assemblies to try to understand what’s happening.

We know the leak is centered around the event handler, so we can directly go and see what’s going on there.

Nothing interesting in the code of the ImageOpened event:

   1: public event EventHandler<RoutedEventArgs> ImageOpened
   2: {
   3:     add
   4:     {
   5:         base.AddEventListener(DependencyProperty.RegisterCoreProperty(0x659e, null), value);
   6:     }
   7:     remove
   8:     {
   9:         base.RemoveEventListener(DependencyProperty.RegisterCoreProperty(0x659e, null), value);
  10:     }
  11: }
  12:  

Removing the event handler fixes the leak, so let’s see what is in this “RemoveEventListener” method.

   1: internal void RemoveEventListener(DependencyProperty property, Delegate handler)
   2: {
   3:     this._coreTypeEventHelper.RemoveEventListener(this, property, handler);
   4: }

Meh, just a wrapper. Let’s see the _coreTypeEventHelper.RemoveEventListener method:

   1: internal void RemoveEventListener(IManagedPeer obj, DependencyProperty property, Delegate handler)
   2: {
   3:     foreach (KeyValuePair<int, EventAndDelegate> pair in this.EventAndDelegateTable)
   4:     {
   5:         if (!pair.Value.WrappedDelegate.Equals(handler) || ((!pair.Value.WrappedEvent.Equals(property) && ((!(pair.Value.WrappedEvent is CoreDependencyProperty) || !(property is CoreDependencyProperty)) || (((CoreDependencyProperty) pair.Value.WrappedEvent).m_nKnownId != ((CoreDependencyProperty) property).m_nKnownId))) && !QuirksMode.SkipEventComparisonDuringRemoveEventListener()))
   6:         {
   7:             continue;
   8:         }
   9:         string eventName = "M@" + pair.Key.ToString();
  10:         XcpImports.RemoveEventListener(obj, property, eventName);
  11:         this.EventAndDelegateTable.Remove(pair.Key);
  12:         break;
  13:     }
  14: }
  15:  

The method checks a few things, then calls the XcpImports.RemoveEventListener method, and finally removes something from the “EventAndDelegateTable” dictionary. Now that’s interesting, maybe the memory is used by the dictionary?

The dictionary stores instances of an internal class called “EventAndDelegate”. It only contains a reference to the dependency property and to the delegate used as event handler (in our case: the bitmap_ImageOpened method). Nothing big enough to explain the leak.

Then maybe the XcpImports.RemoveEventListener method?

   1: [SecuritySafeCritical]
   2: internal static unsafe void RemoveEventListener(IManagedPeerBase obj, DependencyProperty property, string eventName)
   3: {
   4:     CheckThread();
   5:     CValue outval = new CValue();
   6:     outval.SetCountAndType(eventName.Length, VType.valueString);
   7:     fixed (char* str = ((char*) eventName))
   8:     {
   9:         char* chPtr = str;
  10:         outval.m_pchValue = chPtr;
  11:         CheckHResult(RemoveEventListenerNative(obj.NativeObject, property.m_nKnownId, ref outval));
  12:         GC.KeepAlive(obj);
  13:     }
  14: }
  15:  
  16:  

It’s getting really low-level here. But the interesting part is the line:

   1: CheckHResult(RemoveEventListenerNative(obj.NativeObject, property.m_nKnownId, ref outval));

We’re calling a method called “RemoveEventListenerNative”. As its name indicates, it’s a native method, so we can’t use Reflector to see what it’s doing. But the method receives a parameter called “obj.NativeObject”, making it a nice candidate for our leak.

Stepping back and digging into the “add” part of the event, we reach a similar AddEventListener method, passing the “obj.NativeObject” parameter to a method called “AddEventListenerNative”:

   1: CheckHResult(AddEventListenerNative(obj.NativeObject, property.m_nKnownId, ref outval, handledEventsToo));

How to know whether it’s the source of the leak or not? By calling it ourselves!

Back in Visual Studio, with the leaky version of the app. Let’s set a breakpoint in the bitmap_ImageOpened method, then explore the BitmapImage object with the QuickWatch window:

image

Here we find our “EventAndDelegateTable” dictionary, with only one entry: our ImageOpened event handler (I removed the ImageFailed event handler to have as little noise as possible).

The XcpImports.RemoveEventListener takes three parameters: the instance of the BitmapImage class, the dependency property, and an “eventName” string. This string is the concatenation of the constant “M@” and the key of the entry in the EventAndDelegateTable table. In our case, we see in the QuickWatch window that the key is “7”. Therefore, we execute this in the “Immediate” window of Visual Studio:

MS.Internal.XcpImports.RemoveEventListener(bitmap, DependencyProperty.RegisterCoreProperty(13409, null), "M@7");

Then we resume the code execution by pressing F5, wait for the breakpoint to be hit again, and do the whole process once more. This time, the key is “8”, looks like it’s incremented by 1 every time.

After doing that a few times, we can look at the memory usage in the Output window:

28971008
37257216
37691392
44126208
37838848
38096896
45613056
45699072
45240320

The memory usage stays below 50MB. When compared to the previous values, it becomes obvious that we’ve found the source of the leak.

Conclusion

Up to this point, what have we found?

- When assigning an event handler to a BitmapImage (ImageOpened or ImageFailed, I haven’t tested with DownloadProgress but it’s probably the same), the runtime internally keeps a reference to the picture’s native object.

- When removing the event handler, the native object is freed as expected. If the event handler isn’t removed, the runtime keeps the reference to the native object, therefore leaking huge amounts of memory.

This is a bug. Our code follows the .NET guidelines, and we’ve proven that the managed instance of the BitmapImage object is released by the garbage collector. So we’re not keeping stray references anywhere.

What should the framework developers do?

It’s hard to tell, since I don’t know how the reference is used by the native code. But they should at least consider making the BitmapImage implement the IDisposeable interface, and clean the event handlers in the Dispose method. The IDisposeable interface is the .NET way to tell the developer “be extra careful or you may be leaking memory”. In the current implementation of the BitmapImage class, there is no way to suspect that a leak will occur.

What should third-party Windows Phone developers do?

When using a BitmapImage, ensure that you’re removing the event handlers in every possible code path. Use Visual Studio’s Performance Analyzer and test your code with very large pictures to make sure you’re not leaking memory. And set the UriSource of the BitmapImage to null, as an extra precaution.

[WP8] Debugging and fixing a bug in Windows Phone sync tool

Since a few days, I had this annoying bug in the application used to sync my Windows Phone with my desktop computer: every time I tried to synchronize podcasts, the application just crashed. After a while, I decided it would be a nice debugging exercise to dig into the issue myself.

First step: reproducing the issue. It was easy enough, the app automatically crashed when clicking the “Sync button”. And sure enough, this time again, it did crash:

5

 

I suspected that the application was written in .NET (most Windows Phone related apps are), so I clicked on Debug and selected Visual Studio 2012. VS directly showed me that an InvalidCastException was the issue, and that the method was called in ITunesMusicSyncSource.GetLocationForReverseTransfer.

1

It’s precise enough. From there, I started Reflector, loaded the Microsoft.WPSync.Sync.Source.iTunes assembly, and navigated to the specified method:

6

At first, I couldn’t find the problem. There’s sure a few casts in there, but none of them seemed nasty at first glance. I still had my Visual Studio open, so I started manually trying each cast in the QuickWatch window until I found the wrong one:

2

Indeed, according to the signature, the “item.Properties.ObjectForKey” method returns an object. If the property is not found, the default value (passed as the second parameter) is returned. So in this case, if the ZMEDIAITEM_ATTRIBUTE_BOOKMARK property isn’t found, the value 0 is returned. Then, the return value is casted to long. Except that 0 is an int! A cast from int to long is valid, but we first need to unbox the value by explicitly casting it to long.

Basically:

   1: long l = 0L;
   2: object obj = (object)l;
   3: int a = (int)obj; // Invalid
   4: int a = (int)(long)obj; // Valid

That’s an easy-to-make mistake, so I’m not really surprised it wasn’t detected in the released product.

Anyway, now that we know what the bug is, can we fix it? Sure. By using a Reflector add-in called ReflexIL, we can rewrite the bogus code.

3

It’s for those cases that knowing a bit of CIL can be quite useful. The “0” integer value is loaded on the stack by calling “ldc.i4.0”, then boxed to a System.Int32. We simply replace the instruction to box to a System.Int64, and add a “conv.i8” right before to convert the value to long. Then right-click on the assembly, and click on “Save as” to export the modified assembly. Since the original assembly is signed, ReflexIL asks what if we want to sign the new assembly:

4

We don’t have the signature file, so we can’t possibly re-sign it. So I’ve just selected “Register it for verification skipping (on this computer)” to allows the .NET framework to load the unsigned assembly. Since I don’t intend to publish the file, it shouldn’t be a problem.

Once the new assembly is generated, just replace the old one in the application’s folder (don’t forget to make a backup!)

Now start the application again, try to sync the podcast, and... Sure enough, it works!

7

Posté le par KooKiz | 0 commentaire(s)

[WP8] Programmatically terminate a Silverlight app

Exiting programmatically as always been an issue for Silverlight applications on Windows Phone 7. A few workaround existed, from throwing an exception to referencing a XNA assembly and using the ‘Game.Exit()’ method.

Windows Phone 8 brings a new API, that can be used from Silverlight applications: ‘Application.Terminate()’

Using it is really straightforward:

   1: Application.Current.Terminate();

A word of advice though: calling this method will immediately kill your app. It means that the ‘Application.Closing’ event won’t be triggered, and the contents of the ‘IsolatedStorageSettings.ApplicationSettings’ dictionary won’t be automatically saved to the isolated storage. Therefore, if needed, don’t forget to save that dictionary before calling the ‘Terminate’ method:

   1: IsolatedStorageSettings.ApplicationSettings.Save();
   2: Application.Current.Terminate();
Posté le par KooKiz | 0 commentaire(s)

[WP8] Programmatically change the lock screen picture

Windows Phone 8 introduces a new API to allow apps to change the background picture displayed on the lock screen. The displayed picture can be picked from the application’s resources or from the isolated storage.

To use this feature, you need first to declare it in the application’s manifest. Just add the following extension in the “Extensions” node:

   1: <Extension ExtensionName="LockScreen_Background" ConsumerID="{111DFF24-AA15-4A96-8006-2BFF8122084F}" TaskID="_default" />

If you don’t already have an “Extensions” node in the manifest, you’ll have to add it at the same level as the “Capabilities” and “Tasks” nodes:

   1: <Capabilities>
   2:   <!-- ... -->   
   3: </Capabilities>
   4: <Tasks>
   5:   <!-- ... -->
   6: </Tasks>
   7: <Tokens>
   8:   <!-- ... -->
   9: </Tokens>
  10: <Extensions>
  11:   <Extension ExtensionName="LockScreen_Background" ConsumerID="{111DFF24-AA15-4A96-8006-2BFF8122084F}" TaskID="_default" />
  12: </Extensions>

Once the manifest is updated, there’s one remaining step before being able to change the wallpaper: the application must first ask the user for permission. You can check if your app is allowed by checking the value of the “LockScreenManager.IsProvidedByCurrentApplication” property. If the app isn’t allowed, use the “LockScreenManager.RequestAccessAsync()” method to display a popup asking the user for permission.

Then, all you have to do is calling the “LockScreen.SetImageUri” method with the URI of the picture. The URI must be prefixed by “ms-appx:///” if the picture is stored in the resources, or “ms-appdata:///Local/” if the picture is stored in the isolated storage.

The final code should look like (for a picture stored in the isolated storage):

   1: try
   2: {
   3:     var isProvider = Windows.Phone.System.UserProfile.LockScreenManager.IsProvidedByCurrentApplication;
   4:  
   5:     if (!isProvider)
   6:     {
   7:         var permission = await Windows.Phone.System.UserProfile.LockScreenManager.RequestAccessAsync();
   8:  
   9:         isProvider = permission == Windows.Phone.System.UserProfile.LockScreenRequestResult.Granted;
  10:     }
  11:  
  12:     if (isProvider)
  13:     {
  14:         var uri = new Uri("ms-appdata:///Local/LockScreenPicture.jpg", UriKind.Absolute);                    
  15:  
  16:         Windows.Phone.System.UserProfile.LockScreen.SetImageUri(uri);
  17:     }
  18:     else
  19:     {
  20:         MessageBox.Show("Couldn't update the lockscreen picture.");
  21:     }
  22: }
  23: catch (Exception ex)
  24: {
  25:     MessageBox.Show("An error occured while updating the lockscreen picture: " + ex.Message);
  26: }

Of course, once the application is authorized, the lockscreen picture can be changed directly from a background agent. It is a new and interesting way to display information to the user and push phone’s personalization one step further.

Posté le par KooKiz | 0 commentaire(s)

[WP7] Dynamically toggle PanoramaItem visibility

A strange issue with the Panorama control, that has been brought to my attention by @lancewmccarthy, and which has also been encountered by some people on StackOverflow.

The original scenario was a bit too complex for a blog post, but we can reproduce it in a much simpler way.

Create a new page, and add a panorama control called ‘Panorama’. Then add two PanoramaItem, and put a button in the first one:

   1: <controls:Panorama x:Name="Panorama">
   2:     <controls:PanoramaItem Header="Item1" x:Name="Item1">
   3:         <Button Content="Test" Click="Button_Click" />
   4:     </controls:PanoramaItem>
   5:     <controls:PanoramaItem Header="Item2" x:Name="Item2" >
   6:     </controls:PanoramaItem>
   7: </controls:Panorama>

In the click event handler of the button, we toggle the visibility of the second item of the panorama:

   1: private void Button_Click(object sender, RoutedEventArgs e)
   2: {
   3:     this.Item2.Visibility = this.Item2.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
   4: }

Start the application, try tapping on the button, and the visibility of the PanoramaItem changes as expected.

Now, let’s just change the XAML to set the visibility of the second item of the panorama to ‘Collapsed’:

   1: <controls:Panorama x:Name="Panorama">
   2:     <controls:PanoramaItem Header="Item1" x:Name="Item1">
   3:         <Button Content="Test" Click="Button_Click" />
   4:     </controls:PanoramaItem>
   5:     <controls:PanoramaItem Header="Item2" x:Name="Item2" Visibility="Collapsed">
   6:     </controls:PanoramaItem>
   7: </controls:Panorama>

Start the application again, tap on the button, and… Nothing happens! What’s going on?

Diving a bit in the Panorama control source code, using good ol’ friend Reflector, we can see that the Panorama host a PanoramaPanel control. The PanoramaPanel contains most of the items placement logic, and has a ‘VisibleChildren’ property. Looks promising!

image

Only a handful of methods access this property, and we can quickly conclude that the ‘VisibleChildren’ collection is populated only by the `MeasureOverride’ method. From there, we can elaborate a theory: at loading time, our panorama item isn’t visible, and therefore isn’t added to the `VisibleChildren’ collection. Later, when we change the visibility of the PanoramaItem, the panorama’s position isn’t invalidated, so the list of visible items isn’t refreshed, and the PanoramaItem isn’t added back to the ‘VisibleChildren’ collection.

It’s easy to test, let’s just change our click event handler to force the panorama to re-compute its size:

   1: private void Button_Click(object sender, RoutedEventArgs e)
   2: {
   3:     this.Item2.Visibility = this.Item2.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
   4:     this.Panorama.Measure(Size.Empty);
   5: }
And sure enough, it works! Now, the ‘Measure’ method expects a parameter. Giving ‘Size.Empty’ basically tells the control “Use all the space available”. While it should be ok in most case, it may have unforeseen consequences in some specific scenarios.

Unfortunately, just calling the ‘InvalidateMeasure’ method of the panorama doesn’t work. It looks like the event isn’t propagated to the child panel. And we can’t directly access the child panel because it isn’t exposed in a public property. Is there another way out?

By randomly browsing the source code of the PanoramaPanel with Reflector, we can see a ‘NotifyDefaultItemChanged’ method, which looks quite promising:

   1: internal void NotifyDefaultItemChanged()
   2: {
   3:     base.InvalidateMeasure();
   4:     base.InvalidateArrange();
   5:     base.UpdateLayout();
   6: }

Now if we could just trigger this method, our problem would be solved. Using the ‘Analyze’ feature of Reflector, we can see that this method is called by the setter of the ‘DefaultItem’ property of the panorama:

image

That’s perfect! We just have to change the panorama’s default item to ensure that our PanoramaItem becomes visible as expected. Since we don’t want to disrupt the panorama, and since there’s no specific check in the property setter, we just assign back the value of the property to itself:

   1: private void Button_Click(object sender, RoutedEventArgs e)
   2: {
   3:     this.Item2.Visibility = this.Item2.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
   4:     this.Panorama.DefaultItem = this.Panorama.DefaultItem;
   5: }

Now the panorama is behaving as expected, and the visibility of the PanoramaItem is correctly updated, even if the item was collapsed when the page was loaded.

Posté le par KooKiz | 0 commentaire(s)

[WP7] IsolatedStorageException when opening a file played by a MediaElement

Today we’ll dig a little into Windows Phone’s base class library, thanks to a question I found on stack overflow.

The problem occurs with this simple code, used to load a music file from the isolated storage, and play it with a MediaElement:

   1: using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
   2: {            
   3:      using (var isfs = new IsolatedStorageFileStream(selected.Path, FileMode.Open, isf))
   4:      {                        
   5:           this.media.SetSource(isfs);              
   6:           isfs.Close();                        
   7:      }          
   8:           
   9:      isf.Dispose();
  10: }

When executed for the first time, this code works fine. When executed a second time with the same file, it throws an IsolatedStorageException: “Operation not permitted on IsolatedStorageFileStream”.

There isn’t many situations where opening a file from the isolated storage will throw an exception. The main two reasons are: an invalid filename, or a file sharing issue. Since the error only occurs when trying to play the same file twice, the second reason seems a pretty good candidate.
The default value of the ‘FileShare’ parameter in the IsolatedStorageFileStream constructor is ‘FileShare.ReadWrite’. If a file is opened with this flag, any concurrent operation on the same file will fail. And indeed, everything works fine once the code is replaced by:

   1: using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
   2: {            
   3:      using (var isfs = new IsolatedStorageFileStream(selected.Path, FileMode.Open, FileAccess.Read, isf))
   4:      {                        
   5:           this.media.SetSource(isfs);              
   6:           isfs.Close();                        
   7:      }          
   8:           
   9:      isf.Dispose();
  10: }

Now the thing that has been bugging me is: why is there any concurrency issue, since the stream is correctly closed? Note that the ‘Stream.Close’ method is even explicitly closed, even though the dispose should suffice!

To figure that out, I decided to launch Reflector and dig into the source code of the MediaElement.SetSource method:

   1: public void SetSource(Stream stream)
   2: {
   3:     if (stream == null)
   4:     {
   5:         throw new ArgumentNullException("stream");
   6:     }
   7:     if (stream.GetType() != typeof(IsolatedStorageFileStream))
   8:     {
   9:         throw new NotSupportedException("Stream must be of type IsolatedStorageFileStream");
  10:     }
  11:     IsolatedStorageFileStream stream2 = stream as IsolatedStorageFileStream;
  12:     stream2.Flush();
  13:     stream2.Close();
  14:     this.Source = new Uri(stream2.Name, UriKind.Absolute);
  15: }
  16:  
  17:  
  18:  
  19:  

As surprising as it may first seem, the SetSource method doesn’t use the data stored in the stream. It even flushes and closes the stream! All it does is just storing the name of the stream, and the MediaElement will manually re-open the file when you call the ‘Play’ method. And since the control has it own handle on the file, any attempt to open the file with exclusive access while the music is playing will fail.

It’s interesting to note that you can save all the pain of initializing the isolated storage and opening the file. Just setting the file’s URI to the ‘Source’ property of the MediaElement will have exactly the same result,  since the ‘SetSource’ method does nothing more!

Link to the original question on StackOverflow: http://stackoverflow.com/questions/10403631/getting-isolatedstorageexception-operation-not-permitted-on-isolatedstoragefile/

Posté le par KooKiz | 0 commentaire(s)

[WP7] Dynamically change startup page

Let’s say that you want to allow the user to customize the startup page of your application. You can easily change the startup page by editing the ‘NavigationPage’ attribute in the manifest file. But the manifest cannot be modified once the application has been published. How to define it at runtime?

For this, we’ll use a class very useful in ‘classical’ Silverlight, but widely forgotten on Windows Phone: UriMapper.

In this example, we create an application with three pages (Page1, Page2, and Page3), and we want the startup page to be picked randomly among those three pages.

First, change the manifest to use an inexistent dummy starting page:

   1: <DefaultTask Name="_default" NavigationPage="DummyPage.xaml" />

Now, create the UriMapper at the end of the Application constructor (in the App.xaml.cs file) and map the DummyPage to a random page. Then assign the UriMapper to the root frame:

   1: var mapper = new UriMapper();
   2:  
   3: int random = new Random().Next(0, 3);
   4:  
   5: mapper.UriMappings.Add(new UriMapping
   6: {
   7:     Uri = new Uri("/DummyPage.xaml", UriKind.Relative),
   8:     MappedUri = new Uri("/Page" + (random + 1) + ".xaml", UriKind.Relative)
   9: });
  10:  
  11: this.RootFrame.UriMapper = mapper;

And you’re done! Every time you start the application, one of the three pages will be picked at random.

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , , ,

[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!

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , , , ,

[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.

Posté le par KooKiz | 0 commentaire(s)

[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.

Posté le par KooKiz | 0 commentaire(s)

[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.

Posté le par KooKiz | 0 commentaire(s)

[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 !

Posté le par KooKiz | 1 commentaire(s)
Classé sous : , ,

[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!

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , , ,

[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.

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , , , ,

[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.

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , , ,

[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

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , ,

[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 !

Posté le par KooKiz | 2 commentaire(s)

[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.

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , , ,

[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

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , , , ,

[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)

Posté le par KooKiz | 0 commentaire(s)
Classé sous : , , , ,
Plus de Messages Page suivante »


Les 10 derniers blogs postés

- ProcDump 6.0 : support du filtrage sur messages d'exceptions .NET, des filtres multiples et du ciblage par nom de service par CoqBlog le il y a 14 heures et 59 minutes

- Votez pour le TOP 10 des influenceurs SharePoint francophones ! par Le blog de Patrick [MVP SharePoint] le il y a 16 heures et 50 minutes

- [Conf’SharePoint] Dernier rappel ! :-) par Le blog de Patrick [MVP SharePoint] le il y a 20 heures et 41 minutes

- [ #SharePoint 2013 ] les modèles de sites standards… par Le blog de Patrick [MVP SharePoint] le il y a 20 heures et 47 minutes

- 10 erreurs de compréhension concernant SharePoint… par Le blog de Patrick [MVP SharePoint] le il y a 21 heures et 23 minutes

- 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

- SharePoint : Incompatibilité avec Internet Explorer 10 (IE10) par Blog Technique de Romelard Fabrice le 05-08-2013, 16:29

- AutoSPInstaller pour SharePoint 2013 maintenant disponible en “RTM” par Julien Chable le 05-06-2013, 23:30