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
The default power settings for Windows are set up so a computer will go to sleep after 15 to 30 minutes of inactivity (i.e., no mouse or keyboard input). This is great because a computer that's not being used doesn't need to be running at full power. By letting an idle machine enter sleep mode, the user benefits from a significant reduction in electricity use, heat generation, component wear, etc.. And because sleep mode preserves the state of everything in memory, it's quick to enter, quick to exit, and doesn't affect the user's work-flow. All the same applications continue running, windows stay open and where they were, etc.. So sleep mode is a Good Thing and I'm a fan.
However, sometimes a computer is busy even though someone isn't actively using the mouse and keyboard; common examples include playing a movie, burning a DVD, streaming music, etc.. In these cases, you don't want the machine to go to sleep because you're using it - even though you're not actually using it! So most media players and disc burners tell Windows not to go to sleep while they're running. In fact, there's a dedicated API for exactly this purpose: the SetThreadExecutionState Win32 Function.
But what about those times when the computer is busy doing something and the relevant program doesn't suppress the default sleep behavior? For example, it might be downloading a large file, re-encoding a music collection, backing up the hard drive, or hashing the entire contents of the disk. You don't want the machine to go to sleep for now, but are otherwise happy with the default sleep behavior. Unfortunately, the easiest way I know of to temporarily suppress sleeping is to go to Control Panel, open the Power Options page, change the power plan settings, commit them - and then remember to undo everything once the task is finished. It's not hard; but it's kind of annoying...
So here's a better way:
Insomnia is a simple WPF application that calls the SetThreadExecutionState API to disable sleep mode for as long as it's running. (Note that the display can still power off during this time - it's just sleep for the computer that's blocked.) Closing the Insomnia window immediately restores whatever sleep mode was in effect before it was run. It couldn't be easier!
SetThreadExecutionState
[Click here to download the Insomnia application along with its complete source code.]
Notes:
True
Finally, here's the implementation:
public partial class Window1 : Window { private uint m_previousExecutionState; public Window1() { InitializeComponent(); // Set new state to prevent system sleep (note: still allows screen saver) m_previousExecutionState = NativeMethods.SetThreadExecutionState( NativeMethods.ES_CONTINUOUS | NativeMethods.ES_SYSTEM_REQUIRED); if (0 == m_previousExecutionState) { MessageBox.Show("Call to SetThreadExecutionState failed unexpectedly.", Title, MessageBoxButton.OK, MessageBoxImage.Error); // No way to recover; fail gracefully Close(); } } protected override void OnClosed(System.EventArgs e) { base.OnClosed(e); // Restore previous state if (0 == NativeMethods.SetThreadExecutionState(m_previousExecutionState)) { // No way to recover; already exiting } } private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) { // Start an instance of the NavigateUri (in a browser window) Process.Start(((Hyperlink)sender).NavigateUri.ToString()); } } internal static class NativeMethods { // Import SetThreadExecutionState Win32 API and necessary flags [DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags); public const uint ES_CONTINUOUS = 0x80000000; public const uint ES_SYSTEM_REQUIRED = 0x00000001; }
The WPF Toolkit team just published the June 2009 release of the WPF Toolkit. Those of you who know I'm on the Silverlight Toolkit team are probably wondering why this is relevant - so let's get right to the release notes for the next version of Silverlight/WPF Charting because they'll clear things up:
Notable Changes WPF is now an official platform for Charting! Today's release of the June 2009 WPF Toolkit includes the binaries for WPF Charting, the associated design-time assemblies for both Visual Studio 2008 and Blend 3, and the complete source code for everything (under the usual Ms-PL license). Prior to today, WPF Charting only existed informally because of a blog post I'd written and some bits I'd shared. As of today, that "do it yourself" approach is a thing of the past - customers can get signed binaries and ready-to-build source code for Charting as part of the WPF Toolkit. And, as always, Charting exposes the same API and supports the same XAML on both platforms - making application portability trivial! Improved performance of internal data structures for many common scenarios. Charting now makes use of left-leaning red-black trees to maintain properly balanced data structures. For more detail on this change, please refer to my post about the LeftLeaningRedBlackTree implementation. Numerous bug fixes for animation inconsistencies between Silverlight and WPF. Storyboards and Animations sometimes behave a little differently on Silverlight/WPF, and a good bit of effort was spent trying to ensure that Charting will behave the same way on both platforms. In most cases, this was a matter of finding an implementation both platforms agreed on - in some it meant resorting to small, localized #if blocks. Fixed handling of data objects with non-unique hash codes. When each data object had a unique hash code, things already worked fine. But data sets containing items sharing the same hash code could exhibit incorrect behavior in previous releases. Most typical data sets would not have encountered this problem because hash codes are nearly always unique - but there are certain classes that report quite UNunique hash codes and could trigger the problem fairly easily. This is no longer an issue. Corrected behavior of charts at very small sizes and during animations. Some third party controls offer so-called "fluid" layout in which size changes are all animated and elements can easily shrink to a size of 0x0. This kind of environment could previously trigger layout bugs that would result in an unhandled exception from the Chart control. These issues have been fixed and dynamic layout changes are now handled seamlessly. Breaking Changes IRequireGlobalSeriesIndex's GlobalSeriesIndexChanged method takes a nullable int parameter. This should affect only people who have written custom Series implementations - and the code change is a trivial. Other Changes Many other fixes and improvements. Proper RoutedEvent support for DataPointSeries.SelectionChangedEvent Better handling of non-double data by shared Series Addition of StrokeMiterLimit to the Polyline used by LineSeries Fixes for edge case scenarios when removing a Series Ability to set Series.Title with a Binding (on Silverlight 3 and WPF) Automatic inheritance of the Foreground property by the Title control Visual improvements to the LegendItem DataPoint marker Addition of SnapsToDevicePixels to the default Chart Template (WPF-only; unnecessary on Silverlight) Build Notes If you plan to recompile the WPF Toolkit from source, please be aware that two of the three Charting design-time assemblies reference Blend 3 DLLs Microsoft.Windows.Design.Extensibility.dll and Microsoft.Windows.Design.Interaction.dll. Unlike their Visual Studio counterparts, these design-time assemblies are not automatically found by the build and their absence causes 84 build errors in the Controls.DataVisualization.Toolkit.Design and Controls.DataVisualization.Toolkit.Expression.Design projects. :( Most people won't care about building the Blend-specific design-time assemblies and can simply right-click the two failing projects in Visual Studio and choose "Unload Project". After that, everything builds successfully. Alternatively, users with Blend installed can update these projects' references to both assemblies and then everything (including the Blend design-time assemblies!) builds successfully. The default location of the Blend assemblies is something like C:\Program Files (x86)\Microsoft Expression\Blend 3 Beta. (If you're on a 32-bit OS, remove the " x86"; once Blend releases, remove the " Beta". Sorry for the inconvenience - we didn't want to ship pre-release Blend components with the WPF Toolkit.
Notable Changes
WPF is now an official platform for Charting! Today's release of the June 2009 WPF Toolkit includes the binaries for WPF Charting, the associated design-time assemblies for both Visual Studio 2008 and Blend 3, and the complete source code for everything (under the usual Ms-PL license). Prior to today, WPF Charting only existed informally because of a blog post I'd written and some bits I'd shared. As of today, that "do it yourself" approach is a thing of the past - customers can get signed binaries and ready-to-build source code for Charting as part of the WPF Toolkit. And, as always, Charting exposes the same API and supports the same XAML on both platforms - making application portability trivial!
Improved performance of internal data structures for many common scenarios. Charting now makes use of left-leaning red-black trees to maintain properly balanced data structures. For more detail on this change, please refer to my post about the LeftLeaningRedBlackTree implementation.
Numerous bug fixes for animation inconsistencies between Silverlight and WPF. Storyboards and Animations sometimes behave a little differently on Silverlight/WPF, and a good bit of effort was spent trying to ensure that Charting will behave the same way on both platforms. In most cases, this was a matter of finding an implementation both platforms agreed on - in some it meant resorting to small, localized #if blocks.
#if
Fixed handling of data objects with non-unique hash codes. When each data object had a unique hash code, things already worked fine. But data sets containing items sharing the same hash code could exhibit incorrect behavior in previous releases. Most typical data sets would not have encountered this problem because hash codes are nearly always unique - but there are certain classes that report quite UNunique hash codes and could trigger the problem fairly easily. This is no longer an issue.
Corrected behavior of charts at very small sizes and during animations. Some third party controls offer so-called "fluid" layout in which size changes are all animated and elements can easily shrink to a size of 0x0. This kind of environment could previously trigger layout bugs that would result in an unhandled exception from the Chart control. These issues have been fixed and dynamic layout changes are now handled seamlessly.
Chart
Breaking Changes
IRequireGlobalSeriesIndex's GlobalSeriesIndexChanged method takes a nullable int parameter. This should affect only people who have written custom Series implementations - and the code change is a trivial.
Other Changes
Many other fixes and improvements.
RoutedEvent
DataPointSeries.SelectionChangedEvent
double
Series
StrokeMiterLimit
Polyline
LineSeries
Series.Title
Binding
Foreground
Title
LegendItem
DataPoint
SnapsToDevicePixels
Template
Build Notes
If you plan to recompile the WPF Toolkit from source, please be aware that two of the three Charting design-time assemblies reference Blend 3 DLLs Microsoft.Windows.Design.Extensibility.dll and Microsoft.Windows.Design.Interaction.dll. Unlike their Visual Studio counterparts, these design-time assemblies are not automatically found by the build and their absence causes 84 build errors in the Controls.DataVisualization.Toolkit.Design and Controls.DataVisualization.Toolkit.Expression.Design projects. :(
Microsoft.Windows.Design.Extensibility.dll
Microsoft.Windows.Design.Interaction.dll
Controls.DataVisualization.Toolkit.Design
Controls.DataVisualization.Toolkit.Expression.Design
Most people won't care about building the Blend-specific design-time assemblies and can simply right-click the two failing projects in Visual Studio and choose "Unload Project". After that, everything builds successfully.
Alternatively, users with Blend installed can update these projects' references to both assemblies and then everything (including the Blend design-time assemblies!) builds successfully. The default location of the Blend assemblies is something like C:\Program Files (x86)\Microsoft Expression\Blend 3 Beta. (If you're on a 32-bit OS, remove the " x86"; once Blend releases, remove the " Beta".
C:\Program Files (x86)\Microsoft Expression\Blend 3 Beta
x86
Beta
Sorry for the inconvenience - we didn't want to ship pre-release Blend components with the WPF Toolkit.
Long-time readers know that I always include some new Charting samples with my release notes to showcase new features. The big feature here is WPF Charting, so I've put together a solution that contains almost all of the public Charting samples I've ever posted to my blog - now in one handy place! Naturally, the DataVisualizationDemos sample runs on both Silverlight (2 or 3!) and WPF with the exact same code and XAML. And it includes a brand new scenario called "Letter Frequency" that I wrote to help test some of the recent changes.
Here it is on WPF:
Click here to download the complete source code for the DataVisualizationDemos sample application.
(Note: To find the blog post associated with each sample, please refer to the "My posts" section of my Charting Links collection.)
Jafar and I spent most of the previous release cycle helping other teams with their deliverables, so we didn't get as nearly much time to spend on Charting as we would have liked. [Otherwise the release notes would be much longer! :) ] Fortunately, we did make the time to deliver some good fixes for this release and that should help make customers' lives a little easier. Of course, while we've done our best to make Charting as useful and problem-free as possible, there's always room for improvement...
So if you have any questions or feedback, the right places to start are the Silverlight Discussion Forum or the WPF Discussion List. Bugs and feature requests can be logged with the Silverlight Issue Tracker or the WPF Issue Tracker. Please raise issues that are clearly unique to one platform or the other in the obvious place. But for general questions and things that are common to both platforms, the Silverlight forum/list is probably a better place - just because there's more context and history there.
A big "thank you" goes out to everyone who's worked with Charting and helped to make it what it is today! Today's release and announcement are specifically directed at the WPF early-adopters who've truly gone the extra mile to use Charting. We thank you for your dedication! Also, my personal thanks go out to the WPF Toolkit team for making this possible - in particular to Samantha Durante, Vamsee Potharaju, and Alexis Roosa for their assistance and support!
PS - If you're a loyal Silverlight Charting user who's feeling a little left out right about now, I have some good news for you, too. :) The Silverlight Toolkit will also be releasing an update fairly soon. And when it does, Silverlight Charting will contain all the fixes described here, one or two others that came in too late to make this release, AND a nice little surprise that will make the wait worthwhile...
In yesterday's post, I announced the second release of the Silverlight for Windows Phone Toolkit and gave an overview of the four new controls it includes. (For a discussion of the controls in the original Windows Phone Toolkit, please see my announcement for that release.) In today's post, I want to focus on one of the new controls, ListPicker, and discuss it in detail.
ListPicker
The sample application associated with the official Windows Phone Toolkit download offers a great overview of the Windows Phone Toolkit controls, but (deliberately) doesn't get into specific detail on any of them. This post is all about details, so I've written a dedicated sample application which is the source of all the XAML snippets and screenshots below:
[Click here to download the complete source code for the ListPickerSamples application.]
Background
From my previous post:
ListPicker is the Windows Phone 7 equivalent of the ComboBox control: give it a list of items and it will show the selected one and also allowing the user to pick from a list if they want to change it. The core applications on Windows Phone 7 implement two kinds of list selection: an in-place expander for picking among five or fewer items, and a full-screen popup for picking among more. The Toolkit's ListPicker control combines both experiences into one handy package by automatically selecting the right UX based on the number of items in its list! It is a standard ItemsControl subclass with all the common elements of a Selector, so the API will be familiar to just about everyone. In fact, you can take most existing ListBoxes and convert them to ListPickers just by changing the control name in XAML!
That's the gist: ListPicker is the control of choice for selecting values in Windows Phone 7 applications. To be more explicit, it is most appropriate in "Settings"-like scenarios where the user is offered a variety of different options and it makes sense to display only the current value (with an option to show everything once the user decides to make a change). Conversely, ListPicker is not appropriate for displaying long lists of data that the user is going to scan and scroll; scenarios like the "People" or "Marketplace" applications are better served by a ListBox or the Windows Phone Toolkit's new LongListSelector.
ListBox
LongListSelector
Typical Use
The most common scenario for ListPicker looks something like this:
<StackPanel> <toolkit:ListPicker Header="Rating" ItemsSource="{Binding Ratings}" SelectedIndex="1" SelectionChanged="RatingSelectionChanged"/> <TextBlock x:Name="RatingSelection" CacheMode="BitmapCache"/> ... </StackPanel>
Which gets displayed like this (in normal and expanded forms):
As you'd expect for an ItemsControl subclass, the ItemsSource property is used to provide the list of items (see also: the Items property). And as you'd expect for a Selector-like control, the SelectionChanged event is used to signal changes and the SelectedIndex property is used to get or set the selection (see also: SelectedItem). Everything so far looks just like ListBox - the only difference is the Header property which can optionally be used to provide a simple, platform-consistent label for the ListPicker that offers additional context about the control's purpose (see also: HeaderTemplate).
Header
HeaderTemplate
Aside: The built-in ListBox control will throw an exception if you set SelectedIndex as in the example above because it tries to apply the selection before the Binding has provided the list of items. ListPicker specifically handles this common scenario so you don't have to jump through hoops to make it work. :)
SelectedIndex
Custom Templates
Displaying strings is all well and good, but sometimes it's nice to display richer content:
The first thing to do is set the ItemTemplate property as you would for ItemsControl or ListBox - that applies the specified DataTemplate to each item and formats it attractively in the usual manner. That works great, but what about ListPicker's Full mode that's used when the list has too many items? By default, the same ItemTemplate automatically applies there, too, so you may not need to do anything more! However, the Full mode UI uses the entire screen, so it's pretty common to want to specifically customize the appearance of the items for that mode. Therefore, the FullModeItemTemplate property lets you provide a different DataTemplate to be used in the Full mode scenario. Another relevant property for such cases is FullModeHeader which sets the content that's shown at the top of the full-screen "popup".
ItemsControl
Full
ItemTemplate
FullModeItemTemplate
DataTemplate
FullModeHeader
<toolkit:ListPicker Header="Spectrum" FullModeHeader="CHOOSE COLOR" ItemsSource="{Binding Rainbow}" ItemTemplate="{StaticResource RainbowTemplate}" FullModeItemTemplate="{StaticResource LargeRainbowTemplate}"/>
Threshold Overrides
By default, lists with five or fewer items expand in-place while lists with more items switch to a full-screen selection interface. This behavior matches the platform guidelines, but sometimes it might make sense to nudge the threshold one way or another (for very large or very small items, perhaps). You might even want to force a ListPicker to always use Expanded or Full mode...
Expanded
For these scenarios, there's the ItemCountThreshold property: it specifies the maximum number of items that will be displayed in Expanded mode. In addition to nudging it up or down a bit for custom scenarios, it can also be set to 0 to "always use Full mode" or a very large number to "always use Expanded mode". Granted, an application that forces Expanded mode for a list of 1000 items probably won't be easy to use - but the freedom is there to allow developers and designers to dial-in exactly the kind of experience they want.
ItemCountThreshold
<toolkit:ListPicker Header="Rating" FullModeHeader="CHOOSE RATING" ItemsSource="{Binding Ratings}" ItemCountThreshold="0"/> <toolkit:ListPicker Header="Spectrum" ItemsSource="{Binding Rainbow}" ItemTemplate="{StaticResource RainbowTemplate}" ItemCountThreshold="100"/>
Two-Way Binding
As you'd expect, ListPicker can be used with TwoWay Bindings as well. This is particularly convenient for the SelectedIndex/SelectedItem properties where it's common to want to set the initial value based on a data model (see also: MVVM) and/or when you want the model to update directly when selection changes. The corresponding XAML looks just how you'd expect:
SelectedItem
<toolkit:ListPicker Header="Network" ItemsSource="{Binding Networks}" SelectedItem="{Binding CurrentNetwork, Mode=TwoWay}"/> <StackPanel Orientation="Horizontal" Margin="{StaticResource PhoneMargin}" CacheMode="BitmapCache"> <TextBlock Text="Current Network: "/> <TextBlock Text="{Binding CurrentNetwork}"/> </StackPanel>
Tips and Tricks
At this point, I hope everyone knows how ListPicker works and has a good feel for when/where/why/how to use it. That being the case, there are a few additional things I'd like to draw attention to:
The ListPicker philosophy is that "there is always an active selection", so it makes sure to select an item when it initializes or when the list changes. This automatic selection (of the first item in most cases) causes the SelectionChanged event to fire - and that causes the application's associated event handler to run (assuming one has been registered). In practice, this "initialization-time" event catches some people by surprise - but it's the intended behavior (and folks tend to agree it's correct once they understand why it happens). Now that you know about it, maybe your development experience will be a bit easier. :)
SelectionChanged
Aside: If you want to ignore this event in code, it should be easy to detect because its SelectionChangedEventArgs.RemovedItems collection will be empty (have 0 items). And the only time that happens is when ListPicker is transitioning from an empty list to a non-empty one (e.g., on startup).
ListPicker's transitions between Normal and Expanded mode are effectively animations of the control's Height. Because Height changes cause a layout pass, they don't take place on the composition thread and therefore are more susceptible to performance issues. An easy way to mitigate this in the typical "list of items in a StackPanel" scenario is to add CacheMode=BitmapCache to the elements that appear below the ListPicker (i.e., those that are pushed down by the animation). Please refer back to the first XAML snippet for an example - this tweak allows the Silverlight layout system to animate such controls as bitmaps and that helps the animation run a bit more smoothly.
Normal
Height
Aside: If you don't want to apply BitmapCache to every control individually, an alternate approach is to wrap the affected controls in another StackPanel and set the CacheMode property on the StackPanel instead. Please see the last XAML snippet above for an example of this.
BitmapCache
StackPanel
CacheMode
If you have a long list of controls in a StackPanel inside a ScrollViewer and there's a ListPicker near the bottom using Expanded mode, that expansion does not automatically scroll the screen to keep the ListPicker completely in view. On WPF, the fix would be a simple matter of calling FrameworkElement.BringIntoView. However, Silverlight doesn't have that API and there doesn't seem to be a good general purpose way for ListPicker to find the right parent to scroll. (Although walking up the visual tree to find the first ScrollViewer is probably right in most cases, it's not a sure thing; ListPicker errs on the side of caution and doesn't try to make guesses.) In practice, the underlying issue doesn't come up very often - when it has, my suggestion has been to use the ItemCountThreshold property to force the relevant ListPicker to use Full mode (which doesn't expand, so it doesn't alter the parent's layout, so it doesn't have this problem).
ScrollViewer
Summary
ListPicker is a relatively straightforward control that should be familiar to anyone who's used the standard ListBox. But while its API may be unremarkable, its user experience is all Windows Phone 7 goodness! :)
I hope you enjoy it!
I'm happy to report that we've just published the Silverlight for Windows Phone Toolkit November 2010 release! This is the second iteration of the Windows Phone Toolkit and effectively doubles the number of controls we've created to help developers and designers build more compelling, more platform-consistent user experiences with ease. The first Windows Phone Toolkit release has been a big hit and we've seen a lot of developers using it (in both binary and source forms) to build their Windows Phone 7 applications. But while we tried to address the most pressing needs with that release, there were still a couple of prominent controls missing...
With today's update, we've tried to provide more of the fundamental controls customers have been asking for - as well as API documentation and fixes for some of the bugs people reported with the first release. Recall that the Windows Phone Toolkit is published on CodePlex under the Ms-PL open-source license so anyone who wants can look at the source code to learn how we've done things - or customize any control to suit their specific scenario. As always, if you have suggestions for things we should add or change, please search the CodePlex issue tracker and vote for the issue (or create a new one if the idea is unique). We use your input to help prioritize our efforts and ensure we're delivering the right things for the community!
What's New?
<toolkit:ListPicker Header="background"> <sys:String>dark</sys:String> <sys:String>light</sys:String> <sys:String>dazzle</sys:String> </toolkit:ListPicker>
ListPicker is the Windows Phone 7 equivalent of the ComboBox control: give it a list of items and it will show the selected one and also allow the user to pick from a list if they want to change it. The core applications on Windows Phone 7 implement two kinds of list selection: an in-place expander for picking among five or fewer items, and a full-screen popup for picking among more. The Toolkit's ListPicker control combines both experiences into one handy package by automatically selecting the right UX based on the number of items in its list! It is a standard ItemsControl subclass with all the common elements of a Selector, so the API will be familiar to just about everyone. In fact, you can take most existing ListBoxes and convert them to ListPickers just by changing the control name in XAML!
<toolkit:LongListSelector ItemsSource="{StaticResource movies}" ListHeaderTemplate="{StaticResource movieListHeader}" GroupHeaderTemplate="{StaticResource movieGroupHeader}" GroupFooterTemplate="{StaticResource movieGroupFooter}" GroupItemTemplate="{StaticResource groupItemHeader}" ItemTemplate="{StaticResource movieItemTemplate}"> </toolkit:LongListSelector>
While ListPicker is about simple selection scenarios, LongListSelector is about advanced ones! Think of it as ListBox++--, it has everything you expect from ListBox plus a bunch of advanced capabilities and great on-device performance minus the levels of abstraction and generality that tend to slow ListBox down. LongListSelector supports full data and UI virtualization, flat lists, grouped lists (with headers!), and also implements the "jump list" header navigation UI that makes the "People" app so efficient! The theory behind LongListSelector is that it should be easy to fix a poorly-performing ListBox scenario by swapping in a LongListSelector instead: it handles all the tricky parts for you, so there's less to worry about and it "just works". Unless you've spent a lot of time fine-tuning your application's list behavior, you should see improved performance by switching to LongListSelector!
TransitionFrame (and Transitions)
RootFrame = new TransitionFrame();
<toolkit:TransitionService.NavigationInTransition> <toolkit:NavigationInTransition> <toolkit:NavigationInTransition.Backward> <toolkit:TurnstileTransition Mode="BackwardIn"/> </toolkit:NavigationInTransition.Backward> <toolkit:NavigationInTransition.Forward> <toolkit:TurnstileTransition Mode="ForwardIn"/> </toolkit:NavigationInTransition.Forward> </toolkit:NavigationInTransition> </toolkit:TransitionService.NavigationInTransition> <toolkit:TransitionService.NavigationOutTransition> <toolkit:NavigationOutTransition> <toolkit:NavigationOutTransition.Backward> <toolkit:TurnstileTransition Mode="BackwardOut"/> </toolkit:NavigationOutTransition.Backward> <toolkit:NavigationOutTransition.Forward> <toolkit:TurnstileTransition Mode="ForwardOut"/> </toolkit:NavigationOutTransition.Forward> </toolkit:NavigationOutTransition> </toolkit:TransitionService.NavigationOutTransition>
The Windows Phone UI Guidelines encourage smooth, attractive page-to-page animations like the core applications show, but there has been little platform support for creating similar experiences in your own applications - until now! :) The new transition classes in the Windows Phone Toolkit aims to make it easy for application developers to add attractive, platform-consistent transitions to their applications. All that's necessary to enable transitions for an application's pages is tweak App.xaml.cs (shown above) and add a bit of XAML to each page to specify its transitions (also shown above). Everything else is done for you!
App.xaml.cs
Aside: This release includes support for multiple flavors of the following transitions: turnstile, slide, rotate, swivel, and roll. It's also possible to implement custom transitions using the same framework!
AutoCompleteBox
<toolkit:AutoCompleteBox ItemsSource="{StaticResource words}"/>
AutoCompleteBox first appeared in the Silverlight 2 Toolkit, then later graduated to the Silverlight 3 SDK. Now it's back in the Toolkit - this time for Windows Phone 7! Because phones are about being small and quick to use, simplifying tedious tasks like text input is an important goal. Toward that end, a number of the core applications (like Bing search) make use of auto-completion to predict what the user is typing and save time by allowing them to click on the completed word instead. AutoCompleteBox makes it easy to bring the same convenience to your own applications by taking advantage of a phone-friendly implementation of this popular Silverlight control. By providing a suitable completion list (in any of a variety of ways), users are automatically prompted with the relevant matches as they start typing!
API Documentation
The source code for the Windows Phone Toolkit has included full XML Documentation Comments from the beginning, but now we've begun generating a separate CHM file with all the property/method/event comments in a single, easy-to-browse location. The documentation file is automatically installed by the Windows Phone Toolkit installer and a handy link is added to the "Silverlight for Windows Phone Toolkit" folder in the Start Menu. Because we don't have dedicated documentation writers on the Toolkit team, our documentation is a bit on the terse side - but the CHM file is still a great way to survey the Toolkit classes and get a feel for what's available. And because the sample syntax is available in both C# and VB, everyone should be comfortable with the examples!
Bug fixes for existing controls
The previous Toolkit release wasn't all that long ago and we've been quite busy since then, but we've still managed to squeeze in fixes for some of the more annoying bugs customers reported. That's not to say that we fixed them all or that we had a chance to squash your favorite bug, but we were fortunate to be able to fix a good number of customer-impacting issues and I hope everyone finds the new controls even more pleasant to use! :)
[Click here to download the Silverlight for Windows Phone Toolkit November 2010 release.]
The screen shots and XAML shown above are all from the sample application you can download along with the Toolkit. I encourage people to play around with the sample if they want to experiment with any of these controls (in the emulator or on the device) or if they want to learn more about how these controls work. I anticipate more in-depth coverage will show up in the coming weeks (I will be posting a detailed ListPicker overview tomorrow!), but for now the sample application is a great springboard to get folks started!
In closing, I'm really glad we've been able to get this second round of controls out to the community in such a short time! While there are probably still some "nice to have" controls that could be added to further round-out the Windows Phone 7 control offering, I think that what we've included in the Windows Phone Toolkit represents nearly all the critical ones needed to unblock important scenarios. I hope people continue to enjoy their Windows Phone development experience and that the new Windows Phone Toolkit makes application development even easier! :)
Silverlight 2 Beta 1 is available today, yay!! I'm celebrating by publishing an FAQ of sorts for the controls I worked on. While not all of the topics below are "frequently asked questions" (it's a little too soon for that...), they're all intended to help developers get started by covering the basics of these controls.
If you're already familiar with WPF, then Silverlight should come pretty easily to you - but I'd still recommend skimming the topics below because some of the details are unique to Silverlight. If you're new to WPF and Silverlight, then I hope the topics below help jump-start the development process. In either case, I welcome your feedback, so please leave a comment below or contact me with any questions/problems/ideas you have!
Enjoy!!
General Questions
What does this FAQ cover? The Silverlight 2 Beta 1 controls I worked on: ListBox, ListBoxItem, ScrollViewer, and ScrollContentPresenter.
Why do all the hyperlinks point to WPF documentation? For one, that's all that was publically available when I started writing this post. For another, the WPF documentation is a superset of the Silverlight documentation and the additional detail there can help clarify issues. And finally, because one of the big goals for the Silverlight controls was complete (subset) compatibility with their WPF counterparts, the WPF documentation has been MY primary reference as well! But sometimes there's no substitute for the real thing; the Silverlight 2 Beta 1 documentation is a great reference, too. :)
What's the meaning of the WPF/WPFIMPLEMENTATION defines in the controls source code? The development of ListBox (+ListBoxItem) and ScrollViewer (+ScrollContentPresenter) was done in parallel with the development of their base types (e.g., ItemsControl, ContentControl) and also in parallel with the development of the Silverlight 2 platform itself. To minimize the risk/impact of developing on a changing foundation, I did much of my development and unit testing on WPF by deriving from the corresponding base classes, using only the subset of WPF that Silverlight exposes, and avoiding features specific to either platform as much as possible.
When compiling and running on WPF, I would add "WPF" to the list of conditional compilation symbols for the project. Therefore, code inside an #if WPF block applies only when running on WPF. In many cases, the pattern is #if WPF ... #else ... #endif and this corresponds to instances where some bit of code needed to be different between the two platforms (typically because it used features that weren't identical across both). In some cases, the relevant code only applies to one platform and the #else is missing or #if !WPF is used instead.
#if WPF
#if WPF ... #else ... #endif
#else
#if !WPF
The meaning of "WPFIMPLEMENTATION" is similar and is used by the unit tests for code that applies only when testing the WPF implementations of ListBox or ScrollViewer. I used unit tests in three different scenarios to help ensure the code was as correct and compatible as possible: testing the Silverlight implementation on Silverlight, testing the Silverlight implementation on WPF (#if WPF), and testing the WPF implementation on WPF (#if WPF, #if WPFIMPLEMENTATION). Though it may seem silly at first, the point of testing the WPF implementation on WPF was to make sure the unit tests were validating the correct behavior.
#if WPF, #if WPFIMPLEMENTATION
What's a good strategy for investigating possible bugs? If you're trying to do something new and running into behavior that seems wrong, it's often helpful to identify exactly what/where the problem is. My first step is often to try the same scenario on WPF - if the behavior is the same there, then it's probably not a bug (and at least Silverlight is consistent!). But let's say the scenario works fine on WPF - my next step for ListBox problems is to try the same scenario using ItemsControl. This isn't always possible because ItemsControl offers only a subset of ListBox's functionality, but if I'm able to reproduce the problem using just ItemsControl, then ListBox is off the hook and that's a big chunk of code that's no longer in question. If ItemsControl isn't responsible, the next step I'll take is to switch to a debug version of System.Windows.Controls.dll so I can set breakpoints, tweak the control internals, etc.. For bugs in ListBox or ScrollViewer, this should be enough to identify most problems; for bugs in Silverlight, this helps to narrow things down.
System.Windows.Controls.dll
And now that you've identified a bug, please let us know so we can fix it! :)
Common ListBox Scenarios
Here is the XAML code for a handful of the most common ListBox scenarios along with a picture of what it looks like on Silverlight.
ListBoxItems specified in XAML:
<ListBox> <ListBoxItem Content="ListBoxItem 0"/> <ListBoxItem Content="ListBoxItem 1"/> <ListBoxItem Content="ListBoxItem 2"/> </ListBox>
FrameworkElements specified in XAML:
<ListBox> <Ellipse Width="20" Height="20" Fill="Red" Stroke="Black"/> <Rectangle Width="20" Height="20" Fill="Blue" Stroke="White"/> <Rectangle Width="20" Height="20" Fill="Yellow" Stroke="Black" RadiusX="5" RadiusY="5"/> </ListBox>
Objects specified with ItemsSource and StaticResource Binding:
<ListBox ItemsSource="{StaticResource Products}"/>
Horizontal layout with ItemsPanel and ItemsPanelTemplate:
<ListBox> <ListBox.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <ListBoxItem Content="Item 0"/> <ListBoxItem Content="Item 1"/> <ListBoxItem Content="Item 2"/> </ListBox>
Simple data visualization with DisplayMemberPath:
<ListBox ItemsSource="{StaticResource Products}" DisplayMemberPath="Name"/>
Complex data visualization with ItemTemplate and DataTemplate:
<ListBox ItemsSource="{StaticResource Products}"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Source="{Binding Image}"/> <TextBlock Text="{Binding Name}" VerticalAlignment="Center"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>
Customized ListBoxItem appearance with ItemContainerStyle and Style:
<ListBox ItemsSource="{StaticResource Products}" DisplayMemberPath="Name"> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="Background" Value="Gray"/> <Setter Property="Foreground" Value="White"/> </Style> </ListBox.ItemContainerStyle> </ListBox>
Minimally templated ListBox and ListBoxItem with Template and ControlTemplate:
<ListBox ItemsSource="{StaticResource Products}" DisplayMemberPath="Name"> <ListBox.Template> <ControlTemplate TargetType="ListBox"> <Border BorderBrush="Blue" BorderThickness="2" CornerRadius="5"> <ItemsPresenter/> </Border> </ControlTemplate> </ListBox.Template> <ListBox.ItemContainerStyle> <Style TargetType="ListBoxItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <Border BorderBrush="HotPink" BorderThickness="2" CornerRadius="3"> <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" FontWeight="Bold"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.ItemContainerStyle> </ListBox>
Note: For the samples that refer to {StaticResource Products}, the following code/XAML is assumed to be present:
{StaticResource Products}
namespace LB_SV_FAQ { public class Products : List<Product> { public Products() { Add(new Product { Name = "Calculator" }); Add(new Product { Name = "Notepad" }); Add(new Product { Name = "Paint" }); } } public class Product { public string Name { get; set; } public string Image { get { return Name + ".png"; } } public override string ToString() { return "Product: " + Name; } } }
<UserControl x:Class="LB_SV_FAQ.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:LB_SV_FAQ"> <Grid> <Grid.Resources> <local:Products x:Key="Products"/> </Grid.Resources> ... </Grid> </UserControl>
ListBox Implementation Notes
Why is SelectionMode.Single the only supported SelectionMode? The Silverlight controls are subsets of their WPF counterparts in order to keep complexity and download size as low as possible. In cases where it seemed that specific functionality was not widely used, we opted to exclude it for Beta 1 and use customer feedback to identify which missing features are the most important. SelectionMode.Multiple and SelectionMode.Extended both fall into the category of "very nice to have, but seemingly not critical for most scenarios" - if you need multiple selection support, please let us know!
Why doesn't ListBox derive from Selector? On WPF, ListBox derives from the Selector class which derives from ItemsControl; on Silverlight, ListBox derives directly from ItemsControl. To simplify matters and keep development/testing/download costs down, the Selector class was thought to be unnecessary for Silverlight 2 Beta 1. With ListBox being the only Selector-derived class in the Beta 1 controls offering, the Selector functionality was merged into ListBox for compactness. (If a Selector class is introduced to the Silverlight controls in the future, it should have little/no effect on existing ListBox code.)
Why does the ListBoxItem.ChangeVisualState method overlap playing the old/new Storyboards? The pattern of changing visual states by calling Begin for the new Storyboard and then calling Stop for the old one is a key part of the Silverlight state model. Starting the new Storyboard begins applying the values of whatever animations it represents (as you'd expect). Stopping the old one then automatically resets any animations it represents that aren't also being changed by the new Storyboard. Manipulating the two Storyboards in this particular manner is necessary for the correct visual behavior under Silverlight.
ListBox Curiosities
Why is the scrolling behavior slightly different than with WPF? If you have a ListBox with enough items that a vertical ScrollBar is displayed, using the Page Up/Down or arrow keys to scroll through the list behaves just a little bit differently on Silverlight than on WPF. In particular, WPF's default behavior is to keep the top-most item fully visible and flush against the top of the ListBox at all times. Therefore, scrolling doesn't affect the items' vertical placement until the very beginning/end of the list. Silverlight does not have the same behavior with respect to the top-most item, so the item placement tends to change slightly while scrolling through the list. This behavior difference is due to the fact that the WPF ListBox's default Style overrides the default ScrollViewer.CanContentScroll value of False and sets it to True, changing the ScrollViewer's scrolling mode from physical scrolling (pixel-based) to logical scrolling (item-based). Silverlight's ScrollViewer supports only physical scrolling, so this override is not present on Silverlight. To see the same Silverlight scrolling behavior on WPF, set ScrollViewer.CanContentScroll="False" on any ListBox with ScrollBars.
ScrollViewer.CanContentScroll="False"
Why is selection behavior incorrect when I have the same object in a ListBox multiple times? For compatibility with WPF, of course! :) Due to the way ItemsControl is implemented, it is necessary for it to maintain a mapping from the objects it contains to the corresponding wrappers (i.e., ListBoxItems) that get created for them. This is handled by the ItemContainerGenerator.ContainerFromItem method on WPF and by ListBox.GetListBoxItemForObject on Silverlight - but the idea is the same. When the same object reference is added to the ListBox multiple times, this mapping breaks down because it isn't prepared for a single object to map to multiple containers. As it happens, the WPF and Silverlight behaviors differ slightly here. Both seem wrong, but I'll suggest that the Silverlight behavior is slightly less wrong. The following XAML/code demonstrates the problem on WPF and Silverlight (click on the third item, then the second item to see the behavior difference):
<ListBox x:Name="lb"/>
object obj = new object(); lb.Items.Add(obj); lb.Items.Add(obj); lb.Items.Add(obj);
Note: The problematic object-to-ListBoxItem mapping is unnecessary when the items added to the ListBox are of type ListBoxItem. Therefore, if you need to store the same object multiple times, consider wrapping your items in unique ListBoxItem instances (setting the Content property to the wrapped item) before adding them to the ListBox.
ListBox Bugs
Why doesn't setting ListBox's SelectedIndex in XAML or the Page constructor seem to work? The internal ListBox state is actually correct in the code scenario (and unit tests verify this), but the visual representation is wrong. The following XAML/code demonstrates the problem:
<ListBox x:Name="lb"> <TextBlock Text="one"/> <TextBlock Text="two"/> <TextBlock Text="three"/> </ListBox>
public Page() { InitializeComponent(); lb.SelectedIndex = 1; }
The problem is that when the SelectedIndex property gets set in the Page constructor, ItemsControl has not yet called PrepareContainerForItemOverride and so ListBox has not yet set up its mapping from object to ListBoxItem (see the "ListBox Curiosities" section above for more about this mapping). So when ListBox tries to toggle IsSelected on the corresponding ListBoxItem, no such ListBoxItem exists and ListBox gives up instead of making a note to come back and finish this task after the mapping has been established. (Unfortunately, the situation is even worse if SelectedIndex is set in XAML (vs. code) because at that time it is typically the case that ItemsControl's Items collection is still empty.)
The workaround is to call something like the SelectedIndexWorkaround extension method (defined below) after setting SelectedIndex. SelectedIndexWorkaround defers setting SelectedIndex until it will have the desired effect - and thereby works around the bug. The change to the above code is:
lb.SelectedIndex = 1; lb.SelectedIndexWorkaround(); // Added workaround
Why doesn't setting IsSelected True before adding a ListBoxItem to the ListBox look right? Again the internal ListBox state is correct, but the visual representation is wrong. In this case, the visuals fix themselves if you move the mouse over the selected item and then off it because the ListBoxItem transitions from its hover appearance to its selected appearance and the visuals update correctly. The problem here is that when IsSelected is toggled, ListBoxItem plays its state transition Storyboard immediately - but the ListBoxItem is not in the visual tree yet so Silverlight doesn't actually apply the associated state changes. (To behave correctly in this scenario, ListBoxItem should be calling its ChangeVisualState method at the end of OnApplyTemplate and also in a handler for the Loaded event.) The following code demonstrates the problem:
ListBoxItem lbi; lbi = new ListBoxItem(); lbi.Content = "Unselected"; lb.Items.Add(lbi); lbi = new ListBoxItem(); lbi.Content = "Selected"; lbi.IsSelected = true; lb.Items.Add(lbi);
The workaround is to call something like the IsSelectedWorkaround extension method (defined below) after setting IsSelected. This method causes the appropriate Storyboard to play again once it will have the desired effect - and thereby works around the bug. The change to the above code:
lbi.IsSelected = true; lbi.IsSelectedWorkaround(); // Added workaround
Why can't I arrow up/down past a ListBox item with Visibility Collapsed? Because ListBox doesn't expect there to be "invisible" ListBoxItems. If you try the obvious workaround of leaving the ListBoxItem Visible and marking its Content Collapsed, arrow navigation no longer gets "stuck" - but the scenario is still confusing to the user because the ListBox lets focus get set to the "invisible" item. Rather than playing with Visibility, consider the more direct approach of removing items from the collection if you don't want them to be visible. An elegant approach is to use an ObservableCollection for storing items and then assign that collection to the ItemsSource property of the ListBox. Because ObservableCollection implements INotifyCollectionChanged, changes to it (e.g., via Add/Remove) are automatically communicated to ListBox's ItemsControl base class which automatically updates the ListBox display. All you need to do is keep the collection up to date - everything else is handled for you.
ListBox Workaround Code
The following C# static class implementation can be added to a project to introduce extension methods to help work around the bugs described above. Simply call the corresponding extension method after setting one of the associated properties and the proper behavior should occur. Please note that calling these methods is not typically necessary and should be done only if a scenario is affected by one of the above bugs.
Sorry for the trouble!
/// <summary> /// This class contains extensions to help work around two ListBox bugs in /// the Silverlight 2 Beta 1 controls release. Simply add it to your project /// and call the extension methods just after the properties they match. /// </summary> public static class ListBoxExtensions { /// <summary> /// Augments ListBox.SelectedIndex to work around a bug where setting /// SelectedIndex before the ListBox is visible does not update the UI. /// </summary> /// <remarks> /// Instead of: /// listBox.SelectedIndex = 1; /// Use: /// listBox.SelectedIndex = 1; /// listBox.SelectedIndexWorkaround(); /// </remarks> /// <param name="listBox">Extension method class.</param> public static void SelectedIndexWorkaround(this ListBox listBox) { int selectedIndex = listBox.SelectedIndex; bool set = false; listBox.LayoutUpdated += delegate { if (!set) { // Toggle value to force the change listBox.SelectedIndex = -1; listBox.SelectedIndex = selectedIndex; set = true; } }; } /// <summary> /// Augments ListBoxItem.IsSelected to work around a bug where setting /// IsSelected before the ListBoxItem is visible does not update the UI. /// </summary> /// <remarks> /// Instead of: /// listBoxItem.IsSelected = true; /// Use: /// listBoxItem.IsSelected = true; /// listBoxItem.IsSelectedWorkaround(); /// </remarks> /// <param name="listBoxItem">Extension method class.</param> public static void IsSelectedWorkaround(this ListBoxItem listBoxItem) { bool isSelected = listBoxItem.IsSelected; bool set = false; listBoxItem.LayoutUpdated += delegate { if (!set) { // Toggle value to force the change listBoxItem.IsSelected = !isSelected; listBoxItem.IsSelected = isSelected; set = true; } }; } }
Common ScrollViewer Scenarios
Using ScrollViewer to scroll content that is too large:
<!-- Grid used to limit ScrollViewer size --> <Grid Width="150" Height="70"> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <TextBlock Text="ScrollViewer" FontFamily="Arial Black" FontSize="50"/> </ScrollViewer> </Grid>
Using ScrollViewer to add ScrollBars to an application when the browser is resized:
<UserControl x:Class="ResizableApplication.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Grid> <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <Grid> <!-- Page content goes here instead of the outer Grid --> </Grid> </ScrollViewer> </Grid> </UserControl>
ScrollViewer Implementation Notes
Why am I having a hard time specifying ScrollViewer's attached properties in XAML? Silverlight 2 Beta 1's XAML parser throws a XamlParseException (ex: "Unknown attribute ScrollViewer.VerticalScrollBarVisibility on element ListBox.") when trying to specify any control's attached properties on a different control in XAML. This problem should be fixed in a future release; for now there's an easy workaround: add an explicit xmlns definition for System.Windows.Controls and use that xmlns when setting the attached properties:
<UserControl x:Class="LB_SV_FAQ.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"> <Grid> <ListBox controls:ScrollViewer.VerticalScrollBarVisibility="Hidden"/> </Grid> </UserControl>
Why doesn't ScrollViewer support the ScrollViewer.CanContentScroll property? See the "ListBox Implementation Notes" section above for an explanation of how Silverlight controls try to be a minimal subset of core functionality and the "ListBox Curiosities" section for an explanation of how the lack of ScrollViewer.CanContentScroll affects ListBox.
Why is there no ScrollChanged event or IScrollInfo interface? As with ScrollViewer.CanContentScroll, these were deliberate omissions intended to keep things simple and compact.
ScrollViewer Curiosities
Why does the ScrollViewer default to HorizontalScrollBarVisibility Disabled and VerticalScrollBarVisibility Visible? For compatibility with the WPF defaults, of course. :) Regarding the obvious next question about why those values are the WPF defaults, I don't know. Auto/Auto would seem more generally useful to me and, in fact, the WPF ListBox (and Silverlight ListBox) overrides the ScrollViewer defaults to set Auto/Auto! But maintaining compatibility wins, so the Silverlight ScrollViewer defaults to the same values used by WPF.
During his keynote at the MIX07 conference in Las Vegas, Scott Guthrie showed off some of the power of Microsoft's recently announced Silverlight platform. In particular, the ability to easily run managed code in the web browser - coupled with a powerful rendering engine - seems like it will to radically change the landscape of the web. My team had the privilege of writing the Silverlight Airlines demonstration he used to show off just how easy it is to develop with Silverlight. Demoed on stage, it looked something like the following image:
Now that your appetite is whetted, go ahead and click here (or on the image above) to view the demo in your browser with the Silverlight 1.1 Alpha thanks to the seamless cross-browser, cross-platform support that Silverlight provides.
When our manager Shawn Burke asked us to put this demo together, my coworker Ted Glaza and I had practically no experience with Silverlight or WPF. So we spent about a week playing around with the technology to learn how it worked. By that time, the general concept of the demo was fairly well established and we spent time the next week developing the foundations of the application. Before long, we had a working demo that we showed off to Scott. The third week was spent incorporating visual feedback from a designer and adding some finishing touches. For those of you keeping track at home, that's one cool app written by two developers in just three weeks - beginning with nothing and building on top of a platform that was still being developed - pretty compelling, I think!
In the spirit of openness and learning, you can click here to download the complete source code for this demo application and play around with it yourself!
A few notes on the application:
The Silverlight Airlines demo was a fun project to work on - I look forward to be seeing (and doing!) a lot more with Silverlight in the coming days!
PS - Since the keynote presentation, there has been some interest in reusing the calendar control. Beyond making the source code available here, I may write a follow-up blog post going into more detail about the calendar itself. As a quick teaser: it's capable of more sophisticated display than what's in the demo application. :)
PPS - If you watched the Scott Guthrie keynote, you probably saw that there was a time Scott tried to switch the display screen over to the Mac to demonstrate something and it took a while for the conference's A/V folks to actually make the switch. A couple of people have asked if this delay was due to a problem with the Mac or Silverlight. I happen to have been sitting backstage mere inches away from the Mac in question (conferences use KVM switches to keep all the demo machines backstage) and I can assure you that there was no technical issue with the Mac or Silverlight. Aside from the conference tech taking a little while to switch the display over, there were no technical glitches during the demo. :)
A customer recently asked how to implement a simple "grouped ListBox" experience in Silverlight (now available in desktop, mobile, and extra crispy flavor!), so I dashed off this sample to show one way that's pretty easy to work with.
Well, actually, the first thing I did was ask if DataGrid (which supports grouping natively) was an option - but the customer felt strongly about using a ListBox, so here we are...
The core of my solution is a custom IValueConverter implementation. If you've read my blog much, you were probably expecting that because I tend to be a pretty big fan. As usual, IValueConverter is convenient because it allows us to easily transform the source data into something that looks how we want without needing to modify the actual data source or values. In fact, the rest of the application doesn't really need to know what's going on - this is a (mostly) UI-only solution.
IValueConverter
Okay, not needing to modify the original data is a nice advantage. What else would we like to see in a good solution? Well, it would be nice if it were easy to customize the appearance of items and their group headers without writing any code. And it would be nice if the grouping logic were flexible enough to allow grouping on any criteria (ex: value of a property, value ranges, first letter of name, etc.). And of course we want the designer to have the flexibility to hook everything up in XAML.
That seems like a pretty reasonable list of requirements - and we can handle them all without a problem. But first, let's see it in action:
On the left is the original data in a simple ItemsControl, in the center is a grouped version of that same data (just like we wanted!), and on the right is the same grouping of that data - this time a little more fancy and in a ListBox so the items are selectable! The XAML for the middle example looks like this:
<Grid.Resources> <delay:GroupingItemsControlConverter x:Key="GroupingItemsControlConverter"/> <delay:GroupingItemsControlConverterParameters x:Key="SimpleGroupingItemsControlConverterParameter"> <delay:GroupingItemsControlConverterParameters.GroupSelector> <local:AnimalSpeciesGroupSelector/> </delay:GroupingItemsControlConverterParameters.GroupSelector> <delay:GroupingItemsControlConverterParameters.GroupHeaderTemplate> <DataTemplate> <ContentControl Content="{Binding}" FontWeight="Bold"/> </DataTemplate> </delay:GroupingItemsControlConverterParameters.GroupHeaderTemplate> <delay:GroupingItemsControlConverterParameters.ItemTemplate> <DataTemplate> <ContentControl Content="{Binding Name}" Padding="8 0 0 0"/> </DataTemplate> </delay:GroupingItemsControlConverterParameters.ItemTemplate> </delay:GroupingItemsControlConverterParameters> </Grid.Resources> <!-- ... --> <ItemsControl ItemsSource="{Binding Converter={StaticResource GroupingItemsControlConverter}, ConverterParameter={StaticResource SimpleGroupingItemsControlConverterParameter}}"/>
[Click here to download the complete source code for the GroupingItemsControlConverter sample as a Silverlight 4 Visual Studio 2010 solution.]
How does it work? Fairly simply, actually! Once parameters have been validated, the GroupingItemsControlConverter class makes a call to Linq's GroupBy extension method using the custom grouping method specified and follows with a call to the OrderBy extension method. The results are then output as a sequence of ContentControl instances with a custom DataTemplate applied according to whether each thing is a group header or an item. This pattern should seem pretty familiar; it's the standard ItemsControl model mixed together with something kind of like implicit DataTemplates. The GroupingItemsControlConverterParameters class lets you specify the GroupHeaderTemplate, the ItemTemplate, and an class implementing the IGroupingItemsControlConverterSelector interface. And don't worry, the custom implementation of that interface is quite trivial - here's what the sample application uses:
GroupingItemsControlConverter
GroupingItemsControlConverterParameters
GroupHeaderTemplate
IGroupingItemsControlConverterSelector
// Simple IGroupingItemsControlConverterSelector implementation for grouping by an Animal's species public class AnimalSpeciesGroupSelector : IGroupingItemsControlConverterSelector { public Func<object, IComparable> GetGroupSelector() { return (o) => ((Animal)o).Species; } }
As you can see, the simple example really is pretty simple. :) The fancier example on the right is very similar, except that it uses a bit more XAML to get that "black is the new white" effect that's becoming so popular lately. And it makes use of my SetterValueBindingHelper implementation which adds support for specifying a Binding in the Value of a Setter on Silverlight to bind the ListBoxItem's IsEnabled property to another simple IValueConverter to disable the headers so they can't be clicked on or selected.
Aside: Yes, I know that the very top group header is selectable when using the keyboard on current Silverlight bits. No, it's not my bug. Yes, I already reported it to the relevant people. :)
Those of you familiar with my blog may be wondering why I haven't mentioned that everything here works on WPF, too... Okay, fine, I fully expect that what I've done here will work exactly the same on WPF as it does on Silverlight. :) However, WPF's support of additional features like implicit DataTemplates means that I'd probably implement this solution a little differently on WPF. If you're itching to use this code as-is on WPF, go right ahead; I don't anticipate any problems with that. But if you do, maybe spend just a bit of time thinking about how you would do things differently on WPF...
Here's the complete implementation of GroupingItemsControlConverter and its helper classes for those who are interested:
/// <summary> /// Class that implements simple grouping for ItemsControl and its subclasses (ex: ListBox) /// </summary> public class GroupingItemsControlConverter : IValueConverter { /// <summary> /// Modifies the source data before passing it to the target for display in the UI. /// </summary> /// <param name="value">The source data being passed to the target.</param> /// <param name="targetType">The Type of data expected by the target dependency property.</param> /// <param name="parameter">An optional parameter to be used in the converter logic.</param> /// <param name="culture">The culture of the conversion.</param> /// <returns>The value to be passed to the target dependency property.</returns> public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { // Validate parameters var valueAsIEnumerable = value as IEnumerable; if(null == valueAsIEnumerable) { throw new ArgumentException("GroupingItemsControlConverter works for only IEnumerable inputs.", "value"); } var parameterAsGroupingItemsControlConverterParameter = parameter as GroupingItemsControlConverterParameters; if (null == parameterAsGroupingItemsControlConverterParameter) { throw new ArgumentException("Missing required GroupingItemsControlConverterParameter.", "parameter"); } var groupSelectorAsIGroupingItemsControlConverterSelector = parameterAsGroupingItemsControlConverterParameter.GroupSelector as IGroupingItemsControlConverterSelector; if (null == groupSelectorAsIGroupingItemsControlConverterSelector) { throw new ArgumentException( "GroupingItemsControlConverterParameter.GroupSelector must be non-null and implement IGroupingItemsControlConverterSelector.", "parameter"); } // Return the grouped results return ConvertAndGroupSequence(valueAsIEnumerable.Cast<object>(), parameterAsGroupingItemsControlConverterParameter); } /// <summary> /// Converts and groups the values of the specified sequence according to the settings of the specified parameters. /// </summary> /// <param name="sequence">Sequence of items.</param> /// <param name="parameters">Parameters for the grouping operation.</param> /// <returns>Converted and grouped sequence.</returns> private IEnumerable<object> ConvertAndGroupSequence(IEnumerable<object> sequence, GroupingItemsControlConverterParameters parameters) { // Validate parameters var groupSelector = ((IGroupingItemsControlConverterSelector)(parameters.GroupSelector)).GetGroupSelector(); if (null == groupSelector) { throw new NotSupportedException("IGroupingItemsControlConverterSelector.GetGroupSelector must return a non-null value."); } // Do the grouping and ordering var groupedOrderedSequence = sequence.GroupBy(groupSelector).OrderBy(g => g.Key); // Return the wrapped results foreach (var group in groupedOrderedSequence) { yield return new ContentControl { Content = group.Key, ContentTemplate = parameters.GroupHeaderTemplate }; foreach (var item in group) { yield return new ContentControl { Content = item, ContentTemplate = parameters.ItemTemplate }; } } } /// <summary> /// Modifies the target data before passing it to the source object. This method is called only in TwoWay bindings. /// </summary> /// <param name="value">The target data being passed to the source.</param> /// <param name="targetType">The Type of data expected by the source object.</param> /// <param name="parameter">An optional parameter to be used in the converter logic.</param> /// <param name="culture">The culture of the conversion.</param> /// <returns>The value to be passed to the source object.</returns> public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException("GroupingItemsControlConverter does not support ConvertBack."); } } /// <summary> /// Class that represents the input parameters to the GroupingItemsControlConverter class. /// </summary> public class GroupingItemsControlConverterParameters { /// <summary> /// Template to use for the header for a group. /// </summary> public DataTemplate GroupHeaderTemplate { get; set; } /// <summary> /// Template to use for the items of a group. /// </summary> public DataTemplate ItemTemplate { get; set; } /// <summary> /// Selector to use for determining the grouping of the sequence. /// </summary> public IGroupingItemsControlConverterSelector GroupSelector { get; set; } } /// <summary> /// Interface for classes to be used as a selector for the GroupingItemsControlConverterParameters class. /// </summary> public interface IGroupingItemsControlConverterSelector { /// <summary> /// Function that returns the group selector. /// </summary> /// <returns>Key to use for grouping.</returns> Func<object, IComparable> GetGroupSelector(); }
One of the more frequent requests we've gotten for the "Atlas" Control Toolkit is some way to easily alter the content of a popup before displaying it to the user. Typically, page authors want to have a single popup element on their page that is used from multiple locations (often the rows of a data-bound server control) and they want the popup to display information specific to each location that displays it. For example, a page displaying multiple products might want a popup associated with each product to display additional details for that particular product.
There was previously no easy way to accomplish this task, but with the 60914 release of the Toolkit, we've made it simple! The HoverMenu, ModalPopup, and PopupControl now all derive from new DynamicPopulate*Base classes (see the "Other neat stuff" page for details) and automatically expose four new properties: DynamicControlID, DynamicContextKey, DynamicServicePath, and DynamicServiceMethod. These properties behave just like they do for the DynamicPopulate control (please follow that link for more details) and the existing controls have been modified to call the "populate" function as part of their popup actions.
What this means for page authors is that it's simple to add dynamic population to new/existing pages - what it means for control authors is that it's simple to add dynamic population to new/existing controls!
Here's what dynamic population looks like in action (I've arbitrarily chosen ModalPopup for the demonstration):
The complete code for the sample follows; noteworthy sections have been bolded to make them stand out. The important things to note are:
That's all there is to it!
Just save the following code to an .ASPX file in the SampleWebSite directory of a Toolkit installation if you want to try it out for yourself:
If you found this "How to" helpful or if you have any suggestions for ways in which future "How to"'s could be improved, please let me know. Thank you!!
Since getting involved with Silverlight and finding out the XPS document type WPF enables has XAML at its core, I've been wondering how Silverlight would do as a lightweight XPS viewer.
First, a bit of background: WPF is the Windows Presentation Foundation and represents a new approach to UI for Windows. XPS refers to the XML Paper Specification, a device-independent file format for flexible document representation (think PDF) that's part of Office 2007 and .NET 3.0. WPF offers rich support for displaying XPS documents via its DocumentViewer and XpsDocument classes (among others). Because the 1.1 Alpha release doesn't currently include the relevant classes, Silverlight wouldn't appear to be well suited for XPS document display at first glance...
However, Silverlight does have the Downloader class which includes support for packages (for the purposes of this discussion, packages are basically just ZIP archives). Since an XPS document is really just a package, and the core document format XPS uses is XAML, and Silverlight speaks XAML (well, at least a subset of it!), maybe it's not such a stretch to do XPS with Silverlight after all.
I thought it would be a neat exercise to try to write an XPS viewer with the publically available Silverlight 1.1 Alpha bits so I gave it a try and ended up with an application I call SimpleSilverlightXpsViewer:
Go ahead and click here (or on the image above) to play around with the application in your browser. If you find yourself wondering how it works, just click here to download the complete source code/resources and play around with it yourself! (To build the SimpleSilverlightXpsViewer project, you'll want to use Orcas Beta 1 and the Silverlight Tools.)
Of course, this is just a proof-of-concept application built on an Alpha platform, so there are some rough edges. :) Some notes are in order:
#define SETSOURCE
While SimpleSilverlightXpsViewer is a cute proof-of-concept application I enjoyed writing, it is hardly the final word on Silverlight XPS support. (Hey, I'm not even on the Silverlight team!) I don't know what the official plans are for more formal XPS support in the Silverlight platform, but my experience with SimpleSilverlightXpsViewer suggests that most of the pieces are already in place for a pretty reasonable XPS experience with the Silverlight 1.1 Alpha. Throw in a couple of tweaks to Silverlight (and/or SimpleSilverlightXpsViewer!), and it should be possible to provide a pretty compelling XPS-like user experience for Silverlight!
Last week I posted the code for VirtualFileDataObject, an easy-to-use implementation of virtual files for .NET and WPF. This code implements the standard IDataObject COM interface for drag-and-drop and clipboard operations and is specifically targeted at scenarios where an application wants to allow the user to drag an element to a folder and create a file (or files) dynamically on the drop/paste. The standard .NET APIs for drag-and-drop don't support this scenario, so VirtualFileDataObject ended up being a custom implementation of the System.Runtime.InteropServices.ComTypes.IDataObject interface. Fortunately, the specifics aren't too difficult, and a series of posts by Raymond Chen paved the way (in native code).
VirtualFileDataObject
If you read my previous post, you may recall there was an issue with the last scenario of the sample: the application became unresponsive while data for the virtual file was downloading from the web. While this unresponsiveness won't be a noticeable for scenarios involving local data, scenarios that create large files or hit the network are at risk. Well, it's time to find a solution!
And we don't have to look far: the answer is found in the MSDN documentation for Transferring Shell Objects with Drag-and-Drop and the Clipboard under the heading Using IAsyncOperation. As you might expect, we're not the first to notice this behavior; the IAsyncOperation interface exists to solve this very problem. So it seems like things ought to be easy - let's just define the interface, implement its five methods (none of which are very complicated), and watch as the sample application stays responsive during the time-consuming download...
FAIL.
Okay, so that didn't work out quite how we wanted it to. Maybe we defined the interface incorrectly? Maybe we implemented it incorrectly? Or maybe Windows just doesn't support this scenario??
No, no, and no. We've done everything right - it's the platform that has betrayed us. :( Specifically, the DragDrop.DoDragDrop method does something sneaky under the covers: it wraps our respectable System.Runtime.InteropServices.ComTypes.IDataObject instance in a System.Windows.DataObject wrapper. Because this wrapper object doesn't implement or forward IAsyncOperation, it's as if the interface doesn't exist!
System.Runtime.InteropServices.ComTypes.IDataObject
System.Windows.DataObject
IAsyncOperation
Aside: I have the fortune of working with some of the people who wrote this code in the first place, and I asked why this extra level of indirection was necessary. The answer is that it probably isn't - or at least nobody remembers why it's there or why it couldn't be removed now. So the good news is they'll be looking at changing this behavior in a future release of WPF. The bad news is that the change probably won't happen in time for the upcoming WPF 4 release.
Be that as it may, it looks like we're going to need to call the COM DoDragDrop function directly. Fortunately, there's not much that happens between WPF's DragDrop.DoDragDrop and COM's DoDragDrop, so there's not much we have to duplicate. That said, we do need to define the IDropSource interface and write a custom DropSource implementation of its two methods. The nice thing is that both methods are pretty simple and straightforward, so our custom implementation can be private. (And for simplicity's sake, we're not going to bother raising DragDrop's (Preview)GiveFeedbackEvent or (Preview)QueryContinueDragEvent events.)
DragDrop.DoDragDrop
DoDragDrop
DropSource
DragDrop's
(Preview)GiveFeedbackEvent
(Preview)QueryContinueDragEvent
Because we've been careful to define the VirtualFileDataObject.DoDragDrop replacement method with the same signature as the DoDragDrop method it's replacing, updating the sample to use it is trivial. So run the sample again, and - BAM - no more unresponsive window during the transfer! (For real, this time.) You can switch to the window, resize it, drag it, etc. all during the creation of the virtual file.
VirtualFileDataObject.DoDragDrop
But now we've got a bit of a dilemma: if things are happening asynchronously, how can we tell when they're done? The answer lies with the StartOperation and EndOperation methods of the IAsyncOperation interface. Per the interface contact, these methods are called at the beginning/end of the asynchronous operation. So if we just add another constructor to the VirtualFileDataObject class, we can wire things up in the obvious manner:
/// <summary> /// Initializes a new instance of the VirtualFileDataObject class. /// </summary> /// <param name="startAction">Optional action to run at the start of the data transfer.</param> /// <param name="endAction">Optional action to run at the end of the data transfer.</param> public VirtualFileDataObject(Action startAction, Action endAction)
Well, almost... The catch is that while VirtualFileDataObject now supports asynchronous mode, there's no guarantee that the drop target will use it. Additionally, the developer may have specifically set the VirtualFileDataObject.IsAsynchronous property to false to disable asynchronous mode. And when you're in synchronous mode, there aren't any handy begin/end notifications to rely on...
VirtualFileDataObject.IsAsynchronous
false
So I've added support to VirtualFileDataObject for calling the begin/end actions in synchronous mode based on some semi-educated guesses. In my testing, the notifications during synchronous mode behave as identically as possible to those in asynchronous mode. Granted, in some scenarios startAction may run a little earlier in synchronous mode than it would have for asynchronous mode - but as far as the typical developer is concerned, VirtualFileDataObject offers the same handy behavior for both modes!
startAction
Let's celebrate by updating the sample to show a simple busy indicator during the download:
Code-wise, once we've updated the call to DoDragDrop:
VirtualFileDataObject.DoDragDrop(dragSource, virtualFileDataObject, DragDropEffects.Copy);
Everything else stays the same except for the constructor:
var virtualFileDataObject = new VirtualFileDataObject( // BeginInvoke ensures UI operations happen on the right thread () => Dispatcher.BeginInvoke((Action)(() => BusyScreen.Visibility = Visibility.Visible)), () => Dispatcher.BeginInvoke((Action)(() => BusyScreen.Visibility = Visibility.Collapsed)));
The BusyScreen variable above corresponds to a new element in the sample application that provides the simple "Busy..." UI shown above. In real life, we'd probably use MVVM and a bool IsBusy property on our data model to trigger this, but for a sample application, toggling Visibility works fine. Because all the hard work is done by the VirtualFileDataObject class [you're welcome! :) ], the application remains unencumbered by complex logic for anything related to the management of virtual files.
BusyScreen
bool
IsBusy
Visibility
Which is the way it should be!
[Click here to download the complete code for VirtualFileDataObject and the sample application.]
PS - I have one more post planned on this topic demonstrating something I haven't touched on yet to help applications coordinate better with the shell. Stay tuned...