The blog of dlaa.me

Silverlight 4 Beta is out - and the Toolkit has it covered! [Silverlight Toolkit November 2009 release now available for Silverlight 3 and 4!]

We've just published the November 09 release of the Silverlight Toolkit in conjunction with today's unveiling of the Silverlight 4 Beta! The Silverlight 4 Beta offers a bunch of neat new features which enable even more compelling online (and offline!) applications. Stuff like bi-directional text, web cam and microphone support, a RichTextArea control, clipboard access, ICommand, an elevated-trust model, printing, notifications, right-click, and more!

Of course, the big news for this post is a new version of the Silverlight Toolkit that's perfectly suited for developing great applications on Silverlight 4. And though it seems like just yesterday that we finished off the October 09 release, there are some neat, new things in today's release for Silverlight 3, as well. You can be confident that the Silverlight Toolkit has your back whichever platform you choose! :)

 

My announcements are usually all about the Toolkit's Data Visualization assembly - and there are some improvements in that area - but it wasn't my primary focus this time around. If you have a look at the official release notes, you'll see there are all kinds of new things across the board. I'm not going to repeat everything here, but I do want to highlight a few things:

Partial Release Notes

  • SL3/SL4: We've added a new control, BusyIndicator, based on the ActivityControl found in the RIA Services project template and samples. BusyIndicator makes it easy to add a "Please wait..." progress indicator to applications and does so in a way that's easy to customize. It's a simple control, but a handy one. I have a few examples below; please see David Poll's blog for more.
  • SL3/SL4: Minor tweaks to Data Visualization: Support more flexible subclassing scenarios of core classes, provide more helpful exception messages in some scenarios, tweak default Chart template to respect Padding, change type of Chart.LegendItems collection items to object for flexibility, convert Legend to a HeaderedItemsControl for consistency.
  • SL3/SL4: It's now possible to collect code coverage statistics when running unit tests with the Silverlight Unit Test Framework! Jeff spent some time this release productizing some clever hackery Ted Glaza originally did about a year ago, and this new capability fills a pretty big hole in the unit testing story for Silverlight. We're also including a couple of the custom build actions that we use internally - now you'll be able to use them in your projects! And we're including the XML files to make it easy for customers to enable Application Library Caching for their projects. For more information on these topics, please have a look at Jeff Wilcox's blog.
  • SL4: We've modified all the Toolkit/SDK control visuals and input handlers to behave correctly for right-to-left cultures and added mouse wheel support in some of the obvious places.
  • SL4: We've removed ImplicitStyleManager because Silverlight 4 supports implicit styles natively. Correspondingly, we've modified the Theme-based wrapper classes (TwilightBlue, ShinyRed, etc.) to use implicit styles.
  • SL4: Viewbox has moved into the Silverlight 4 core (i.e., it's available in the System.Windows.dll assembly that every Silverlight 4 install includes), so it has been removed from the Silverlight 4 Toolkit.
  • SL4: Along with the new MEF (Managed Extensibility Framework) features in the Silverlight 4 SDK, there's an experimental System.ComponentModel.Composition.Packaging.Toolkit assembly in the Toolkit that adds support for downloading secondary XAP’s and loading them into a MEF catalog. For more information, please have a look at Wes Haggard's blog.

 

BusyIndicator is in the house...

At its core, BusyIndicator is a simple wrapper control into which you put whatever makes up the UI of your application. (You can think of it as a special kind of Border with special abilities.) BusyIndicator exposes an IsBusy property which should be set to true (possibly via data binding) whenever the relevant portion of the application is busy and won't respond to user input. When this happens, BusyIndicator automatically disables its content and shows a simple UI to let the user know what's going on. It's really quite simple! :)

Here's the most basic scenario:

<controlsToolkit:BusyIndicator
    IsBusy="{Binding MyBusyProperty}">

    <!-- Content goes here... -->

</controlsToolkit:BusyIndicator>

And this is how it looks when IsBusy is set:

Default BusyIndicator

The most common change is to customize the message, and of course that's simple to do:

<controlsToolkit:BusyIndicator
    IsBusy="{Binding MyBusyProperty}"
    BusyContent="My custom message...">

    <!-- Content goes here... -->

</controlsToolkit:BusyIndicator>

Yielding:

BusyIndicator with custom message

Note that the BusyContent property is of type object, so we could have used other UI elements (like Grid, Image, and Button) for a message with more than just text. Of course, sometimes you want things to be totally custom - so there are some straightforward ways to do that which don't require you to completely re-Template:

<controlsToolkit:BusyIndicator
    IsBusy="{Binding MyBusyProperty}"
    BusyContent="{Binding}">

    <!-- Provide custom UI for busy display -->
    <controlsToolkit:BusyIndicator.BusyContentTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="Downloading Email" FontWeight="Bold" HorizontalAlignment="Center"/>
                <StackPanel Margin="6">
                    <TextBlock Text="{Binding MyStatus}"/>
                    <ProgressBar Value="{Binding MyProgress}" Height="15"/>
                </StackPanel>
                <Button Content="Cancel" HorizontalAlignment="Center"/>
            </StackPanel>
        </DataTemplate>
    </controlsToolkit:BusyIndicator.BusyContentTemplate>

    <!-- Remove unnecessary default ProgressBar -->
    <controlsToolkit:BusyIndicator.ProgressBarStyle>
        <Style TargetType="ProgressBar">
            <Setter Property="Visibility" Value="Collapsed"/>
        </Style>
    </controlsToolkit:BusyIndicator.ProgressBarStyle>

    <!-- Content goes here... -->

</controlsToolkit:BusyIndicator>

Which looks like:

BusyIndicator with custom UI

Another property to be aware of is the DisplayAfter property which lets you configure the initial delay before the busy indicator is shown - to avoid the annoying "on/off" flicker that would otherwise result from a lot of quick operations in succession. The BusyIndicator page of the public sample project has an interactive section where you can experiment with various delays and durations to see how this looks in action.

 

ImplicitStyleManager has left the building...

I wanted to show how to convert an existing application using ImplicitStyleManager over to using Silverlight 4's new implicit styling support. Because I made this change for the 11 Toolkit themes, I can tell you it is quite easy. :) Basically, it's just a matter of removing the ImplicitStyleManager attached property/properties and - where relevant - moving the ResourceDictionary of Styles into the Resources section of the parent element (or all the way up to App.xaml). Also, be sure to go through and remove any explicit assignments to the Style property or it's friends (e.g., ItemContanerStyle). (This probably isn't common in most applications, but the Toolkit Themes did it all over the place.)

To make that a little more concrete, here's a simplified "before" example that makes Buttons have blue text and ListBoxItems purple:

<StackPanel controlsThemingToolkit:ImplicitStyleManager.ApplyMode="OneTime">

    <!-- Styles for controls -->
    <StackPanel.Resources>
        <Style TargetType="Button">
            <Setter Property="Foreground" Value="Blue"/>
        </Style>
        <Style TargetType="ListBoxItem">
            <Setter Property="Foreground" Value="Purple"/>
        </Style>
    </StackPanel.Resources>

    <!-- Styled controls -->
    <Button Content="Button"/>
    <ListBox>
        <ListBoxItem Content="Item 1"/>
        <ListBoxItem Content="Item 2"/>
    </ListBox>

</StackPanel>

