Delay's Blog

Silverlight, WPF, Windows Phone, Web Platform, .NET, and more...

Posts
  • Delay's Blog

    Give your computer insomnia [Free tool and source code to temporarily prevent a machine from going to sleep!]

    • 111 Comments

    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 application

    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!

     

    [Click here to download the Insomnia application along with its complete source code.]

     

    Notes:

    • Insomnia basically boils down to a single function call - but to a function that's a Win32 API and is not part of the .NET Framework. This is where a very powerful feature saves the day: Platform Invoke. For those who aren't familiar with it, P/Invoke (as it's called) lets a managed application call into the APIs exposed by a native DLL. All it takes is a dash of the DllImport attribute and a little bit of translation from the Win32 types to their managed counterparts. The MSDN documentation goes into lots of detail here, and I encourage interested parties to go there.
    • One popular resource for P/Invoke assistance is PInvoke.net, where you can find managed definitions for hundreds of native APIs. But I usually just end up creating my own - if nothing else, it's a good learning exercise. :)
    • The Insomnia window has its Topmost property set to True so it's always visible and people will be less likely to accidentally leave their computers sleep-less. Other than taking up a small bit of screen space and memory, Insomnia consumes no system resources, so it won't get in the way of whatever else is running.
    • It is considered polite to leave things the way you found them, so Insomnia makes an extra call to SetThreadExecutionState when it's closed in order to restore things to how they were. However, this is really more for show than anything else, because the execution state is clearly per-thread and Insomnia's thread is about to die anyway.
    • I did a quick web search for similar tools before I wrote Insomnia and there are a few out there. Most of what I found was for Linux and Mac for some reason, but I'm sure Insomnia isn't the first of its kind for Windows. However, that doesn't stop it from being a nice introduction to P/Invoke - and besides, I'm always happier running my own code! :)

     

    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;
    }
    
  • Delay's Blog

    WPF Charting: It's official! [June 2009 release of the WPF Toolkit is now available!]

    • 80 Comments

    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!

    WPF Toolkit installer

    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.

     

    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:

    DataVisualizationDemos Letter Frequency sample

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

  • Delay's Blog

    ListPicker? I hardly even know 'er! [A detailed overview of the Windows Phone Toolkit's ListPicker control]

    • 63 Comments

    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.

     

    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.

     

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

    Typical example (normal) Typical example (expanded)

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

    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. :)

     

    Custom Templates

    Displaying strings is all well and good, but sometimes it's nice to display richer content:

    Custom template (normal) Custom template (expanded)

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

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

    Threshold (full) Threshold (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.

    <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

    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:

    <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:

    1. 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. :)

      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).
    2. 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.

      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.
    3. 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).

     

    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!

  • Delay's Blog

    Creating something from nothing, asynchronously [Developer-friendly virtual file implementation for .NET improved!]

    • 60 Comments

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

    VirtualFileDataObjectDemo sample application

     

    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!

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

    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.

     

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

    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!

     

    Let's celebrate by updating the sample to show a simple busy indicator during the download:

    VirtualFileDataObjectDemo sample application

    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.

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

  • Delay's Blog

    Mo controls, mo controls, mo controls... [Announcing the second release of the Silverlight for Windows Phone Toolkit!]

    • 56 Comments

    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?

     

    ListPicker

    ListPicker sample ListPicker popup sample
    <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!

     

    LongListSelector

    LongListSelector movies sample LongListSelector people sample LongListSelector jump-list sample
    <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)

    TransitionFrame sample
    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!

    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

    AutoCompleteBox sample
    <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

    CHM file example

    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! :)

  • Delay's Blog

    LB-SV-FAQ [Examples, Notes, Tips, and More for Silverlight 2 Beta 1's ListBox and ScrollViewer controls!]

    • 52 Comments

    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.

    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.

    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.

    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:

    ListBoxItems specified in XAML
    <ListBox>
        <ListBoxItem Content="ListBoxItem 0"/>
        <ListBoxItem Content="ListBoxItem 1"/>
        <ListBoxItem Content="ListBoxItem 2"/>
    </ListBox>

    FrameworkElements specified in XAML:

    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:

    Objects specified with ItemsSource and StaticResource Binding
    <ListBox
        ItemsSource="{StaticResource Products}"/>

    Horizontal layout with ItemsPanel and ItemsPanelTemplate:

    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:

    Simple data visualization with DisplayMemberPath
    <ListBox
        ItemsSource="{StaticResource Products}"
        DisplayMemberPath="Name"/>

    Complex data visualization with ItemTemplate and DataTemplate:

    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:

    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:

    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:

    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.

    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:

    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.

     

  • Delay's Blog

    Confessions of a ListBox groupie [Using IValueConverter to create a grouped list of items simply and flexibly]

    • 50 Comments

    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.

    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:

    GroupingItemsControlConverter sample

    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:

    // 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();
    }
  • Delay's Blog

    The web just got even better... [Silverlight announced at MIX07!]

    • 48 Comments

    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:

    Silverlight Airlines Demonstration

    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:

    • It's implemented as three self-contained controls (the map, calendar, and itinerary picker) that are hooked together via a couple of simple event handlers and property accessors.
    • The goal of code/design separation was achieved here to an extent I haven't experienced before. In particular, the fact that we were able to incorporate an external party's XAML design into our existing code with such ease was a rare treat.
    • I experimented with a vaguely CSS-y approach to XAML design with the calendar and itinerary picker controls I wrote, overlapping a number of different styles and using opacity to display the right style at the right time. I liked the way it worked out here because it enabled me to (for example) completely define the look of the calendar's day cells in a single XAML file - even though that style is actually somewhat complex (there are different styles for alternating months, weekends, hovering, selection, etc.).
    • The "plane flying" animation was Ted's doing and adds a really nice effect. All it took to implement was a plane graphic, a few transforms, a couple of animations, and a bit of high school math. :)
    • The XAML content is automatically resized to fit the browser window (while maintaining its original aspect ratio) with some JavaScript code that hooks the Silverlight control's onLoad and onResize events and manipulates two simple transforms it creates for that purpose. Silverlight lets you program against it with C# and JavaScript - at the same time!
    • In order to compile and build the source code, you need Visual Studio "Orcas" Beta 1 and the Silverlight Tools installed on you machine. Complete details and download links for these tools (and others) can be found on the Silverlight web site.
    • The inspiration for the demo application came from Bret Victor's Magic Ink essay which is a great read and recommended for anyone who's interested in user interface design.

    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. :)

  • Delay's Blog

    Dynamic content made easy [How to: Use the new dynamic population support for Toolkit controls]

    • 47 Comments

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

    Animated demonstration

    The complete code for the sample follows; noteworthy sections have been bolded to make them stand out. The important things to note are:

    • The page content is generated by data binding an XmlDataSource to a DataList. The data are simple CSS style declarations that are used to decorate the dynamic content when it renders. The user can select which decoration to apply by choosing any of the links on the page.
    • Each row of the DataList includes a ModalPopupExtender declaration that pops a modal panel when the CSS style declaration is clicked. All ModalPopupsExtenders share the same popup element in order to avoid unnecessary duplication. The text of the row and the DynamicContextKey of the ModalPopupProperties are both set by a (data bound) call to Eval.
    • The popup (Panel1) contains both static content and dynamic content (Panel2). The static content includes the "OK" button that dismisses the popup. The dynamic content consists of the current server time which is styled according to the selected row's CSS style declaration and displayed in a random color.
    • The dynamic content is generated by a page method which uses its context key parameter to identify the specific row that is being dynamically populated. (Here, the context key is the CSS style declaration, but typically it might be a database index or row number.)

    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:

    <%@ Page Language="C#" %>
    <%
    @ Register Assembly="AtlasControlToolkit" Namespace="AtlasControlToolkit"
      TagPrefix="atlasToolkit" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <
    script runat="server">
      [System.Web.Services.WebMethod]
      
    public string GetContent(string contextKey)
      {
        
    // Create a random color
        string color = (new Random()).Next(0xffffff).ToString("x6");
        
    // Use the style specified by the page author
        string style = contextKey;
        
    // Display the current time
        string time = DateTime.Now.ToLongTimeString();
        
    // Compose the content to return
        return "<span style='color:#" + color + "; " + style + "'>" + time + "</span> ";
      }
    </script>

    <
    html xmlns="http://www.w3.org/1999/xhtml">
    <
    head runat="server">
      <title>Dynamic ModalPopup Demonstration</title>

      <%-- Style the page so it looks pretty --%>
      
    <style type="text/css">
        body { font-family:Verdana;     font-size:10pt; }
        
    .background { background-color:Gray; }
        
    .popup { width:200px;             padding:10px;        background-color:White;
                
    border-style:solid;      border-color:Black;  border-width:2px;
                
    vertical-align: middle;  text-align:center; }
      
    </style>
    </
    head>
    <
    body>
      <form id="form1" runat="server">

        <%-- Atlas pages need a ScriptManager --%>
        
    <atlas:ScriptManager ID="ScriptManager1" runat="server" />

        <%-- A very simple data source to drive the demonstration --%>
        
    <asp:XmlDataSource ID="XmlDataSource1" runat="server">
          <Data>
            <items>
              <item style="font-weight:bold" />
              <item style="font-style:italic" />
              <item style="text-decoration:underline" />
            </items>
          </Data>
        </asp:XmlDataSource>

        <%-- A simple list of all the data items available --%>
        
    <asp:DataList ID="DataList1" runat="server" DataSourceID="XmlDataSource1">

          <HeaderTemplate>
            How would you like your dynamic content styled?
          
    </HeaderTemplate>

          <ItemTemplate>
            &bull; <asp:LinkButton ID="LinkButton" runat="server" Text='<%# Eval("style") %>' />

            <%-- The ModalPopupExtender, popping up Panel1 and dynamically populating Panel2 --%>
            
    <atlasToolkit:ModalPopupExtender ID="ModalPopupExtender" runat="server">
              <atlasToolkit:ModalPopupProperties
                TargetControlID="LinkButton" PopupControlID="Panel1" OkControlID="Button1"
                BackgroundCssClass="background" DynamicControlID="Panel2"
                DynamicContextKey='<%# Eval("style") %>' DynamicServiceMethod="GetContent" />
            </atlasToolkit:ModalPopupExtender>
          </ItemTemplate>

        </asp:DataList>

        <%-- All ModalPopups share the same popup --%>
        
    <asp:Panel ID="Panel1" runat="server" CssClass="popup" style="display:none;">
          <p>This popup popped at <asp:Label ID="Panel2" runat="server" /> and all was well.</p>
          <p><asp:Button ID="Button1" runat="server" Text="OK" /></p>
        </asp:Panel>

      </form>
    </
    body>
    </
    html>

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

  • Delay's Blog

    Lighting up the XML Paper Specification [Proof-of-concept XPS reader for Silverlight!]

    • 46 Comments

    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:

    SimpleSilverlightXpsViewer Application

    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:

    • I created my own XPS documents so I wouldn't have to worry about getting permission to use someone else's XPS documents. Office 2007 comes with a handy "Microsoft XPS Document Writer" printer driver that lets you create an XPS document from any application simply by printing to the XPS "printer" (which then saves the resulting output to a file you specify). I created the three sample documents this way: the "Intro" document came from a simple Word document, the "Blog" document came from my blog via IE7, and the "Site" document came from the Silverlight Forums via IE7 with a landscape page layout.
    • Because the only kind of XPS document I've worked with is the kind the XPS printer driver outputs, there's a very good chance SimpleSilverlightXpsViewer won't understand the internal format of other valid XPS files. Remember, though, that I didn't set out to write an XPS file parser - I just set out to write a simple XPS viewer for Silverlight. :)
    • The translation of "XPS XAML" to "Silverlight XAML" is done by the XpsToSilverlightXamlReader class, a minimal derivation from XmlReader that performs on-the-fly modification of the "XPS XAML" to translate it into "Silverlight XAML". Specifically, some elements are renamed, some attribute values are tweaked, and some attributes are removed entirely. The tweaking is done to address the Glyphs.FontUri/ImageBrush.ImageSource issue mentioned next and re-points the relevant content to an external location.
    • XPS documents are entirely self-contained, with any necessary fonts and images embedded in the file (package) itself. This is great for simplifying distribution and Silverlight's Downloader class makes it easy to get at individual files in a package. However, SimpleSilverlightXpsViewer works best when the images and the fonts are extracted from the XPS file:
      • Under the right conditions, embedded images can be fetched by Silverlight by using the ImageBrush.SetSource method. However, things tend to break if there are multiple references to the same image in a single page (an exception is thrown when the second call to SetSource is made), so SimpleSilverlightXpsViewer doesn't enable this by default. Interested parties can #define SETSOURCE (for both C# files) to experiment with this feature (things work fine for the first page of all of the sample documents, but break on the second pages of the Blog and Site documents).
      • The default behavior of Glyphs.FontUri does not seem to automatically pull the font out of the package - at least not as it's used by SimpleSilverlightXpsViewer (possibly because Silverlight doesn't seem to like the leading '/' on package-relative paths). TextBlock has a SetFontSource method that seems interesting, but XPS XAML uses the Glyphs class which doesn't seem to support SetFontSource.
    • For some reason, the XPS documents generated by the XPS printer driver aren't directly open-able by Silverlight's Downloader (a COM exception is thrown). However, I've found that a quick un-ZIP/re-ZIP with either of my favorite ZIP tools yields XPS documents that open right up. I suspect this is due to a simple issue with the Alpha Downloader implementation (endian-ness of the ZIP file, some special section embedded by the XPS printer driver, etc.) that could be fixed by the Silverlight team without much difficulty.
    • Silverlight doesn't support the TIFF file format (which is not surprising because full support can be quite complex and TIFF images are hardly ever used on the web). As it happens, XPS printer driver output may contain TIFF images (it seems they're used as a mask of some kind behind another PNG or JPG image) - SimpleSilverlightXpsViewer simply ignores the TIFF images and neatly side-steps the support issue. :)

    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!

Page 1 of 28 (277 items) 12345»