Delay's Blog is the blog of David Anson, a Microsoft developer who works with the Silverlight, WPF, Windows Phone, and web platforms.
http://dlaa.me/
@DavidAns
As computing devices have become more powerful, the trend has been toward more "fluid" user interfaces - interaction models that flow smoothly from one state to another. This differs from earlier approaches where resources were more limited and interactions tended to be Spartan and isolated from each another. Some of the motivation behind an increased focus on fluid UI is almost certainly the "wow" factor; modern interfaces can be quite attractive and fun to use! But there's also scientific research to back this up: people tend to find smooth transitions less disruptive to their workflow than abrupt ones - and the animation itself can help tie the "before" and "after" states together by showing how one becomes the other.
With that in mind, one of the things I've wanted to do is apply this principle to enhance the user experience for Silverlight applications on the Windows Phone platform. Specifically, I wanted to make it easy for developers to animate the orientation change of an application that occurs when the phone is rotated from its default portrait orientation to landscape. This is one of those cases where a picture is worth a thousand words, so I've created a short video showing the default behavior of a Silverlight-based Windows Phone application when the device (in this case the emulator) is rotated.
Click the image below to view a brief H.264-encoded MP4 video of the default rotation behavior:
For people who have never used the emulator: those two buttons I click rotate the device one quarter turn clockwise or counter-clockwise. I begin by rotating counter-clockwise once, then clockwise once back to the starting orientation, then clockwise three more times to loop all the way around.
What I show above is what you get for free when you create a new application - and it's great the device and platform support dynamic orientation changes without any special effort! But I thought it would be cool if I could extend that just a bit in order to animate those orientation changes - again, without requiring the developer to change anything (beyond a couple of superficial name changes).
Click the image below to view a short video of the animated behavior I've created:
As in the first video, I show the device rotating to landscape, then back, then all the way around. But this time I've included a little bit of fun at the end. :)
The custom rotation behavior is made possible by the AnimateOrientationChangesPage class I created which works by lying to the layout system (Now, where have I heard that before?). It's a small, self-contained class you insert into the default hierarchy and then never need to worry about again. AnimateOrientationChangesPage automatically gets involved with the necessary steps for rotation and even handles page-to-page navigation changes seamlessly.
AnimateOrientationChangesPage
My goal was to make it easy for Windows Phone applications to offer the kind of fluid rotation experience that's becoming common these days - and it seems like AnimateOrientationChangesPage does that. I hope you find it useful!
[Click here to download the AnimateOrientationChanges sample for the Windows Phone 7 platform.]
Notes:
MainPage.xaml
AnimateOrientationChangesPage.cs
// Remove this #define when using more recent phone/tools builds #define APRIL_TOOLS
MainPage
SupportedOrientations = SupportedPageOrientation.Portrait | SupportedPageOrientation.Landscape;
MainPage.cs
using System; // ... using Delay; namespace WindowsPhoneApplication1 { public partial class MainPage : AnimateOrientationChangesPage { // ... } }
MainPage.cs.xaml
<delay:AnimateOrientationChangesPage x:Class="WindowsPhoneApplication1.MainPage" xmlns:delay="clr-namespace:Delay" ...> <!-- ... --> </delay:AnimateOrientationChangesPage>
Duration
EasingFunction
IsAnimationEnabled
false
PortraitDown
case
My last post talked about the upcoming MSDN blogging platform upgrade. That upgrade took place during last week and new posts/comments for this blog were consequently disabled.
Today, I'm happy to report the upgrade was successfully completed and this blog is back online! I have some new articles in the queue and look forward to posting them soon...
Thank you for your patience - I hope you enjoy the new blogging platform!
The administrators of the MSDN blogging platform are performing a software upgrade and will be putting all blogs into read-only mode on Sunday, May 16th. New posts and new comments will not be possible for this blog during the transition. The upgrade is expected to finish by Monday, May 24th - but difficulties during the migration could push that date back. I'll post a quick note once the dust has settled and things are back to normal.
In the meantime, you should be able to contact me using the old platform's email form. Alternatively, I can be reached on Twitter as @DavidAns.
Thank you for your patience - see you on the other side! :)
I've described the "app building" process before in this post about my HeadTraxExtreme sample for the Silverlight 3 Beta and a later in this update of HeadTraxExtreme for the Silverlight 3 RTW. The gist is that:
... everyone comes up with an idea for a medium-sized application they think could be built with the bits at hand - then goes off and tries to build as much of that application as they can before time runs out. The emphasis is on testing new scenarios, coming up with creative ways of integrating components, and basically just getting the same kind of experience with the framework that customers have every day. Coming up with a beautifully architected solution is nice if it happens, but not specifically a goal. Rather, the point is to help people take a holistic look at how everything works together...
App building is a great way to get exposure to parts of the product you don't normally deal with and can help give team members a more complete view of the platform they work on. I had another app building opportunity recently and my goal was to create a utility application I've been wanting for a while.
Here's where I got in the bit of time I had:
Disclaimer: In case it's not completely obvious, none of the file names above is accurate. :)
The Sales Pitch
Did you ever want an easy way to transfer files between two machines on semi-isolated networks (ex: home and work)? Looking for an easier way to publish content to the web? Tired of sending yourself files as email attachments all the time? Sneaker-net got you down? Well, your dreams have just come true!
BlobStore is the hot new craze that's taking the world by storm. It's a small, lightweight Silverlight 4 application that acts as a basic front-end for the Windows Azure Simple Data Storage and the Amazon Simple Storage Service (S3)! Just run the BlobStore app, point it at a properly provisioned Azure or S3 account (see below for details on provisioning), and all of a sudden sharing files with yourself and others becomes as simple as dragging and dropping them! BlobStore makes it easy to manage your files by providing a feature-rich (okay, that's an exaggeration) interface that allows you to view, download, or delete files and copy their URL to the clipboard easy-peasy. Logon credentials are stored on your machine so there's no need to remember long account passwords - once you've connected once, future connections are simple and painless.
BlobStore
But wait, there's more!
If you download right now, you'll also get the complete source code from which you can explore all the inner workings of this life-changing application. Included with every code download is a free REST wrapper for basic Azure and S3 blob access that handles all the tricky Authorization header details for you. It's almost guaranteed to make your next Azure/S3 application a snap to develop and a wild success in the marketplace.
So now how much would you pay? :)
Featured Silverlight 4 Features
Potential Enhancements (TODOs)
Potential v.Next Enhancements (features not supported by Silverlight 4)
Notes
[Click here to run the BlobStore application in your browser.]
[Click here to download the complete source code for the Silverlight 4 BlobStore sample.]
Provisioning
Before BlobStore can access an Azure/S3 account, that account needs to be provisioned to allow access by a Silverlight browser-based application and permit unauthenticated third parties to download content. Steve Marx has a great overview of provisioning an Azure account - the basic steps are to create the special $root container, make it world-readable, and upload a clientaccesspolicy.xml file that lets Silverlight know the cross-domain access is okay. For an S3 account, the concept is the same, but the implementation is a bit simpler - just upload a clientaccesspolicy.xml file and make it world-readable.
$root
clientaccesspolicy.xml
These one-time provisioning steps can be done manually, in code, or with one of the public tools for Azure or S3. The clientaccesspolicy.xml file I use is taken from Steve's example with a tweak to make support for http and https more explicit:
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-methods="*" http-request-headers="*"> <domain uri="http://*" /> <domain uri="https://*" /> </allow-from> <grant-to> <resource path="/" include-subpaths="true" /> </grant-to> </policy> </cross-domain-access> </access-policy>
Summary
BlobStore was a fun project to work on and it gave me some nice exposure to Silverlight's networking stack (which I don't normally get a chance to use). There are some rough edges I didn't get around to smoothing over, but on the whole I think BlobStore turned out pretty well: it's small and quick to download, easy to use, it simplifies something I do every day, and it was a great learning experience!
Aside: You can even use BlobStore to publish Silverlight applications (just upload the XAP and HTML file), so it's got that recursive thing going for it, too...
XAP
HTML
I'd never claim BlobStore is the best example of application design, but I did try to follow "best practices" in most cases; there are some interesting techniques in there that some of you may not have seen in action. And if writing and sharing BlobStore helps others learn how to develop better Silverlight applications, we all win. :)
Enjoy!
I was contacted by Simon Weaver via Twitter about a problem where the Bindings for MenuItems of a ContextMenu on a DataGrid were acting as though they were associated with a different row than they really were. This seemed pretty weird until Yifung Lin, one of the original DataGrid developers, suggested this might be due to the row recycling behavior DataGrid uses to improve performance. It turns out he was right - but the rabbit hole goes much deeper than that...
MenuItem
ContextMenu
DataGrid
Working from an internal reproduction of the problem Brian Braeckel created, I found that I was able to simplify the scenario significantly - to the point of removing ContextMenu and DataGrid entirely! Signs were pointing strongly to this being a bug in the Silverlight framework, but before I started crying wolf, I wanted someone to review my work to be sure I hadn't over-simplified things. Fortunately, RJ Boeke was around and not only confirmed the validity of my repro, but also pointed out further simplifications!
When all is said and done, the bug is pretty easy to describe:
If a Binding points to an element via its ElementName or Source parameters and the DataContext of a parent of that element changes, that change will NOT be propagated through the Binding. However, if the DataContext of the element itself changes, the change will be propagated correctly.
Binding
DataContext
RJ and I were both kind of surprised to find this bug in Silverlight 4, but when Sage LaTorra looked into this from the QA side, he reported the same problem with Silverlight 3. Which - in a weird kind of way - is actually good news because it means things haven't gotten worse with Silverlight 4 and existing applications won't break unexpectedly!
Unfortunately, the DataGrid+ContextMenu scenario relies on this exact behavior to work correctly... When a ContextMenu is about to be displayed, a Popup is created, the menu UI is added to it, and it's shown. Because the Popup isn't in the visual tree, the ContextMenu won't see the right DataContext by default - so it sets up a Binding with its Source set to the ContextMenu's owner element. This works well and the DataContext then "flows" into the Popup where MenuItems can see it and bind to it successfully. The problem in the DataGrid scenario arises when DataGrid decides to recycle one of its rows by swapping one DataContext for another. Though this is a perfectly legitimate thing to do, it runs afoul of this bug and breaks the scenario.
Popup
Source
Fortunately, there's an easy workaround. Even better: it works for all known instances of the problem, not just the DataGrid+ContextMenu kind! Here's an example of code that demonstrates the bug when the DataContext of a parent element is changed (which you can do in the sample application by clicking on the red or green background):
<!-- Simple example of the broken scenario --> <Grid x:Name="SimpleBroken" Grid.Column="0" Grid.RowSpan="2" Background="Red"> <TextBlock Text="{Binding DataContext, ElementName=SimpleBroken}"/> </Grid>
And here's what it looks like with the workaround applied:
<!-- Simple example of the workaround --> <delay:DataContextPropagationGrid x:Name="SimpleWorking" Grid.Column="1" Grid.RowSpan="2" Background="Green"> <TextBlock Text="{Binding DataContext, ElementName=SimpleWorking}"/> </delay:DataContextPropagationGrid>
Pretty similar, huh? The way this works is that the DataContextPropagationGrid class derives from Grid, listens for DataContext changes in the usual manner, then uses the fact that changes to the local value of DataContext work to "re-broadcast" the change to any Bindings targeting it via ElementName or Source. The important thing to note is that any Binding broken because of the underlying platform bug needs to be pointed at the DataContextPropagationGrid wrapper instead - and should then behave correctly.
DataContextPropagationGrid
ElementName
Aside: I haven't historically considered Grid for workaround scenarios like this. However, it seems like a good fit: Grid can be wrapped around nearly anything without side-effects, it allows multiple children for scenarios where that might be necessary, it's familiar to developers and designers, and it keeps the workaround code simple!
Grid
Just to "close the loop", here's an example of a DataGrid+ContextMenu demonstrating the problem:
To reproduce it yourself, run the sample application, right-click on every row of the red DataGrid (everything will be correct), then scroll to the end of the list and do the same - you'll quickly find a mis-match like I show above. The fix is as easy as introducing the DataContextPropagationGrid class (as I did for the green DataGrid) - the important thing is to be sure to attach the ContextMenu to the DataContextPropagationGrid so the workaround has a chance to do its thing:
<!-- DataGrid+ContextMenu example of the workaround --> <sdk:DataGrid Grid.Column="1" Grid.Row="1" AutoGenerateColumns="False" Margin="10"> <sdk:DataGrid.Columns> <sdk:DataGridTemplateColumn Header="Values"> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <delay:DataContextPropagationGrid> <toolkit:ContextMenuService.ContextMenu> <toolkit:ContextMenu> <toolkit:MenuItem Header="{Binding}"/> </toolkit:ContextMenu> </toolkit:ContextMenuService.ContextMenu> <TextBlock Text="{Binding}"/> </delay:DataContextPropagationGrid> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> </sdk:DataGrid.Columns> </sdk:DataGrid>
If you run into this Silverlight bug in the DataGrid+ContextMenu scenario, please apply the DataContextPropagationGrid workaround and things should work properly. And if you happen run into the bug in some other scenario, the good news is that the DataContextPropagationGrid workaround should work there, too! Just be mindful to point the Binding's ElementName or Source at the DataContextPropagationGrid element, and you're good to go.
[Click here to download the complete source code for the SilverlightDataContextBugWorkaround sample.]
PS - Here's the code for the workaround:
using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace Delay { /// <summary> /// Class to help work around a Silverlight bug where DataContext changes to /// an element aren't propagated through Bindings on child elements that use /// ElementName or Source. /// </summary> public class DataContextPropagationGrid : Grid { /// <summary> /// Initializes a new instance of the DataContextPropagationGrid class. /// </summary> public DataContextPropagationGrid() { // Create a Binding to keep InheritedDataContextProperty correct SetBinding(InheritedDataContextProperty, new Binding()); } /// <summary> /// Identifies the InheritedDataContext DependencyProperty. /// </summary> public static readonly DependencyProperty InheritedDataContextProperty = DependencyProperty.Register( "InheritedDataContext", typeof(object), typeof(DataContextPropagationGrid), new PropertyMetadata(null, OnInheritedDataContextChanged)); /// <summary> /// Handles changes to the InheritedDataContext DependencyProperty. /// </summary> /// <param name="d">Instance with property change.</param> /// <param name="e">Property change details.</param> private static void OnInheritedDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { DataContextPropagationGrid workaround = (DataContextPropagationGrid)d; // Update local value of DataContext to prompt Silverlight to update problematic Bindings workaround.DataContext = e.NewValue; // Unset local value of DataContext so it will continue to inherit from the parent workaround.ClearValue(FrameworkElement.DataContextProperty); } } }
Last week saw the release of the Windows Phone Developer Tools CTP - April Refresh, a free set of tools and an emulator that lets everyone get started writing Windows Phone 7 applications. Not only is the application platform for Windows Phone 7 based on the same Silverlight framework many of us already know and love, but the tools run on any standard Windows machine and the inclusion of the device emulator means you can test apps on the device without having a device. It's a great developer story and I'm a big fan!
However, because Windows Phone 7 is still under development, early adopters sometimes need to deal with a few rough edges... :) I'd heard of occasional difficulties when folks tried to update their applications to run on the April Tools Refresh, so I thought it would be a good learning experience to update an application myself. The obvious choice was my "Silverlight Data Visualization assembly running on Windows Phone 7" blog post sample. Here's how it went for me on a machine that already had the April Refresh installed:
DataVisualizationOnWindowsPhone.sln
Capability
WMAppManifest.xml
Properties
... <Capabilities> <Capability Name="ID_CAP_NETWORKING" /> <Capability Name="ID_CAP_LOCATION" /> <Capability Name="ID_CAP_SENSORS" /> <Capability Name="ID_CAP_MICROPHONE" /> <Capability Name="ID_CAP_MEDIALIB" /> <Capability Name="ID_CAP_GAMERSERVICES" /> <Capability Name="ID_CAP_PHONEDIALER" /> <Capability Name="ID_CAP_PUSH_NOTIFICATION" /> <Capability Name="ID_CAP_WEBBROWSERCOMPONENT" /> </Capabilities> ...
FileLoadException
System.Windows.Controls.DataVisualization.Toolkit.dll
System.Windows.Controls.dll
Aside: Yes, this is super-goofy - but I'm told it's going to be fixed!
So, when all is said and done, this ended up being more involved than I expected - but once I jumped through the hoops, my sample application worked exactly the same as it did when I wrote it. I'm happy to report that Charting still runs fine on Windows Phone 7 - and that I've updated the source code download with these changes so nobody else needs to repeat the process! :)
[Click here to download the updated Windows Phone 7 Data Visualization sample application.]
A few days ago, Martin Naughton and Tiago Halm de Carvalho e Branco independently contacted me to report a problem they were having with the new ContextMenu control in the April '10 release of the Silverlight Toolkit. In both cases, they were toggling the IsEnabled property of a MenuItem directly and reported that the control's visuals weren't updating correctly. I was a little surprised at first because I knew I'd tested dynamic changes to the enabled state and I'd seen them work properly. But once I created a test project to investigate the report, I saw how the problem scenario was different.
The approach I focused my testing on (and which works correctly by all accounts) is the ICommand (Command/CommandParameter) scenario where the enabled state of the MenuItem is controlled by the CanExecute method of the ICommand implementation. In this scenario, the MenuItem changes its own IsEnabled state and updates its visuals explicitly, so everything is always in sync. But the code from the bug reports wasn't using ICommand; it was manipulating the IsEnabled property directly. The bug is that MenuItem doesn't find out about those changes - the indirect reason being that it doesn't own the IsEnabled property (which it inherits from Control). Because MenuItem doesn't know about the change, it doesn't know to update its visual state. :(
ICommand
IsEnabled
Fortunately, there are some easy workarounds!
Workarounds
System.Windows.Controls.Input.Toolkit
MenuItem.cs
@@ -143,6 +143,7 @@ public MenuItem() { DefaultStyleKey = typeof(MenuItem); + IsEnabledChanged += new DependencyPropertyChangedEventHandler(HandleIsEnabledChanged); UpdateIsEnabled(); } @@ -301,6 +302,16 @@ } /// <summary> + /// Called when the IsEnabled property changes. + /// </summary> + /// <param name="sender">Source of the event.</param> + /// <param name="e">Event arguments.</param> + private void HandleIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + ChangeVisualState(true); + } + + /// <summary> /// Changes to the correct visual state(s) for the control. /// </summary> /// <param name="useTransitions">True to use transitions; otherwise false.</param>
Template
// "Bouncing" the Template after toggling works around the issue menuItemBounce.IsEnabled = !menuItemBounce.IsEnabled; var template = menuItemBounce.Template; menuItemBounce.Template = null; menuItemBounce.Template = template;
MenuItemIsEnabledWorkaround.IsActive
MenuItemIsEnabledWorkaround
IsActive
True
Command
CanExecuteChanged
CanExecute
<toolkit:MenuItem x:Name="menuItemWorkaround" Header="MenuItem with workaround active" delay:MenuItemIsEnabledWorkaround.IsActive="True"/>
// Activating the workaround in XAML requires no code changes menuItemWorkaround.IsEnabled = !menuItemWorkaround.IsEnabled;
Examples
I've created a sample application to demonstrate the use of the last two workarounds in practice. It contains a simple ContextMenu with three MenuItems and toggles their IsEnabled state every second (whether the menu is open or not). You'll see either of the last two workarounds is enough to keep the corresponding MenuItem's visual state up to date.
[Click here to download the MenuItemIsEnabledWorkaround sample application and source code.]
It's never fun when a bug sneaks by you. :( But it is nice when there are a variety of good options that don't involve jumping through hoops to implement. If you've run into this bug, I apologize for the trouble - and I hope these options help get you going again!