To convert this XAML over to Silverlight 4's implicit style support, just remove the highlighted portion above. The visuals will look exactly the same, but everything is more efficient because the platform is handling it internally. And what's more, various scenarios that were tricky to get working with ImplicitStyleManager (like styling the contents of a TabControl) now "just work" thanks to the new framework support for implicit styles!

 

Silverlight 4 has a lot of great improvements that really raise the bar for rich, interactive web applications. I encourage everyone to check it out today and start thinking about how to take advantage of all the new stuff! :) And when you're done with that, please check out the live Toolkit samples for Silverlight 3 or Silverlight 4, download the Toolkit installer(s) for the platform/platforms of your choice, and enjoy!

Creating something from nothing - and knowing it [Developer-friendly virtual file implementation for .NET refined!]

A couple of weeks ago I wrote about VirtualFileDataObject, my developer-friendly virtual file implementation for .NET and WPF. I followed that up by adding support for asynchronous behavior to improve the user experience during long-running operations. Last week, these posts got a shout-out from blogging legend Raymond Chen, whose work provided the inspiration for the project. [Best week ever! :) ] Now it's time for one last tweak to wrap things up!

 

Recall that the Windows APIs for drag-and-drop and clipboard operations deal with objects that implement the IDataObject COM interface. And while the native DoDragDrop function tries to provide a mechanism for the source and target to communicate (via the pdwEffect parameter and the function's return value), there's nothing similar available for the SetClipboardData function. And once you enable asynchronous drag-and-drop, it's clear that the return value of DoDragDrop isn't going to work, either, because the call returns immediately (before the operation has completed). So it seems like there must be some other way for the source and target to share information...

Sure enough, there are some shell clipboard formats specifically for enabling communication between the source and target! For the purposes of this sample, we're interested in using two of them:

And here's another which is relevant enough that I wrote code to support it, though I'll only mention it once more here:

  • CFSTR_PASTESUCCEEDED - Indication that a paste succeeded and what kind of operation (copy/move/link) it did

 

Armed with that knowledge, let's tweak the sample application so one of the scenarios does a move/cut instead of a copy:

VirtualFileDataObjectDemo sample application

The first tweak to the code modifies our helper function to accept a DragDropEffects parameter so the caller can indicate its copy/move preference with the new PreferredDropEffect property in the clipboard scenario:

private static void DoDragDropOrClipboardSetDataObject(MouseButton button, DependencyObject dragSource,
    VirtualFileDataObject virtualFileDataObject, DragDropEffects allowedEffects)
{
    try
    {
        if (button == MouseButton.Left)
        {
            // Left button is used to start a drag/drop operation
            VirtualFileDataObject.DoDragDrop(dragSource, virtualFileDataObject, allowedEffects);
        }
        else if (button == MouseButton.Right)
        {
            // Right button is used to copy to the clipboard
            // Communicate the preferred behavior to the destination
            virtualFileDataObject.PreferredDropEffect = allowedEffects;
            Clipboard.SetDataObject(virtualFileDataObject);
        }
    }
    catch (COMException)
    {
        // Failure; no way to recover
    }
}

Then we can tweak the VirtualFileDataObject constructor to pass a custom "end" action that uses the new PerformedDropEffect property to find out what action took place. If the target performed a move (or a cut with the clipboard), then we'll hide the corresponding "button" to reflect that fact:

private void VirtualFile_MouseButtonDown(object sender, MouseButtonEventArgs e)
{
    var virtualFileDataObject = new VirtualFileDataObject(
        null,
        (vfdo) =>
        {
            if (DragDropEffects.Move == vfdo.PerformedDropEffect)
            {
                // Hide the element that was moved (or cut)
                // BeginInvoke ensures UI operations happen on the right thread
                Dispatcher.BeginInvoke((Action)(() => VirtualFile.Visibility = Visibility.Hidden));
            }
        });

    // Provide a virtual file (generated on demand) containing the letters 'a'-'z'
    ...

    DoDragDropOrClipboardSetDataObject(e.ChangedButton, TextUrl, virtualFileDataObject, DragDropEffects.Move);
}

Please note that the begin/end actions are now of type Action<VirtualFileDataObject>. The new parameter is always a reference to the active VirtualFileDataObject instance and is provided to make it easy to use anonymous methods like you see above.

Aside: Otherwise you'd need to capture the VirtualFileDataObject instance so you could pass it to the action being provided to that same instance's constructor! (Catch-22 much?) While there may be a clever way to do this without making the begin/end actions properties of the class (which I didn't want to do because that wouldn't ensure they're invariant), passing the VirtualFileDataObject in this manner is both easy and obvious.
Further aside: As a consequence of the changes to support these new properties, VirtualFileDataObject now implements the IDataObject::SetData method for HGLOBAL-style data. So if there's some other CFSTR_ property that's relevant to your scenario, you can query it off the VirtualFileDataObject instance in much the same manner!

 

[Click here to download the complete code for VirtualFileDataObject and the sample application.]

 

And that's pretty much all there is to it! With the addition of PreferredDropEffect and PerformedDropEffect (and PasteSucceeded, though it's not demonstrated above), VirtualFileDataObject makes it easy for your application to provide a seamless virtual file drag/drop experience with all the sophisticated nuances users expect from a polished Windows application.

Enjoy!

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

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

As the platform evolves, so do the workarounds [Better SetterValueBindingHelper makes Silverlight Setters better-er!]

Back in May, I mentioned that Silverlight 2 and 3 don't support putting a Binding in the Value of a Setter. I explained why this is useful (ex: MVVM, TreeView expansion, developer/designer separation, etc.) and shared a helper class I wrote to implement the intended functionality on Silverlight. My workaround supported setters for normal DependencyPropertys as well as attached ones, so it covered all the bases. It worked well on both flavors of Silverlight and a bunch of you went off and used SetterValueBindingHelper successfully in your own projects.

The sun was shining, birds were chirping, and all was right with (that part of) the world...

SetterValueBindingHelperDemo sample

 

Now flash forward to a few days ago when I was contacted by fellow Silverlight team members RJ Boeke and Vinoo Cherian with a report that certain uses of SetterValueBindingHelper which worked fine on Silverlight 2 and 3 were likely to break if used in a possible future version of Silverlight that was more consistent with WPF's handling of such things. You can imagine my astonishment and dismay...

Important aside: The Silverlight team takes backward compatibility very seriously, so running any Silverlight 2 or 3 application with SetterValueBindingHelper on such a future version of Silverlight would continue to work in the expected manner. The Silverlight team makes a concerted effort to ensure that each version of Silverlight is "bug compatible" with previous versions to prevent existing applications from suddenly breaking when a new version of Silverlight comes out. However, were someone to recompile such an application to target a newer release of Silverlight, that application would no longer be subject to the backwards compatibility quirks and would begin seeing the new (more correct/consistent) platform behavior.

RJ and Vinoo pointed out that a more WPF-consistent handling of Styles would break one of the samples that was part of my original blog post. Specifically, the following example would not have the first Binding applied (note: per convention, code in italics is wrong):

<Style TargetType="Button">
    <!-- WPF syntax:
    <Setter Property="Grid.Column" Value="{Binding}"/>
    <Setter Property="Grid.Row" Value="{Binding}"/> -->
    <Setter Property="local:SetterValueBindingHelper.PropertyBinding">
        <Setter.Value>
            <local:SetterValueBindingHelper
                Type="System.Windows.Controls.Grid, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"
                Property="Column"
                Binding="{Binding}"/>
        </Setter.Value>
    </Setter>
    <Setter Property="local:SetterValueBindingHelper.PropertyBinding">
        <Setter.Value>
            <local:SetterValueBindingHelper
                Type="Grid"
                Property="Row"
                Binding="{Binding}"/>
        </Setter.Value>
    </Setter>
</Style>

What's important to note is that two Setters are both setting the same Property (local:SetterValueBindingHelper.PropertyBinding) and WPF optimizes this scenario to only apply the last Value it sees. Clearly, it was time to think about how tweak SetterValueBindingHelper so it would work with this theoretical future release of Silverlight...

Tangential aside: This kind of platform change wouldn't affect just SetterValueBindingHelper - any place where multiple Setters targeted the same Property would behave differently. But that difference won't matter 99% of the time - SetterValueBindingHelper is fairly unique in its need that every Value be applied.

 

One idea for a fix is to expose something like PropertyBinding2 from SetterValueBindingHelper and treat it just like another PropertyBinding. While that would definitely work, how do we know that two properties is enough? What if you need three or four? No, despite its simplicity, this is not the flexible solution we're looking for.

Taking a step back, what we really want is to somehow provide an arbitrary number of Property/Binding pairs instead of being limited to just one. And if you read that last sentence and thought "Collection!", I like the way you think. :) Specifically, what if the same SetterValueBindingHelper class we're already using to provide the attached DependencyProperty and the data for it were also capable of storing a collection of other SetterValueBindingHelper objects? Yeah, sure, that would work!

 

So let's lay a few ground rules to help guide us:

  • Every current use of SetterValueBindingHelper should continue to be valid after we make our changes. In other words, upgrading should be a simple matter of dropping in the new SetterValueBindingHelper.cs file and that's all.
  • The new SetterValueBindingHelper syntax should work correctly for the current Silverlight 3 release as well as this mythical future version of Silverlight with the WPF-consistent Style changes.
  • The new collection syntax should be easy to use and easy to understand.
  • Arbitrary nesting is unnecessary; either someone's using a SetterValueBindingHelper on its own, or else they're using it as a container for a single, nested layer of SetterValueBindingHelper children.
  • We could try to be fancy and let children inherit things from their parent, but it's not actually as useful as it seems. Let's not go there and instead keep everything simple and consistent.

Keeping these guidelines in mind, the resulting changes to SetterValueBindingHelper give us the following alternate representation of the above XAML which works fine on Silverlight 3 today and will also give the desired effect on a possible future version of Silverlight with the WPF optimization:

<Style TargetType="Button">
    <!-- WPF syntax:
    <Setter Property="Grid.Column" Value="{Binding}"/>
    <Setter Property="Grid.Row" Value="{Binding}"/> -->
    <Setter Property="delay:SetterValueBindingHelper.PropertyBinding">
        <Setter.Value>
            <delay:SetterValueBindingHelper>
                <delay:SetterValueBindingHelper
                    Type="System.Windows.Controls.Grid, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"
                    Property="Column"
                    Binding="{Binding}"/>
                <delay:SetterValueBindingHelper
                    Type="Grid"
                    Property="Row"
                    Binding="{Binding}"/>
            </delay:SetterValueBindingHelper>
        </Setter.Value>
    </Setter>
</Style>
Aside: The two different ways of identifying Grid above are part of the original sample showing that both ways work - in practice, both instances would use the simple "Grid" form.

 

Other than the namespace change to "delay" (for consistency with my other samples), the only change here is the extra SetterValueBindingHelper wrapper you see highlighted. Everything else is pretty much the same and now it works on imaginary versions of Silverlight, too! :) So if you're working on an app and you find yourself needing SetterValueBindingHelper, please use this latest version; you can rest assured that you're future-proof.

 

[Click here to download the complete source code for SetterValueBindingHelper and its sample application.]

 

Here's the updated code in its entirety. Please note that I have used a normal (i.e., non-observable) collection, so dynamic updates to the Values property are not supported. This was a deliberate decision to minimize complexity. (And besides, I've never heard of anyone modifying the contents of a Style dynamically.)

/// <summary>
/// Class that implements a workaround for a Silverlight XAML parser
/// limitation that prevents the following syntax from working:
///    &lt;Setter Property="IsSelected" Value="{Binding IsSelected}"/&gt;
/// </summary>
[ContentProperty("Values")]
public class SetterValueBindingHelper
{
    /// <summary>
    /// Optional type parameter used to specify the type of an attached
    /// DependencyProperty as an assembly-qualified name, full name, or
    /// short name.
    /// </summary>
    [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
        Justification = "Unambiguous in XAML.")]
    public string Type { get; set; }

    /// <summary>
    /// Property name for the normal/attached DependencyProperty on which
    /// to set the Binding.
    /// </summary>
    public string Property { get; set; }

    /// <summary>
    /// Binding to set on the specified property.
    /// </summary>
    public Binding Binding { get; set; }

    /// <summary>
    /// Collection of SetterValueBindingHelper instances to apply to the
    /// target element.
    /// </summary>
    /// <remarks>
    /// Used when multiple Bindings need to be applied to the same element.
    /// </remarks>
    public Collection<SetterValueBindingHelper> Values
    {
        get
        {
            // Defer creating collection until needed
            if (null == _values)
            {
                _values = new Collection<SetterValueBindingHelper>();
            }
            return _values;
        }
    }
    private Collection<SetterValueBindingHelper> _values;

    /// <summary>
    /// Gets the value of the PropertyBinding attached DependencyProperty.
    /// </summary>
    /// <param name="element">Element for which to get the property.</param>
    /// <returns>Value of PropertyBinding attached DependencyProperty.</returns>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
        Justification = "SetBinding is only available on FrameworkElement.")]
    public static SetterValueBindingHelper GetPropertyBinding(FrameworkElement element)
    {
        if (null == element)
        {
            throw new ArgumentNullException("element");
        }
        return (SetterValueBindingHelper)element.GetValue(PropertyBindingProperty);
    }

    /// <summary>
    /// Sets the value of the PropertyBinding attached DependencyProperty.
    /// </summary>
    /// <param name="element">Element on which to set the property.</param>
    /// <param name="value">Value forPropertyBinding attached DependencyProperty.</param>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
        Justification = "SetBinding is only available on FrameworkElement.")]
    public static void SetPropertyBinding(FrameworkElement element, SetterValueBindingHelper value)
    {
        if (null == element)
        {
            throw new ArgumentNullException("element");
        }
        element.SetValue(PropertyBindingProperty, value);
    }

    /// <summary>
    /// PropertyBinding attached DependencyProperty.
    /// </summary>
    public static readonly DependencyProperty PropertyBindingProperty =
        DependencyProperty.RegisterAttached(
            "PropertyBinding",
            typeof(SetterValueBindingHelper),
            typeof(SetterValueBindingHelper),
            new PropertyMetadata(null, OnPropertyBindingPropertyChanged));

    /// <summary>
    /// Change handler for the PropertyBinding attached DependencyProperty.
    /// </summary>
    /// <param name="d">Object on which the property was changed.</param>
    /// <param name="e">Property change arguments.</param>
    private static void OnPropertyBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Get/validate parameters
        var element = (FrameworkElement)d;
        var item = (SetterValueBindingHelper)(e.NewValue);

        if ((null == item.Values) || (0 == item.Values.Count))
        {
            // No children; apply the relevant binding
            ApplyBinding(element, item);
        }
        else
        {
            // Apply the bindings of each child
            foreach (var child in item.Values)
            {
                if ((null != item.Property) || (null != item.Binding))
                {
                    throw new ArgumentException(
                        "A SetterValueBindingHelper with Values may not have its Property or Binding set.");
                }
                if (0 != child.Values.Count)
                {
                    throw new ArgumentException(
                        "Values of a SetterValueBindingHelper may not have Values themselves.");
                }
                ApplyBinding(element, child);
            }
        }
    }

    /// <summary>
    /// Applies the Binding represented by the SetterValueBindingHelper.
    /// </summary>
    /// <param name="element">Element to apply the Binding to.</param>
    /// <param name="item">SetterValueBindingHelper representing the Binding.</param>
    private static void ApplyBinding(FrameworkElement element, SetterValueBindingHelper item)
    {
        if ((null == item.Property) || (null == item.Binding))
        {
            throw new ArgumentException(
                "SetterValueBindingHelper's Property and Binding must both be set to non-null values.");
        }

        // Get the type on which to set the Binding
        Type type = null;
        if (null == item.Type)
        {
            // No type specified; setting for the specified element
            type = element.GetType();
        }
        else
        {
            // Try to get the type from the type system
            type = System.Type.GetType(item.Type);
            if (null == type)
            {
                // Search for the type in the list of assemblies
                foreach (var assembly in AssembliesToSearch)
                {
                    // Match on short or full name
                    type = assembly.GetTypes()
                        .Where(t => (t.FullName == item.Type) || (t.Name == item.Type))
                        .FirstOrDefault();
                    if (null != type)
                    {
                        // Found; done searching
                        break;
                    }
                }
                if (null == type)
                {
                    // Unable to find the requested type anywhere
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                        "Unable to access type \"{0}\". Try using an assembly qualified type name.",
                        item.Type));
                }
            }
        }

        // Get the DependencyProperty for which to set the Binding
        DependencyProperty property = null;
        var field = type.GetField(item.Property + "Property",
            BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
        if (null != field)
        {
            property = field.GetValue(null) as DependencyProperty;
        }
        if (null == property)
        {
            // Unable to find the requsted property
            throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                "Unable to access DependencyProperty \"{0}\" on type \"{1}\".",
                item.Property, type.Name));
        }

        // Set the specified Binding on the specified property
        element.SetBinding(property, item.Binding);
    }

    /// <summary>
    /// Returns a stream of assemblies to search for the provided type name.
    /// </summary>
    private static IEnumerable<Assembly> AssembliesToSearch
    {
        get
        {
            // Start with the System.Windows assembly (home of all core controls)
            yield return typeof(Control).Assembly;

            // Fall back by trying each of the assemblies in the Deployment's Parts list
            foreach (var part in Deployment.Current.Parts)
            {
                var streamResourceInfo = Application.GetResourceStream(
                    new Uri(part.Source, UriKind.Relative));
                using (var stream = streamResourceInfo.Stream)
                {
                    yield return part.Load(stream);
                }
            }
        }
    }
}

My new home page, revised [Updated collection of great Silverlight/WPF Data Visualization resources!]

In the time since sharing my last collection of Silverlight/WPF Charting links, there have been some great new articles I'd like to highlight. And in case you haven't heard, we published the October 09 release of the Silverlight Toolkit last week, so please consider upgrading if you haven't already!

Here are the latest links (FYI: previously published links are gray):

Overviews (100 level)

Scenarios (200 level)

Internals (300 level)

Team Member posts (Partner level)

My posts (Ego level)

Many, many thanks to everyone who's spent time helping others learn how to use Silverlight/WPF Data Visualization!

PS - If I've missed any good resources, please leave a comment with a link - I'm always happy to find more good stuff! :)

PPS - The most recent version of this collection will always be pointed to by http://cesso.org/r/DVLinks. If you're going to link to this post, please use that URL so you'll always be up to date.

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

Have you ever used one of those programs that lets you drag a UI widget, drop it in a folder, and - poof - a file that didn't exist magically appears? Me, too - it's cool! But how does it work? Are they really deferring the work of creating that file until it's needed and then creating the file during the drag-and-drop operation? Yes they are - and now you can, too!

If I have seen a little further it is by standing on the shoulders of Giants.- Sir Isaac Newton

Everything you ever needed to know about drag-and-drop in Windows can probably be found in the MSDN documentation for Transferring Shell Objects with Drag-and-Drop and the Clipboard. That documentation is a great resource for specific questions, but because it covers so many topics, it's not necessarily the best way to get an overview. For that, we turn to Raymond Chen's blog - specifically, a series he did called "What a drag" in March of last year. Raymond's example uses native code exclusively, but don't let that scare you away - his presentation and explanations are always engaging! Please take a moment to read (or at least skim) the following articles, or else the rest of this post might not make much sense:

Okay, so we know what we want to do and now we know how it's supposed to work! The first thing to consider is whether the WPF platform supports the virtual file scenario. And unfortunately, it doesn't seem to. :( Specifically, the DataObject class is where we'd expect to find such support, but the closest it has is SetFileDropList. And while that sounds promising, it's really just a list of strings with paths to existing files. Recall that the DragDrop.DoDragDrop method is synchronous (i.e., does not return until the drag-and-drop operation is complete), and the obvious consequence is that creating a virtual file on the fly isn't practical with this API. Specifically, the bits already need to exist on disk by the time the user starts the drag operation - but you don't know what data they're going to drag until they start! It's a classic Catch-22...

The natural next step is to consider whether subclassing DataObject would help - but it's sealed, so that's a pretty quick dead end.

Move on to consider whether the System.Windows.IDataObject interface used by DataObject would be useful. But it seems not; it's pretty much the same API as DataObject which we've already dismissed.

So that leaves us looking at the System.Runtime.InteropServices.ComTypes.IDataObject interface which is a simple managed representation of the actual IDataObject COM interface that the shell uses directly. Clearly, anything is possible at this point, so if we can just channel our inner Raymond, we ought to be in business!

 

The good news is that I've already done this for you. I've even written a simple WPF application to show how everything fits together:

VirtualFileDataObjectDemo sample application

 

[Click here to download the complete code for VirtualFileDataObject and the sample application.]

 

What I've done is write a custom IDataObject class called VirtualFileDataObject that does all the hard work for you. All you need to do is provide the relevant data, and your users will be dragging-and-dropping virtual files in no time. And what's really neat is that writing the code to support drag-and-drop automatically gives complete support for the clipboard because the Clipboard.SetDataObject method uses the same IDataObject interface!

Let's look at the sample scenarios to understand how VirtualFileDataObject is used:

 

Text only

var virtualFileDataObject = new VirtualFileDataObject();

// Provide simple text (in the form of a NULL-terminated ANSI string)
virtualFileDataObject.SetData(
    (short)(DataFormats.GetDataFormat(DataFormats.Text).Id),
    Encoding.Default.GetBytes("This is some sample text\0"));

DoDragDropOrClipboardSetDataObject(e.ChangedButton, Text, virtualFileDataObject);

This is the simplest possible scenario - just to show off that simple things stay simple with VirtualFileDataObject. Here's the signature for the SetData method used above:

/// <summary>
/// Provides data for the specified data format (HGLOBAL).
/// </summary>
/// <param name="dataFormat">Data format.</param>
/// <param name="data">Sequence of data.</param>
public void SetData(short dataFormat, IEnumerable<byte> data)

Note that it operates in "HGLOBAL mode" where all the data is provided at the time of the call. Note also that it doesn't know anything about what the data is, so it's up to the caller to make sure it's in the right format. Specifically, the right format for DataFormats.Text is a NULL-terminated ANSI string, so that's what the sample passes in.

Aside: The DoDragDropOrClipboardSetDataObject method used above is a simple helper method for the test application - it calls DragDrop.DoDragDrop or Clipboard.SetDataObject depending on what the user did. It's not very exciting, so I won't be showing it (or the VirtualFileDataObject constructor) in the following examples.

 

Text and URL

// Provide simple text and a URL in priority order
// (both in the form of a NULL-terminated ANSI string)
virtualFileDataObject.SetData(
    (short)(DataFormats.GetDataFormat(CFSTR_INETURLA).Id),
    Encoding.Default.GetBytes("http://blogs.msdn.com/delay/\0"));
virtualFileDataObject.SetData(
    (short)(DataFormats.GetDataFormat(DataFormats.Text).Id),
    Encoding.Default.GetBytes("http://blogs.msdn.com/delay/\0"));

Another simple example based on Raymond's discussion of drag-and-drop into Internet Explorer. For our purposes, this example demonstrates that you can set multiple data formats and that formats other than those exposed by WPF's DataFormats enumeration are easy to deal with. Per the guidelines, supported formats are provided in order by priority, with higher priority formats coming first.

 

Virtual file

// Provide a virtual file (generated on demand) containing the letters 'a'-'z'
virtualFileDataObject.SetData(new VirtualFileDataObject.FileDescriptor[]
{
    new VirtualFileDataObject.FileDescriptor
    {
        Name = "Alphabet.txt",
        Length = 26,
        ChangeTimeUtc = DateTime.Now.AddDays(-1),
        StreamContents = stream =>
            {
                var contents = Enumerable.Range('a', 26).Select(i => (byte)i).ToArray();
                stream.Write(contents, 0, contents.Length);
            }
    },
});

At last, something juicy! This example creates a virtual file named Alphabet.txt that's 26 bytes long and appears to have been written exactly one day ago. The contents of this file aren't generated until they're actually required by the drop target, so there's no wasted effort if the user doesn't start the drag, aborts it, or whatever. When the file's contents are eventually needed, VirtualFileDataObject calls the user-provided Action (not necessarily a lambda expression, though I've used one here for conciseness) and passes it a write-only Stream instance for writing the data. The user code writes to this stream as much or as little as necessary, then returns control to VirtualFileDataObject in order to complete the operation.

The file that gets created when you drop/paste this item into a folder looks just like you'd expect. And because VirtualFileDataObject supports the length and change time fields, Windows has all the information it needs to help the user resolve possible conflicts:

Windows conflict dialog

Here's the relevant SetData method (note that you can provide an arbitrary number of FileDescriptor instances, so you can create as many virtual files as you want):

/// <summary>
/// Provides data for the specified data format (FILEGROUPDESCRIPTOR/FILEDESCRIPTOR)
/// </summary>
/// <param name="fileDescriptors">Collection of virtual files.</param>
public void SetData(IEnumerable<FileDescriptor> fileDescriptors)

It makes use of another SetData method that's handy for dealing with "ISTREAM mode":

/// <summary>
/// Provides data for the specified data format and index (ISTREAM).
/// </summary>
/// <param name="dataFormat">Data format.</param>
/// <param name="index">Index of data.</param>
/// <param name="streamData">Action generating the data.</param>
/// <remarks>
/// Uses Stream instead of IEnumerable(T) because Stream is more likely
/// to be natural for the expected scenarios.
/// </remarks>
public void SetData(short dataFormat, int index, Action<Stream> streamData)

And accepts data in the following form:

/// <summary>
/// Class representing a virtual file for use by drag/drop or the clipboard.
/// </summary>
public class FileDescriptor
{
    /// <summary>
    /// Gets or sets the name of the file.
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// Gets or sets the (optional) length of the file.
    /// </summary>
    public UInt64? Length { get; set; }

    /// <summary>
    /// Gets or sets the (optional) change time of the file.
    /// </summary>
    public DateTime? ChangeTimeUtc { get; set; }

    /// <summary>
    /// Gets or sets an Action that returns the contents of the file.
    /// </summary>
    public Action<Stream> StreamContents { get; set; }
}

 

Text, URL, and a virtual file!

// Provide a virtual file (downloaded on demand), its URL, and descriptive text
virtualFileDataObject.SetData(new VirtualFileDataObject.FileDescriptor[]
{
    new VirtualFileDataObject.FileDescriptor
    {
        Name = "DelaysBlog.xml",
        StreamContents = stream =>
            {
                using(var webClient = new WebClient())
                {
                    var data = webClient.DownloadData("http://blogs.msdn.com/delay/rss.xml");
                    stream.Write(data, 0, data.Length);
                }
            }
    },
});
virtualFileDataObject.SetData(
    (short)(DataFormats.GetDataFormat(CFSTR_INETURLA).Id),
    Encoding.Default.GetBytes("http://blogs.msdn.com/delay/rss.xml\0"));
virtualFileDataObject.SetData(
    (short)(DataFormats.GetDataFormat(DataFormats.Text).Id),
    Encoding.Default.GetBytes("[The RSS feed for Delay's Blog]\0"));

Finally, here's a sample that pulls everything together in a nice, fancy package with a bow on top. The text is an informative snippet, the URL is a link to an RSS feed, and the virtual file is the dynamically downloaded content of that RSS feed! Way cool - it's like there's this big file sitting around that the user can drop anywhere they want - except that it only really exists on the web and it's always up to date whenever you drop it somewhere!

As you can see, the VirtualFileDataObject class makes the whole scenario really easy and approachable - even if you're not an expert on shell interoperability. It's pretty snazzy, I'd say. :)

 

There's just one small problem...

If you tried the drag-and-drop version of the last sample above on a machine with a slow network connection, you probably noticed that the sample application became unresponsive as soon as you dropped the virtual file and didn't recover until the download completed. This is a natural consequence of the DoDragDrop method being synchronous and getting called from the UI thread (like it should be). In most scenarios, you probably won't notice this problem because generating the file's data is practically instantaneous. But when there's a delay, unresponsiveness is a possibility. The good news is that there's an official technique for solving this problem. The bad news is that it doesn't work for WPF apps. The good news is that I can show you how to make it work anyway.

But that's a topic for another blog post - one that I'll write in a week or so... :)

 

Updated 2017-04-20: The sample behavior is slightly different on Windows 10. The cause was hard to track down, but feedback and investigation have determined that this code and sample work correctly as-is on Windows 10 (just like on previous versions of Windows). What's different is how Windows 10 renders the mouse pointer during a drag of a virtual file when only DragDropEffects.Move is specified. Although previous versions of Windows would show the "move" icon whether or not the Shift key was down to force a "move" operation, Windows 10 shows the "no smoking" icon by default and only shows the "move" icon when Shift is held down. The lack of fallback from "copy" to "move" on Windows 10 makes it seem like the virtual file drag part of the sample is broken, though it works fine when the modifier key is used. The workaround is to use the modifier key or pass DragDropEffects.Copy.

Two birds, one stone [Silverlight/WPF Data Visualization Development Release 2 and DataVisualizationDemos update]

The October 2009 release of the Silverlight Toolkit came out on Monday and the Data Visualization assembly includes some nice updates. I discussed the details of the new release then and promised to revise my samples to run on the new bits. While I anticipated doing things separately, it turned out to be easier to do everything at once. Here goes! :)

 

Silverlight/WPF Data Visualization Development Release 2

In the grand tradition of Data Visualization Development Releases, I've updated things to match the most recently released Toolkit code. In this case, that's the Silverlight Toolkit, so the code in the new Development Release is identical to what just went out with the Silverlight Toolkit. That means there's a bunch of new code for WPF here! People using Data Visualization on WPF can take advantage of the latest changes by updating to the binaries included with this Development Release or by compiling the corresponding code themselves. The release notes detail all the changes; there's nothing to call out here.

[Click here to download the SilverlightWpfDataVisualization solution including complete source code and pre-compiled binaries for both platforms.]

 

DataVisualizationDemos Sample Project Updated

The DataVisualizationDemos application is a collection of all the Data Visualization samples I've posted to my blog. Like the Data Visualization assembly itself, the demo application runs on Silverlight and WPF and shares the same code and XAML across both platforms. Not only is it a convenient way to look at a variety of sample code, it also has links back to the relevant blog posts for more detail about each sample.

Click here to download the complete source code for the cross-platform DataVisualizationDemos sample application.

 

Notes:

  • New to this release of the DataVisualizationDemos is my simple Column annotations sample.
  • I've added out-of-browser support to the Silverlight version of DataVisualizationDemos so users can easily install it and/or run it outside the browser.
  • Both flavors of DataVisualizationDemos now take advantage of custom icons for a little bit of added flair: DataVisualizationDemos icon
  • Because this version of the Data Visualization assembly contains a breaking change, the DataVisualizationDemos project can no longer use the assembly that shipped with the WPF Toolkit (or else both platforms wouldn't be able to share the same samples). Therefore, DataVisualizationDemos uses the WPF assembly from Data Visualization Development Release 2.
  • Which means TreeMap (added after the WPF Toolkit release) can now be part of the WPF version of DataVisualizationDemos!
  • If you're doing cross-platform development, sometimes you'll come across a control that lives in two different places. When that happens, it's hard to share the same XAML for both platforms - unless you know a trick! My usual technique for this is to declare my own same-named subclass in code (which automatically resolves to the right platform-specific class thanks to the namespace):

    public class DockPanel : System.Windows.Controls.DockPanel
    {
    }
    

    And then use my "custom" control (after adding the corresponding XML namespace declaration):

    <local:DockPanel ... />

    That works swell most of the time - except for when the class is sealed like Viewbox is on Silverlight... So I came up with a slight tweak of this strategy that solves the problem:

    #if SILVERLIGHT
        // Silverlight's Viewbox is sealed; simulate it with a ContentControl wrapper
        public class Viewbox : ContentControl
        {
            public Viewbox()
            {
                Template = (ControlTemplate)XamlReader.Load(@"
                    <ControlTemplate
                        xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
                        xmlns:controls=""clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"">
                        <controls:Viewbox>
                            <ContentPresenter/>
                        </controls:Viewbox>
                    </ControlTemplate>");
            }
        }
    #else
        public class Viewbox : System.Windows.Controls.Viewbox
        {
        }
    #endif
    

    And then just use it the same as above:

    <local:Viewbox ... />

 

The latest Data Visualization release has some nice improvements - I hope these two updates help people understand the new functionality and make it even easier to upgrade!

Silverlight (and WPF) Data Visualization classes unsealed [Silverlight Toolkit October 2009 release now available!]

We've just published the October 2009 release of the Silverlight Toolkit as part of today's .NET 4 and Visual Studio 2010's Beta 2 release! One of the big things we've done with this release of the Toolkit is to add rich support for Visual Studio 2010's vastly improved Silverlight design-time experience. In fact, the new VS 2010 design-time experience has gotten so good that some developers have stopped using Blend altogether! :) I encourage everyone to have a look at the live samples for the latest release of the Silverlight 3 Toolkit, download the Toolkit installer, and try for yourself!

Other big news for this release is the introduction of comprehensive, WPF-compatible drag-and-drop support for Silverlight! Although this support doesn't extend outside the web browser (that would require changes to Silverlight itself), it enables full-fidelity drag-and-drop experiences within the browser using the same API that WPF users are already accustomed to. And if that wasn't enough, there are also a collection of drag-and-drop-friendly "wrapper controls" for common scenarios (ex: ListBox, TreeView, and DataGrid) that make it trivial to add support for drag-and-drop to an existing control. Dragging and dropping within a control (to re-order items) or between controls (to move items around) is now just a few lines of XAML away! (Note: No code changes necessary!) But wait, there's more: There's also a wrapper for Charting's DataPointSeries that enables drag-and-drop into and out of a live Chart control! This really needs to be seen to be believed, so please visit the "Drag and Drop" page of the public samples for a great example of this. Then go read Jafar's post about the new drag/drop support for all the juicy details!

Note: The October 09 release of the Silverlight Toolkit includes binaries for Silverlight 3 only. Now that Silverlight 3 has been out for a few months and is fully backward-compatible with all Silverlight 2 applications, we expect that everyone has upgraded from Silverlight 2 and are therefore no longer actively developing the Toolkit for Silverlight 2. Of course, if some of you have a specific need for Silverlight 2 Toolkit bits, previous releases continue to be available to download from CodePlex!

 

With the introductory stuff out of the way, let's move on to the details of changes to the Data Visualization assembly and the corresponding improvements to Silverlight and WPF Charting. My recent post on Data Visualization Development Release 1 has already discussed most of these changes at length, so I'm just going to include the change descriptions here. For more detail on the motivation behind these changes or their implications for current and future possibilities, please refer back to that post.

Notable Changes

Unsealed (i.e., removed the "sealed" modifier from) all core Data Visualization classes. Although we aren't yet completely settled on the public-facing API for Data Visualization and reserve the right to make breaking changes in the future, these classes are being unsealed now to help simplify a wide variety of user scenarios that are being actively developed and that are cumbersome without the ability to subclass (without needing to create a private build of the assembly solely for the purpose of unsealing these classes). Other changes were kept to a minimum, but a couple of methods have been changed to protected virtual for consistency and/or convenience as well as some tweaks that resulted due to new code analysis warnings due to explicit interface implementations in an unsealed class.

Introduced ISeries interface to Charting as "base interface" for all Series. This allows users to write ItemsControl-based Series which will automatically leverage all of the ItemsControl infrastructure for creating points, tracking data changes, etc. and also gives us a safe root for a future 3D series hierarchy. As part of this change, some interfaces have been cleaned up a bit (IStyleDispenser, ISeriesHost) and others have been created (IStyleDispenser.StylesChanged event). Also, some public methods with little justification have been removed/made private/moved lower (Chart.Refresh, Chart.ResetStyles, StyleDispenser.ResetStyles) and some vestigial code has been removed (ISeriesHost.GlobalSeriesIndexesInvalidated).

Various usability improvements. Updated Series to look for "LegendItemStyle" in their ResourceDictionary for increased customizability. Added Owner property to LegendItem pointing to owning Series instance to simplify LegendItem-based user scenarios. Added ActualDataPointStyle and ActualLegendItemStyle properties and used Bindings to automatically propagate changes to the right places. (Aside: This fixes a bug that was reported against the WPF Toolkit as I was making the change!) Moved code so that PieSeries now has the DataPointStyle property like the other Series. Updated LegendItem default Template to include standard TemplateBindings for Background/BorderBrush/BorderThickness for more friendly designer experience.

Breaking Changes

Renamed Charting's StylePalette to Palette (for clarity) AND changed its type to IEnumerable<ResourceDictionary> (from IEnumerable<Style>) for a significant flexibility boost. Performed related renamings (many internal/private): IStyleDispenser->IResourceDictionaryDispenser, StylePalette->ResourceDictionaryCollection, StyleDispensedEventArgs->ResourceDictionaryDispensedEventArgs, StyleDispenser->ResourceDictionaryDispenser, StyleEnumerator->ResourceDictionaryEnumerator.

Most notably, this change makes it possible to associate MULTIPLE things with a palette entry and enables designers to easily and flexibly customize things like the LineSeries PolyLineStyle in the Palette. Additionally it enables the use of DynamicResource (currently only supported by the WPF platform) to let users customize their DataPointStyle without inadvertently losing the default/custom Palette colors. (Note: A very popular request!) Thanks to merged ResourceDictionaries, this also enables the addition of arbitrary resources at the Palette level (like Brushes) which can then be referenced by DataPoints, etc..

Changed return value of Charting's IAxis.GetPlotAreaCoordinate from UnitValue? to UnitValue to better support custom Axis implementations. Specifically, some numeric axis types (logarithmic axis, for example) don't support all numeric values and need a way to indicate that certain values (ex: <= 0 for logarithmic) are "not supported" for plotting. This was previously done by returning a null value, but now the code should return a UnitValue with Value=double.NaN. Convenience method UnitValue.NaN has been added to create such values easily. Because the Series implementations already need to handle NaN values, this change collapses two different edge cases into one and simplifies the code accordingly. Added code to Series to handle this situation by hiding (via Visibility=Collapsed) DataPoints on coordinates that are not valid.

One notable consequence of this change is that the Visibility of DataPoints is now controlled by the Series and will be set to Visible or Collapsed as necessary. Therefore, any customizations that directly set this property may no longer work, but there are other simple ways of achieving the same effect and this change is not expected to cause any difficulty. For example, the "Sparkline" demo of the samples project was affected by this change because it provided a custom DataPointStyle that set Visibility to Collapsed. The fix is not only trivial, but an improvement: change the Style to specify a null Template instead!

Other Changes

Remove unnecessary code. Moved duplicated DependencyProperties IRangeAxis DependentRangeAxis and IAxis IndependentAxis from ColumnSeries and BarSeries into common base class ColumnBarBaseSeries. Moved duplicated DependencyProperties IRangeAxis DependentRangeAxis and IAxis IndependentAxis from AreaSeries and LineSeries into common base class LineAreaBaseSeries. Made similar changes for methods OnApplyTemplate and UpdateDataPoint and half of UpdateShape.

Simplified default Palette Brushes by removing ScaleTransform and TranslateTransform and replacing with RadialBrush. The on-screen visuals remain the same, but the XAML is considerably smaller and simpler - and should be a bit quicker to render as well!

Various other small changes.

 

Of the two breaking changes, only the rename to Palette is likely to affect most people. Fortunately, converting existing code/XAML is really quite simple - which you can see as I recycle the example I gave previously.

The old way:

<chartingToolkit:Chart Title="Statistics (Custom Palette)">
    <chartingToolkit:Chart.StylePalette>
        <visualizationToolkit:StylePalette>
            <Style TargetType="Control">
                <Setter Property="Background" Value="Blue"/>
            </Style>
            <Style TargetType="Control">
                <Setter Property="Background" Value="Green"/>
            </Style>
            <Style TargetType="Control">
                <Setter Property="Background" Value="Red"/>
            </Style>
        </visualizationToolkit:StylePalette>
    </chartingToolkit:Chart.StylePalette>
    ...
</chartingToolkit:Chart>

And the new way (with changes highlighted):

<chartingToolkit:Chart Title="Statistics (Custom Palette)">
    <chartingToolkit:Chart.Palette>
        <visualizationToolkit:ResourceDictionaryCollection>
            <ResourceDictionary>
                <Style x:Key="DataPointStyle" TargetType="Control">
                    <Setter Property="Background" Value="Blue"/>
                </Style>
            </ResourceDictionary>
            <ResourceDictionary>
                <Style x:Key="DataPointStyle" TargetType="Control">
                    <Setter Property="Background" Value="Green"/>
                </Style>
            </ResourceDictionary>
            <ResourceDictionary>
                <Style x:Key="DataPointStyle" TargetType="Control">
                    <Setter Property="Background" Value="Red"/>
                </Style>
            </ResourceDictionary>
        </visualizationToolkit:ResourceDictionaryCollection>
    </chartingToolkit:Chart.Palette>
    ...
</chartingToolkit:Chart>

It's pretty clear that once you've done this once, it'll be easy to do anywhere else your project requires. I explained the motivations for this change previously, so I won't repeat myself here - I just wanted to call out how straightforward the upgrade is expected to be.

 

Clearly, the big news for Data Visualization is the unsealing of the primary charting classes! Because I went into great detail on this earlier, I won't spend a lot of time on that here. Instead, I'd like to call out a particularly timely and relevant use of the new subclassing ability: Cory Plotts' LogarithmicAxis implementation for WPF and Silverlight! What's great about what he's done is that logarithmic axis support is one of our most requested features, and something we haven't had a chance to implement yet. I've always hoped that somebody in the community would be able to step up and share something here, so I was really excited to see Cory's blog post. If you're one of the users who's been waiting for a logarithmic axis, please have a look at Cory's implementation and see if it does what you need!

Aside: You might be wondering why we haven't gotten to logarithmic axis ourselves... Well, as you may be aware, we've been operating under severe resource constraints from the beginning, and that forces us to try to choose our investments carefully. When we're trying to decide between two features and one of them constitutes a change to the core of the Charting framework while the other is something that derives from an existing class to build on top of the framework, we'll tend to make the core framework change and hope that the community is able to help with the subclassing change. Honestly, this seems like the right balance to me and is a large part of why we're unsealing now even though the Charting APIs aren't completely set in stone.

Along similar lines, I encourage people who have been wanting to annotate their PieSeries charts to have a look at the fantastic work Bea Stollnitz has done: Part 1, Part 2, Part 3. Bea built on top of the sealed Charting hierarchy using some pretty clever tricks and techniques. But now that we've unsealed, it's my hope that she'll be able to take advantage of that to spend more time working on the great features she's adding and less time trying to jump through artificial hoops. :)

 

There are two other things I'd like to call out here:

As always, 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 because there's more context and history there.

 

Thanks very much for your interest in Silverlight and WPF Data Visualization - I hope you like the improvements!

Brevity is the soul of wit [A free, easy, custom URL shortening solution for ASP.NET!]

I tend to write pretty long blog post titles.

There, I said it.

That's the first step, right? Admitting the problem? Except I don't usually think it's a problem... :) Nobody's going to type post URLs, anyway - long ones are really only an issue in one scenario: Twitter. And there are already plenty of solutions for URL shortening, so why does the world need another one?

It probably doesn't. But I wanted one anyway. In particular, I wanted something ridiculously simple to manage that I had complete control over and that didn't depend on how my web site host configured their server. I wanted to be sure I could use human-readable aliases and never need to worry about namespace collisions (a problem with existing services because all the good names have been taken). Besides, I don't see the point in relying on someone else (or some other web site) to do something I can do for myself pretty easily.

 

So as long as I'm writing my own URL shortener, I might as well give it a special power or something, right? Well, one thing that seemed pretty useful (to me and you) was an easy way to list all the supported aliases. Therefore, when you navigate to the root of the redirect namespace on my web site, that's just what you'll get:

Custom URL shortener in action

 

Now, when I want to point to a blog or sample of mine, instead of linking to it like this:

http://blogs.msdn.com/delay/archive/2009/07/19/my-new-home-page-enhanced-updated-collection-of-great-silverlight-wpf-data-visualization-resources.aspx

I can link to it like this:

http://cesso.org/r/DVLinks

What's more, because these redirects are of the 302/Found variety, I can update where they go when new content becomes available without having to change any existing content. This is something I've wanted for my links post(s) ever since I posted the first version! Problem solved: from now on, the DVLinks alias will always take you to my most recent post of Data Visualization links.

Some of the aliases above use MixedCase and some use ALLCAPS - there's actually meaning behind that. When I create an alias I intend to be widely useful and expect to use more than once, I'll use mixed case like I did with DVLinks. But when I'm creating an alias just so I can post it to Twitter, I'll use all caps like I did for TWITTER. This way, it's easy to browse my list of aliases and pick out the particularly interesting ones.

 

Here's the complete code for the IHttpModule-based URL shortener I wrote last night. It's standard ASP.NET and doesn't use any .NET 3.5 features, so it should work fine on pretty much any ASP.NET server.

public class UrlShortener : IHttpModule
{
    /// <summary>
    /// The prefix (directory/namespace) for all redirect requests.
    /// </summary>
    private const string RedirectPrefix = "~/r/";

    /// <summary>
    /// Maintains a mapping from alias to Uri for all redirect entries.
    /// </summary>
    private IDictionary<string, Uri> _aliases =
         new SortedDictionary<string, Uri>(StringComparer.OrdinalIgnoreCase);

    /// <summary>
    /// Initializes the IHttpModule instance.
    /// </summary>
    /// <param name="context">HttpApplication instance.</param>
    public void Init(HttpApplication context)
    {
        // Populate the alias mappings
        AddAlias("ChartBuilder", "https://dlaa.me/Samples/ChartBuilder/");
        AddAlias("DVLinks",      "http://blogs.msdn.com/delay/archive/2009/07/19/my-new-home-page-enhanced-updated-collection-of-great-silverlight-wpf-data-visualization-resources.aspx");
        AddAlias("SLHash",       "https://dlaa.me/Samples/ComputeFileHashes/");
        AddAlias("TWITTER",      "http://blogs.msdn.com/delay/archive/2009/10/13/if-all-my-friends-jumped-off-a-bridge-apparently-i-would-too-just-created-a-twitter-account-davidans.aspx");

        context.BeginRequest += new EventHandler(HttpApplication_BeginRequest);
    }

    /// <summary>
    /// Adds an alias/Uri pair and validates it.
    /// </summary>
    /// <param name="alias">Alias to add.</param>
    /// <param name="uri">Uri to associate with the alias.</param>
    private void AddAlias(string alias, string uri)
    {
        // Validate the URI when adding it
        _aliases.Add(alias, new Uri(uri, UriKind.Absolute));
    }

    /// <summary>
    /// Handles the HttpApplication's BeginRequest event.
    /// </summary>
    /// <param name="source">HttpApplication source.</param>
    /// <param name="e">Event arguments.</param>
    private void HttpApplication_BeginRequest(object source, EventArgs e)
    {
        // Check if the request is a redirect attempt
        HttpApplication context = (HttpApplication)source;
        string requestPath = context.Request.AppRelativeCurrentExecutionFilePath;
        if (requestPath.StartsWith(RedirectPrefix, StringComparison.OrdinalIgnoreCase))
        {
            // Extract the alias
            string alias = requestPath.Substring(RedirectPrefix.Length);
            if (_aliases.ContainsKey(alias))
            {
                // Known alias; redirect the user there
                context.Response.Redirect(_aliases[alias].ToString());
            }
            else
            {
                // Invalid alias; write a simple list of all known aliases
                using (HtmlTextWriter writer = new HtmlTextWriter(context.Response.Output, ""))
                {
                    writer.RenderBeginTag(HtmlTextWriterTag.Html);
                    writer.RenderBeginTag(HtmlTextWriterTag.Head);
                    writer.RenderBeginTag(HtmlTextWriterTag.Title);
                    writer.Write("Supported Aliases");
                    writer.RenderEndTag();
                    writer.RenderEndTag();
                    writer.RenderBeginTag(HtmlTextWriterTag.Body);
                    foreach (string key in _aliases.Keys)
                    {
                        writer.AddAttribute(HtmlTextWriterAttribute.Href, _aliases[key].ToString());
                        writer.RenderBeginTag(HtmlTextWriterTag.A);
                        writer.Write(key);
                        writer.RenderEndTag();
                        writer.WriteBreak();
                        writer.WriteLine();
                    }
                    writer.RenderEndTag();
                    writer.RenderEndTag();
                }
                context.Response.End();
            }
        }
    }

    /// <summary>
    /// Disposes of resources.
    /// </summary>
    public void Dispose()
    {
    }
}

To enable this URL shortener for IIS 7, just save the code above to a .CS file, put that file in the App_Code directory at the root of your web site, and register it in web.config with something like the following:

<?xml version="1.0"?>
<configuration>
    <system.webServer>
        <modules>
            <add name="UrlShortener" type="UrlShortener"/>
        </modules>
    </system.webServer>
</configuration>

(For IIS 6, you'll need to change system.webServer to system.web and modules to httpModules - otherwise it's just the same.)

Tags: Technical

If all my friends jumped off a bridge, apparently I would, too [Just created a Twitter account: DavidAns]

Until today, I'd never gotten around to signing up for Twitter. After all, there are only so many hours in the day... But then Pete Brown (or should I say Pete_Brown??) stopped by my office for a quick visit and the topic of Twitter came up. Pete explained that he thought that Twitter would be a good way to help get the word out about Charting - and interact more directly with customers in general.

Sigh... he had me at "charting". So I've finally created a Twitter account: I'm DavidAns.

To set expectations appropriately, it is not intended to be a personal account (just like this is not a personal blog). You're not going to see posts about what I ate for breakfast, what bar I'm drinking at, or what I think about the guy who cut me off in traffic. Instead, my intent is to use Twitter like my blog - as a way to share cool coding tricks, .NET development tidbits, handy tools, and the like.

That said, I realize Twitter is a more socially interactive environment than a blog, so I can't promise everything I write will be 100% educational. :) But I'll definitely try to bias things way more toward signal than noise.

So if you're on Twitter and have a question for me you've been dying to ask, this is your lucky day. If not, don't worry - I'll continue to blog and answer any questions that come up there!