The blog of dlaa.me

A preview of upcoming Charting changes [Silverlight/WPF Data Visualization Development Release 1]

It was about two months ago that I posted about Silverlight/WPF Data Visualization Development Release 0. At the time, I explained how I was hoping to do occasional, out-of-band releases of the Data Visualization assembly that's part of the Silverlight Toolkit and WPF Toolkit in order to give people an early glimpse of upcoming changes and maybe get a bit of feedback along the way.

It's that time again...

 

Announcing Silverlight/WPF Data Visualization Development Release 1!

 

As usual, there have been plenty of distractions these past weeks to keep us, um..., distracted - but we've still managed to make some significant architectural tweaks that I think people are going to appreciate. Maybe a little less so in the short term because there are a couple of breaking changes, but definitely in the long term because these changes enable some very interesting scenarios and should do a lot to make it easier to develop with the Data Visualization framework.

Please bear in mind that this is just a development release, so it hasn't gone through the same level of scrutiny that our official releases get. Therefore, there may be some behavioral anomalies - and if there are, I apologize in advance. So if you do find an issue, please contact me (by leaving a comment below or by clicking the Email link on my blog) as I'd love to fix whatever I can before the next official release!

Because I'm trying to keep the cost of doing Development Releases down, I'm not doing my usual long-winded write up of new features. Instead, I'm going to include the notable changeset descriptions from our source control system along with a few brief notes. If that isn't enough detail to get you excited, then you're probably not the target audience for these Development Releases. ;)

 

Notable Changes (from the check-in comments)

Unseal (i.e., remove the "sealed" modifier) from all Data Visualization classes that were previously sealed. 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.

While this is the most controversial change, I think it's the right thing for us to do now. The concern is that people will take our unsealing as encouragement to go off and subclass everything - and then be disappointed/frustrated when they need to change their code after we make some subsequent breaking change to the API. And I sympathize with this concern - so if you're worried about this happening to you, just pretend everything's still sealed for now. The official indication that the API has stabilized will be when the classes in the Data Visualization assembly change quality bands from Preview (their current band) to Stable. That's not happening yet, so please be aware that there's a certain amount of risk when making the decision to build on the current API.

That said, I'm of the opinion that we have little to lose with this because the decision is entirely in the customers' hands. If you don't want the risk, don't take it. But if you're doing something cool with Charting and wish you could subclass to avoid a bunch of additional effort, then this change is for you. :) For instance, Bea Stollnitz is doing some cool stuff with adding annotations to PieSeries - and she's basically called us out in that post for making her task harder because our classes are sealed. Well, discouraging folks from using the Data Visualization assembly is the last thing I'm trying to do - so we're unsealing now to help make the platform as friendly as possible.

And while you're busy taking advantage of the new ability to subclass, I fully expect there will be places we haven't exposed all the extensibility points people want. When that happens, please let me know, and we'll look into addressing that oversight in a future release. Think of it as the "You scratch our backs, we'll scratch yours" model of software development... :)

 

Introduce ISeries interface to Charting as "base interface" for all Series. This will allow 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). Aside from adjusting for renamed/deleted functionality, all tests continue to pass as-is.

There are two pretty big wins from this change - so go back and re-read that paragraph if you weren't paying attention. We've prototyped both an ItemsControl-based PieSeries and a (WPF-only) Viewport3D-based PieSeries and the results are very promising! Simply by changing our base Series contract from a class to an interface, we give people with simple needs the ability to leverage the existing ItemsControl framework and significantly decrease the amount of code they need to understand and interact with. (Aside: There have even been suggestions to change our existing series over to use this model!) And the benefits for 3D are also compelling - though further off in the future due to a variety of open issues and unanswered questions. I'm not going to dwell on the implications of this change more right now, but there are obviously some cool possibilities that I'd love to see folks start to explore. (Hint, hint, friends of Charting...)

 

Rename Charting's StylePalette to Palette (for clarity) AND change its type to IEnumerable<ResourceDictionary> (from IEnumerable<Style>) for a significant flexibility boost. Perform related renamings (many internal/private): IStyleDispenser->IResourceDictionaryDispenser, StylePalette->ResourceDictionaryCollection, StyleDispensedEventArgs->ResourceDictionaryDispensedEventArgs, StyleDispenser->ResourceDictionaryDispenser, StyleEnumerator->ResourceDictionaryEnumerator. Modify all code, comments, tests, samples, themes, etc. accordingly.

Most notably, this change gives us the ability to associate MULTIPLE things with a palette entry and enables designers to easily and flexibly customize things like 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. Due to merged ResourceDictionaries, this also enables the addition of arbitrary resources at the Palette level (like Brushes) which can be referenced by DataPoints, etc..

Also: Simplify default Background Brushes by removing ScaleTransform and TranslateTransform and replacing with RadialBrush properties, and more...

This is going to be the most painful change for existing users of Data Visualization - sorry! If you've ever customized a StylePalette, your XAML is going to need to change. However, the opportunities this change opens up seem sufficiently compelling that we've decided to make it now (while we still have the freedom to do so). The good news is that the migration is really quite simple - and once you've seen it done once, you can mindlessly apply the change everywhere it's needed.

To prove it, here's a sample of the "old" way from my original Charting Introduction post:

<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 here's that same XAML converted to the "new" way of doing things (I've highlighted the changes):

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

Yes, the new syntax is a little bit longer, no doubt about that - but the ability to associate multiple resources with a single palette entry addresses some very tricky problems we've been avoiding till now. And the DynamicResource benefits for WPF address the single biggest complaint people have had with StylePalette: Why should I have to redefine the entire color palette when all I want to do is provide a new template? This is a really powerful shift, and something I'll probably spend more time showing off in a future blog post.

 

Update Series to look for "LegendItemStyle" in their ResourceDictionary for increased customizability. Add Owner property to LegendItem pointing to owning series to simplify LegendItem-based user scenarios. Add ActualDataPointStyle and ActualLegendItemStyle properties and use 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 this change*!) Move code so that PieSeries now has the DataPointStyle property like the other Series. Update LegendItem default Template to include standard TemplateBindings for Background/BorderBrush/BorderThickness for more friendly designer experience.

Hey, look, we're already making use of the new ResourceDictionaryCollection! :) The other two big improvements here are the comprehensive use of bindings for the DataPointStyle property that fixes an issue a few customers have bumped into (it's confusing unless you know exactly what's going on) and the addition of DataPointStyle to PieSeries which is like the DynamicResource change in that it should do a lot to simplify things on the WPF platform.

 

Move unnecessarily duplicated DependencyProperties IRangeAxis DependentRangeAxis and IAxis IndependentAxis from ColumnSeries and BarSeries into common base class ColumnBarBaseSeries. Move unnecessarily duplicated DependencyProperties IRangeAxis DependentRangeAxis and IAxis IndependentAxis from AreaSeries and LineSeries into common base class LineAreaBaseSeries. Same for methods OnApplyTemplate and UpdateDataPoint and half of UpdateShape. Also remove an unnecessary override from CategoryAxis. No functional impact.

Less code, same features, no functional impact - 'nuff said.

 

[Please click here to download the complete SilverlightWpfDataVisualization solution (includes all source code and pre-compiled binaries for both platforms).]

 

Release Notes

  • When you add a project reference to the Data Visualization assembly on WPF, you also need to add a reference to WPFToolkit.dll or you'll get weird runtime errors because the Visual State Manager (VSM) isn't available.
  • The file structure of the ZIP archive remains the same (see my previous post for details).
  • Design-time assemblies are not part of the Development Releases because I don't expect the target audience to need them and because they add additional complexity.
  • I haven't updated my DataVisualizationDemos application for this unofficial release.

 

So there you have it: the second Silverlight/WPF Data Visualization Development Release in a nutshell. Though, in many ways, this is really the first release because the previous release didn't showcase upcoming changes like this one does (it mainly set the stage for future releases). I said before that these Development Releases are an experiment - so please let me know if you find them useful or if you'd all rather just wait for an official release and find out what's changed then. I'm hopeful that early access to the new code will be helpful to our early adopters and that we'll be able to incorporate their feedback to deliver an even more compelling, more reliable official release.

So this is me trying to do my part; the ball is in your court now, Charting fans. So, what's it going to be then, eh?

When framework designers outsmart themselves [How to: Perform streaming HTTP uploads with .NET]

As part of a personal project, I had a scenario where I expected to be doing large HTTP uploads (ex: PUT) over a slow network connection. The typical user experience here is to show a progress bar, and that's exactly what I wanted to do. So I wrote some code to start the upload and then write to the resulting NetworkStream in small chunks, updating the progress bar UI after each chunk was sent. In theory (and my test harness), this approach worked perfectly; in practice, it did not...

What I saw instead was that the progress bar would quickly go from 0% to 100% - then the application would stall for a long time before completing the upload. Which is not a good user experience, I'm afraid. I'll show what I did wrong in a bit, but first let's take a step back to look at the sample application I've written for this post.

 

The core of the sample app is a simple HttpListener that logs a message whenever it begins an operation, reads an uploaded byte, and finishes reading a request:

// Create a simple HTTP listener
using (var listener = new HttpListener())
{
    listener.Prefixes.Add(_uri);
    listener.Start();

    // ...

    // Trivially handle each action's incoming request
    for (int i = 0; i < actions.Length; i++)
    {
        var context = listener.GetContext();
        var request = context.Request;
        Log('S', "Got " + request.HttpMethod + " request");
        using (var stream = request.InputStream)
        {
            while (-1 != stream.ReadByte())
            {
                Log('S', "Read request byte");
            }
            Log('S', "Request complete");
        }
        context.Response.Close();
    }
}
Aside: The code is straightforward, but it's important to note that HttpListener is only able to start listening when run with Administrator privileges (otherwise it throws "HttpListenerException: Access is denied"). So if you're going to try the sample yourself, please remember to run it from an elevated Visual Studio or Command Prompt instance.

 

With our test harness in place, let's start with the simplest possible code to upload some data:

/// <summary>
/// Test action that uses WebClient's UploadData to do the PUT.
/// </summary>
private static void PutWithWebClient()
{
    using (var client = new WebClient())
    {
        Log('C', "Start WebClient.UploadData");
        client.UploadData(_uri, "PUT", _data);
        Log('C', "End WebClient.UploadData");
    }
}

Here's the resulting output from the Client and Server pieces):

09:27:07.72 <C> Start WebClient.UploadData
09:27:07.76 <S> Got PUT request
09:27:07.76 <S> Read request byte
09:27:07.76 <S> Read request byte
09:27:07.76 <S> Read request byte
09:27:07.76 <S> Read request byte
09:27:07.76 <S> Read request byte
09:27:07.76 <S> Request complete
09:27:07.76 <C> End WebClient.UploadData

The WebClient's UploadData method offers a super-simple way of performing an upload that's a great choice when it works for your scenario. However, all the upload data must be passed as a parameter to the method call, and that's not always desirable (especially for large amounts of data like I was dealing with). Furthermore, it's all sent to the server in arbitrarily large chunks, so our attempt at frequent progress updates isn't likely to work out very well. And while there's the OnUploadProgressChanged event for getting status information about an upload, WebClient doesn't offer the granular level of control that's often nice to have.

 

So WebClient is a great entry-level API for uploading - but if you're looking for more control, you probably want to upgrade to HttpWebRequest:

/// <summary>
/// Test action that uses a normal HttpWebRequest to do the PUT.
/// </summary>
private static void PutWithNormalHttpWebRequest()
{
    var request = (HttpWebRequest)(WebRequest.Create(_uri));
    request.Method = "PUT";
    Log('C', "Start normal HttpWebRequest");
    using (var stream = request.GetRequestStream())
    {
        foreach (var b in _data)
        {
            Thread.Sleep(1000);
            Log('C', "Writing byte");
            stream.WriteByte(b);
        }
    }
    Log('C', "End normal HttpWebRequest");
    ((IDisposable)(request.GetResponse())).Dispose();
}

Aside from the Sleep call I've added to simulate client-side processing delays, this is quite similar to the code I wrote for my original scenario. Here's the output:

09:27:08.78 <C> Start normal HttpWebRequest
09:27:09.79 <C> Writing byte
09:27:10.81 <C> Writing byte
09:27:11.82 <C> Writing byte
09:27:12.83 <C> Writing byte
09:27:13.85 <C> Writing byte
09:27:13.85 <C> End normal HttpWebRequest
09:27:13.85 <S> Got PUT request
09:27:13.85 <S> Read request byte
09:27:13.85 <S> Read request byte
09:27:13.85 <S> Read request byte
09:27:13.85 <S> Read request byte
09:27:13.85 <S> Read request byte
09:27:13.85 <S> Request complete

Although I've foreshadowed this unsatisfactory result, maybe you can try to act a little surprised that it didn't work the way we wanted... :) The data bytes got written at 1 second intervals over the span of 5 seconds to simulate a gradual upload from the client - but on the server side it all arrived at the same time after the client was completely finished making its request. This is exactly the behavior I was seeing in my application, so it's nice that we've managed to reproduce the problem.

But what in the world is going on here? Why is that data sitting around on the client for so long?

 

The answer lies in the documentation for the AllowWriteStreamBuffering property (default value: True):

Remarks
When AllowWriteStreamBuffering is true, the data is buffered in memory so it is ready to be resent in the event of redirections or authentication requests.

Notes to Implementers:
Setting AllowWriteStreamBuffering to true might cause performance problems when uploading large datasets because the data buffer could use all available memory.

In trying to save me from the hassle of redirects and authentication requests, HttpWebRequest has broken my cool streaming scenario. :( Fortunately, it's easy to fix - just set the property to False, right?

Wrong; that'll get you one of these:

ProtocolViolationException: When performing a write operation with AllowWriteStreamBuffering set to false, you must either set ContentLength to a non-negative number or set SendChunked to true.

Okay, so we need to set one more property before we're done. Fortunately, the choice was easy for me - my target server didn't support chunked transfer encoding, so choosing to set ContentLength was a no-brainer. The only catch is that you need to know how much data you're going to upload before you start - but that's probably true most of the time anyway! And I think ContentLength is a better choice in general, because the average web server is more likely to support it than chunked encoding.

 

Making the highlighted changes below gives the streaming upload behavior we've been working toward:

/// <summary>
/// Test action that uses an unbuffered HttpWebRequest to do the PUT.
/// </summary>
private static void PutWithUnbufferedHttpWebRequest()
{
    var request = (HttpWebRequest)(WebRequest.Create(_uri));
    request.Method = "PUT";
    // Disable AllowWriteStreamBuffering allows the request bytes to send immediately
    request.AllowWriteStreamBuffering = false;
    // Doing nothing else will result in "ProtocolViolationException: When performing
    // a write operation with AllowWriteStreamBuffering set to false, you must either
    // set ContentLength to a non-negative number or set SendChunked to true.
    // The most widely supported approach is to set the ContentLength property
    request.ContentLength = _data.Length;
    Log('C', "Start unbuffered HttpWebRequest");
    using (var stream = request.GetRequestStream())
    {
        foreach (var b in _data)
        {
            Thread.Sleep(1000);
            Log('C', "Writing byte");
            stream.WriteByte(b);
        }
    }
    Log('C', "End unbuffered HttpWebRequest");
    ((IDisposable)(request.GetResponse())).Dispose();
}

Here's the proof - note how each byte gets uploaded to the server as soon as it's written:

09:27:14.86 <C> Start unbuffered HttpWebRequest
09:27:14.86 <S> Got PUT request
09:27:15.88 <C> Writing byte
09:27:15.88 <S> Read request byte
09:27:16.89 <C> Writing byte
09:27:16.89 <S> Read request byte
09:27:17.90 <C> Writing byte
09:27:17.90 <S> Read request byte
09:27:18.92 <C> Writing byte
09:27:18.92 <S> Read request byte
09:27:19.93 <C> Writing byte
09:27:19.93 <C> End unbuffered HttpWebRequest
09:27:19.93 <S> Read request byte
09:27:19.93 <S> Request complete

 

Like many things in life, it's easy once you know the answer! :) So if you find yourself wondering why your streaming uploads aren't so streaming, have a look at the AllowWriteStreamBuffering property and see if maybe that's the cause of your problems.

 

[Please click here to download a sample application demonstrating everything shown here.]

Tags: Technical

Get out of the way with the tray ["Minimize to tray" sample implementation for WPF]

"Minimize to tray" is a feature in some applications where minimizing the application removes its taskbar button and replaces it with a (much smaller) notification area icon. Clicking that notification icon restores the application's window - just like clicking its taskbar button would have done.

Well, I was considering using this functionality in a project I'm working on, so I looked to see what my options were. This feature didn't seem to be directly supported by WPF, so I searched the web a bit. What I found after a minute or two of searching was plenty of questions about how to implement this, a few suggestions, and not a lot else. So I figured I'd put something together myself and post it to my blog...

 

Here's the sample application I'm about to minimize - note the (boring) green square of an application icon:

About to minimize

And here's what things look like just after the application is minimized to the tray:

Notify balloon

Aside from just looking cool, that notification bubble serves an important purpose: it helps to draw the user's attention to the new icon in the notification area and calls out the application's custom minimize behavior. (To avoid being annoying, the bubble is only shown the first time the application is minimized each time it's run.) This is a nice convenience on most versions of Windows, but is pretty much necessary due to the new Windows 7 behavior of hiding notification area icons as quickly as possible. Without a helpful indication like this bubble, users might "lose" the application when it minimizes to the tray.

Aside: I understand why the Windows 7 team introduced this new behavior and I think it's perfectly reasonable. However, it has implications for this scenario, so it's good to think twice (or thrice!) about choosing to use "minimize to tray" in your application. The document I link to above has lots more guidance on the proper use of notification icons - interested parties are encouraged to review it!

After Windows 7 hides the notification icon for the application, it can be accessed via the "Show hidden icons" popup. Clicking on the hidden notification icon restores the application just like you'd expect:

Hidden by Windows 7
Aside: If you really dislike the new hiding behavior, it's easy to disable - just click the "Customize..." link (shown in the image above) and you'll be presented with a window that lets you disable this behavior for specific applications - or disable it for all of them with a single checkbox.

 

Notes:

  • I've implemented this functionality in a static MinimizeToTray class with a single Enable method that's super-easy to use. Just add a call to it in your Window's constructor and you're done:
    // Enable "minimize to tray" behavior for this Window
    MinimizeToTray.Enable(this);
    
  • WPF doesn't natively offer notify icon support, but there's nothing stopping us from using the NotifyIcon implementation that's part of WinForms! That's what MinimizeToTray does, so it's important to note that you'll need to change your project to add references to the System.Drawing and System.Windows.Forms .NET assemblies. (FYI, they're both part of the .NET Framework and are already present in the GAC (and NGEN-ed), so you don't need to worry about distributing them with your application.)
  • But it's also important to note that I've written my code such that MinimizeToTray doesn't cause either of these assemblies get loaded until the user first minimizes the application. This means neither assembly will impact the startup time of your application! (You can verify this by running the sample application, attaching the debugger, checking the module list to see that neither assembly is present, minimizing the application, and then noting that both assemblies just got loaded.)
  • I only use the most basic notify icon functionality in this sample, so the WinForms implementation is more than adequate for my needs. However, if you're looking for true WPF-style notification icon support, this implementation by Philipp Sumi looks quite promising (though I haven't tried it myself).

 

As a final favor, I'll ask that people please use "minimize to tray" wisely - just because you can minimize your application to the tray doesn't mean you should. :) Minimizing to the tray is something that's only meaningful for a limited set of scenarios - but if you find yourself in one of them, I hope MinimizeToTray is helpful!

 

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

 

The code is quite straightforward - here it is in its entirety:

/// <summary>
/// Class implementing support for "minimize to tray" functionality.
/// </summary>
public static class MinimizeToTray
{
    /// <summary>
    /// Enables "minimize to tray" behavior for the specified Window.
    /// </summary>
    /// <param name="window">Window to enable the behavior for.</param>
    public static void Enable(Window window)
    {
        // No need to track this instance; its event handlers will keep it alive
        new MinimizeToTrayInstance(window);
    }

    /// <summary>
    /// Class implementing "minimize to tray" functionality for a Window instance.
    /// </summary>
    private class MinimizeToTrayInstance
    {
        private Window _window;
        private NotifyIcon _notifyIcon;
        private bool _balloonShown;

        /// <summary>
        /// Initializes a new instance of the MinimizeToTrayInstance class.
        /// </summary>
        /// <param name="window">Window instance to attach to.</param>
        public MinimizeToTrayInstance(Window window)
        {
            Debug.Assert(window != null, "window parameter is null.");
            _window = window;
            _window.StateChanged += new EventHandler(HandleStateChanged);
        }

        /// <summary>
        /// Handles the Window's StateChanged event.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event arguments.</param>
        private void HandleStateChanged(object sender, EventArgs e)
        {
            if (_notifyIcon == null)
            {
                // Initialize NotifyIcon instance "on demand"
                _notifyIcon = new NotifyIcon();
                _notifyIcon.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);
                _notifyIcon.MouseClick += new MouseEventHandler(HandleNotifyIconOrBalloonClicked);
                _notifyIcon.BalloonTipClicked += new EventHandler(HandleNotifyIconOrBalloonClicked);
            }
            // Update copy of Window Title in case it has changed
            _notifyIcon.Text = _window.Title;

            // Show/hide Window and NotifyIcon
            var minimized = (_window.WindowState == WindowState.Minimized);
            _window.ShowInTaskbar = !minimized;
            _notifyIcon.Visible = minimized;
            if (minimized && !_balloonShown)
            {
                // If this is the first time minimizing to the tray, show the user what happened
                _notifyIcon.ShowBalloonTip(1000, null, _window.Title, ToolTipIcon.None);
                _balloonShown = true;
            }
        }

        /// <summary>
        /// Handles a click on the notify icon or its balloon.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event arguments.</param>
        private void HandleNotifyIconOrBalloonClicked(object sender, EventArgs e)
        {
            // Restore the Window
            _window.WindowState = WindowState.Normal;
        }
    }
}
Tags: WPF

Following up on some of the attention [Live sample posted and a *very* small tweak to the Html5Canvas source code!]

I posted the source code for a Silverlight implementation of the HTML 5 <canvas> API yesterday and some readers have been pretty interested in the concept/implementation. Thanks for all your comments and feedback!

Earlier today, kind user Fabien Ménager left a comment reporting an unhandled exception in the sample app and was generous enough to follow up by sending me the text of the exception. And as soon as I saw it, I knew the problem - both because of what it said and because of the language it was in!

Here, you give it a try:

System.FormatException: Le format de la chaîne d'entrée est incorrect.

Here's a hint: the operating system's culture settings for France (and many other countries) use a ',' for the decimal separator instead of '.' (as is used in the United States). For example, while Pi would be written as "3.14" with the US culture settings, it would be written as "3,14" with French culture settings. And while Html5Canvas doesn't accept any user input, it does parse some of the JavaScript input that makes up the script... Specifically, my sample application includes the following line:

context.fillStyle = "rgba(255, 127, 39, 0.8)";

The <canvas> specification allows for strings to be assigned to the fillStyle property, so it's necessary for Html5Canvas to parse that string. And it was the "0.8" alpha value that was the problem here. Attempting to parse that value with the user's culture settings will fail for a culture that uses ',' as the decimal separator. This is actually a common problem for text formats that are meant to be used in multiple cultures - and the typical solution is to require that the text always be written for some form of invariant culture (which typically adheres to the US's conventions). Therefore, the trivial fix was to parse that value according to the invariant culture (i.e., always use '.' as the decimal separator) instead of using the default parsing behavior which honors the user's settings.

Aside: The default behavior is nearly always what you want... except for very rarely when it's not... like this time! :)

Of course, if I'd reviewed the project's code analysis warnings a few days ago, I would have found this issue earlier. And while I usually do that for all my sample code, I skipped it this time because it was proof-of-concept code and because I knew the "Naming" analysis rules threw lots of warnings for the official <canvas> API (mainly because JavaScript tends to follow different conventions than .NET). So as punishment for my misdeed, I went ahead and fixed (or suppressed) all of the non-"Naming" code analysis warnings in the project!

To be clear, there is no functional change because of this - but now the code is just a little bit cleaner. :)

 

[Please click here to download the updated source code to Html5Canvas and its sample application.]

 

And while I was at it, I decided to post the sample application in runnable form just in case there were some people who wanted to watch the Hypotrochoid draw itself, but didn't want to compile the sample themselves.

 

[Click here or on the image below to run the sample application in your browser.]

Sample application in IE

 

It has been great to see all the interest in this project over the past couple of days - thanks very much for everyone's support, feedback, and kind words!!

Using one platform to build another [HTML 5's canvas tag implemented using Silverlight!]

Background

There's been some buzz about the upcoming HTML 5 standard over the past few months. In particular, there are a couple of new features that people are looking forward to. One of them is the new <canvas> element which introduces a 2D drawing API offering a pretty rich set of functionality. If you've worked with HTML much, you can probably imagine some of the things that become possible with this. In fact, those are probably some of the same things that Flash and Silverlight are being used for today! So some people have gone as far as to suggest HTML 5 could eliminate the need for Flash and Silverlight...

I didn't know much about the <canvas> element and I wanted to understand the situation better, so I did some research. The specification for <canvas> is available from two sources: the W3C and the WHATWG. (The two groups are working together, so the spec is the same on both sites.) The API is comprised of something close to 100 interfaces, properties, and methods and offers an immediate mode programming model that's superficially similar to the Windows GDI API. At a very high level, the following concepts are defined (though Philip Taylor did some investigation a while back which suggested that no browser offered complete support):

  • Paths and shapes (move/line/curve/arc/clipping/etc.)
  • Strokes and fills (using solid colors/gradients/images/etc.)
  • Images
  • Context save/restore
  • Transformations (scale/rotate/translate/etc.)
  • Compositing (alpha/blending/etc.)
  • Text (font/alignment/measure/etc.)
  • Pixel-level manipulation
  • Shadows

There's a variety of stuff there - and as I read through the list, I was struck by how much of it is natively supported by Silverlight. Pretty much all of it, in fact! :) So I thought it might be a fun and interesting learning experience to try implementing the HTML 5 <canvas> specification in Silverlight! What better way to understand the capabilities of <canvas> than to implement them, right?

So I went off and started coding... I didn't set out to support everything and I didn't set out to write the most efficient code (in fact, some of what's there is decidedly inefficient!) - I just set out to implement enough of the specification to run some sample apps and see how hard it was.

And it turns out to have been pretty easy! Thanks to Silverlight's HTML Bridge, I had no trouble creating a Silverlight object that looks just like a <canvas> to JavaScript code running on a web page. So similar, in fact, that I'm able to run some pretty cool sample applications on my own <canvas> simply by tweaking the HTML to instantiate a Silverlight <canvas> instead of the browser's <canvas>. And as a nice side effect, Internet Explorer "magically" gains support for the <canvas> tag!

Aside: Yes, I know I'm not the first person to add <canvas> support to IE. :)

 

Samples

I started off easy by working my way through the Mozilla Developer Center's Canvas tutorial and implementing missing features until each of the samples loaded and rendered successfully. Here are three of my favorite samples as rendered by Html5Canvas, my custom <canvas> implementation:

Mozilla samples

 

Aside: It's important to note that I did NOT change the JavaScript of these (or any other) samples - I just tweaked the HTML host page to load my <canvas> implementation. My goal was API- and feature-level parity; a Silverlight implementation of <canvas> that was "plug-compatible" with the existing offerings.

 

After that, I knew I just had to run Ben Joffe's Canvascape "3D Walker" because it bears a striking resemblance to one of the games I played when I was younger:

Canvascape

 

Aside: Be sure to look for the "Silverlight" context menu item I've included in the image above and in the next few screen shots to prove there's no trickery going on. :)

 

Next up was Bill Mill's Canvas Tutorial - and another shout-out to hours of misspent youth:

Canvas Tutorial

 

For something completely different, I got Bjoern Lindberg's Blob Sallad running:

Blob Sallad

 

Then it was on to Ryan Alexander's Chrome Canopy fractal zoomer (despite the note, it runs okay-ish in IE8 with my Silverlight <canvas>):

Chrome Canopy

 

Finally, I wanted to see how the 9elements HTML5 Canvas Experiment ran:

Canvas Experiment

 

Whew, What a rush - those apps are really cool! :)

 

But I still wanted a sample I could include with the source code download, so I also wrote a little app to exercise most of the APIs supported by Html5Canvas (thanks to Mozilla for the Hypotrochoid animation inspiration and Wikipedia for the equation). Here it is on IE:

Sample application in IE

 

And wouldn't it be nice to see how Html5Canvas compares to a "real" <canvas> implementation? Sure, here's my sample running on Firefox:

Sample application in Firefox

 

Details

So how does it actually work? Well, I'm obviously not modifying the browser itself, so redefining the actual <canvas> tag isn't an option. Instead, I've written a simple Html5Canvas.js file which gets referenced at the top of the HTML page:

<script type="text/javascript" src="Html5Canvas.js"></script>

Among other things, it defines the handy function InsertCanvasObject(id, width, height, action) function which can be used to insert a Silverlight <canvas> thusly:

<script type="text/javascript">
    InsertCanvasObject("mycanvas", 200, 200, onCanvasLoad);
</script>

That code inserts a Silverlight object running Html5Canvas.xap that looks just like the <canvas> tag would have. Yep, it's that easy!

And it brings up an important difference between Html5Canvas and a real <canvas>: Html5Canvas can be used only after the Silverlight object has loaded - whereas <canvas> is usable as soon as the body/window has loaded (which happens sooner). This distinction is important to keep in mind if you're converting an existing page, because it requires moving the initialization call from onload to the action parameter of InsertCanvasObject. Believe it or not, that's really the only big "gotcha"!

Aside: While I could think of a few ways to avoid exposing this difference to the developer, none of them were general enough to apply universally.

Other minor differences between Html5Canvas and <canvas> are that Silverlight doesn't natively support the relevant repeat modes used by createPattern to tile images (though I could implement them without much difficulty), Silverlight doesn't support the GIF image format for use with drawImage (also easily worked around), and the conventional technique of JavaScript feature-detection by writing if (canvas.toDataURL) { ... } doesn't work because Silverlight's HTML Bridge doesn't allow a method to be treated like a property (I could work around this, too, but the extra level of indirection was unnecessarily confusing for a sample app).

Finally, let me reiterate that I did not attempt to implement the complete <canvas> specification. Instead, I implemented just enough to support the first 5 (of 6 total) Mozilla sample pages as well as the handful of applications shown above. Specifically, I've implemented everything that's not in italics in the feature list at the beginning of this post. Thinking about what it would take to add the stuff that's not implemented: text and pixel-level manipulation are both directly supported by Silverlight and should be pretty easy. Shadows seem like a natural fit for Silverlight's pixel shader support (though I haven't played around with it yet). All that's left is layer compositing, which does worry me just a little... I haven't thought about it much, but this seems like another job for WriteableBitmap, perhaps.

 

[Please click here to download the complete source code to Html5Canvas and the sample application shown above.]

Be sure to set the Html5Canvas.Web project as the active project and run the TestPage.html within it to see the sample application.

 

Summary

Html5Canvas was a fun project that definitely accomplished its goal of bringing me up to speed on HTML 5's <canvas> element! The implementation proved to be fairly straightforward, though there were a couple of challenging bits that ended up being good learning opportunities. :) The code I'm sharing here is intended to be a proof-of-concept and is not optimized for performance. However, if there's interest in making broader use of Html5Canvas, I have some ideas that should really improve things...

I hope folks find this all as interesting as I did - and maybe next time you want to add browser features, you'll use Silverlight, too! ;)

Using your own code is its own kind of encouragement [How to: Automatically update the widths of ListView columns - updated!]

Some time ago, I blogged about a helper class to automatically adjust the size of the columns of a ListView's GridView to fit their contents. In my scenario, I was changing the value of the ListView's ItemsSource property and wanted the columns to update each time to accommodate the new data. The solution I described and posted at that time has been working fine for me ever since. However, a new project has just revealed a shortcoming: if the collection is one that implements INotifyCollectionChanged (such as ObservableCollection<T>), its contents will change and provide notice they've done so, but the column widths won't - because the original implementation only cared about changes to the ItemsSource property itself...

I've updated sample application I wrote for the previous post to include a new "Add Detail" button that dynamically adds an item to the current collection. As you can see from the image below, the second ListView (which doesn't take advantage of the new code) hasn't expanded the widths of its two columns in response to the new item. However, the bottom ListView which uses the IsAutoUpdatingColumnWidths attached DependencyProperty has automatically detected the new item and updated itself accordingly:

ListViewColumnWidthAutoUpdate sample

[Click here to download the ListViewColumnWidthAutoUpdate sample application.]

 

The new code seemed like it should be easy enough, then turned out to be a bit more involved than I expected. But the important point is that if you're already using IsAutoUpdatingColumnWidths, then you don't need to change your code at all! Just drop in the new implementation of my ListViewBehaviors class, and you're set!

 

Notes:

  • At the end of the day, this update is really just about detecting that the ItemsSource collection implements INotifyCollectionChanged and listening to its CollectionChanged event. The complications arise because IsAutoUpdatingColumnWidths is an attached DependencyProperty and therefore static - so there's no convenient place to associate instance-specific state. In particular, the necessary state is a list of collection-owning ListViews for reverse-mapping and a reference to the previous collection instance because the handler that DependencyPropertyDescriptor.AddValueChanged calls when a value changes only gets the new value (unlike the PropertyChangedCallback that's typically associated with DependencyPropertys).
  • So I introduced a simple ListViewState helper class and some code to maintain a list of known ListView instances an their associated INotifyCollectionChanged collections.
  • But we don't want to create "false" references to user objects and introduce leaks, so both of ListViewState's properties are backed by a WeakReference to allow them to be garbage collected when possible.
  • At this point, you might wonder if it's necessary to employ the WeakEvent pattern (which I've previously written about here)... Well, for now I've managed to convince myself that it is not - because unlike the scenario I originally wrote about, in this case the "backwards references" are all to a single static method. The way I'm thinking about it right now, that method can't really "leak" - or if it can, its size and scope are small enough that they're not worth worrying about. That said, I'm open to being proven wrong, so please don't be shy about correcting me if you know better! :)
  • The only other detail worth calling out is that it's necessary to hook and unhook the INotifyCollectionChanged event in two circumstances: when the ItemsSource property changes and when the IsAutoUpdatingColumnWidths property changes.

 

And that's all there is to it! Here's the complete, updated implementation of ListViewBehaviors:

/// <summary>
/// Class implementing useful behaviors for the ListView control.
/// </summary>
public static class ListViewBehaviors
{
    /// <summary>
    /// Updates the column widths of a GridView to fit the contents.
    /// </summary>
    /// <param name="gridView">GridView to update.</param>
    public static void UpdateColumnWidths(GridView gridView)
    {
        // Validate parameter
        Debug.Assert(null != gridView,
            "UpdateColumnWidths requires a non-null gridView parameter.");

        // For each column...
        foreach (var column in gridView.Columns)
        {
            // If this is an "auto width" column...
            if (double.IsNaN(column.Width))
            {
                // Set its Width back to NaN so it will auto-size
                column.Width = 0;
                column.Width = double.NaN;
            }
        }
    }

    /// <summary>
    /// Represents the IsAutoUpdatingColumnWidths attached DependencyProperty.
    /// </summary>
    public static readonly DependencyProperty IsAutoUpdatingColumnWidthsProperty =
        DependencyProperty.RegisterAttached(
            "IsAutoUpdatingColumnWidths",
            typeof(bool),
            typeof(ListViewBehaviors),
            new UIPropertyMetadata(false, OnIsAutoUpdatingColumnWidthsChanged));

    /// <summary>
    /// Gets the value of the IsAutoUpdatingColumnWidths property.
    /// </summary>
    /// <param name="listView">ListView for which to get the value.</param>
    /// <returns>Value of the property.</returns>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
        Justification = "Only applies to ListView instances.")]
    public static bool GetIsAutoUpdatingColumnWidths(ListView listView)
    {
        return (bool)listView.GetValue(IsAutoUpdatingColumnWidthsProperty);
    }

    /// <summary>
    /// Sets the value of the IsAutoUpdatingColumnWidths property.
    /// </summary>
    /// <param name="listView">ListView for which to set the value.</param>
    /// <param name="value">Value of the property.</param>
    [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
        Justification = "Only applies to ListView instances.")]
    public static void SetIsAutoUpdatingColumnWidths(ListView listView, bool value)
    {
        listView.SetValue(IsAutoUpdatingColumnWidthsProperty, value);
    }

    /// <summary>
    /// Change handler for the IsAutoUpdatingColumnWidths property.
    /// </summary>
    /// <param name="o">Instance for which it changed.</param>
    /// <param name="e">Change details.</param>
    private static void OnIsAutoUpdatingColumnWidthsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        // Get the ListView instance and new bool value
        var listView = o as ListView;
        Debug.Assert(!VirtualizingStackPanel.GetIsVirtualizing(listView),
            "VirtualizingStackPanel.IsVirtualizing should be False for " +
            "ListViewBehaviors.IsAutoUpdatingColumnWidths to work best.");
        if ((null != listView) && (e.NewValue is bool))
        {
            // Get a descriptor for the ListView's ItemsSource property
            var descriptor = DependencyPropertyDescriptor.FromProperty(ListView.ItemsSourceProperty, typeof(ListView));
            if ((bool)e.NewValue)
            {
                // Enabling the feature, so add the change handler
                descriptor.AddValueChanged(listView, ListViewItemsSourceValueChanged);

                // Create a ListViewState instance for tracking
                var listViewState = new ListViewState(listView);

                // Hook the CollectionChanged event (if possible)
                var iNotifyCollectionChanged = listView.ItemsSource as INotifyCollectionChanged;
                if (null != iNotifyCollectionChanged)
                {
                    iNotifyCollectionChanged.CollectionChanged +=
                        new NotifyCollectionChangedEventHandler(ListViewItemsSourceCollectionChanged);
                    listViewState.INotifyCollectionChanged = iNotifyCollectionChanged;
                }

                // Track the instance
                _listViewStates.Add(listViewState);
            }
            else
            {
                // Disabling the feature, so remove the change handler
                descriptor.RemoveValueChanged(listView, ListViewItemsSourceValueChanged);

                // Look up the corresponding ListViewState instance
                var listViewState = _listViewStates
                    .Where(lvs => listView == lvs.ListView)
                    .FirstOrDefault();
                if (null != listViewState)
                {
                    // Unhook the CollectionChanged event (if present)
                    var iNotifyCollectionChanged = listViewState.INotifyCollectionChanged;
                    if (null != iNotifyCollectionChanged)
                    {
                        iNotifyCollectionChanged.CollectionChanged -=
                            new NotifyCollectionChangedEventHandler(ListViewItemsSourceCollectionChanged);
                        listViewState.INotifyCollectionChanged = null;
                    }
                }

                // Remove the ListViewState (and any other unnecessary ones)
                foreach (var lvs in _listViewStates
                    .Where(lvs => (listView == lvs.ListView) || (null == lvs.ListView))
                    .ToArray()) // ToArray avoids modifying the current collection
                {
                    _listViewStates.Remove(lvs);
                }
            }
        }
    }

    /// <summary>
    /// Handles changes to the ListView's ItemsSource and updates the column widths.
    /// </summary>
    /// <param name="sender">Event source.</param>
    /// <param name="e">Event args.</param>
    private static void ListViewItemsSourceValueChanged(object sender, EventArgs e)
    {
        // Get a reference to the ListView
        var listView = sender as ListView;
        if (null != listView)
        {
            // Look up the corresponding ListViewState instance
            var listViewState = _listViewStates
                .Where(lvs => listView == lvs.ListView)
                .FirstOrDefault();
            if (null != listViewState)
            {
                // Unhook the CollectionChanged event (if present)
                var oldINotifyCollectionChanged = listViewState.INotifyCollectionChanged;
                if (null != oldINotifyCollectionChanged)
                {
                    oldINotifyCollectionChanged.CollectionChanged -=
                        new NotifyCollectionChangedEventHandler(ListViewItemsSourceCollectionChanged);
                    listViewState.INotifyCollectionChanged = null;
                }

                // Hook the new CollectionChanged event (if possible)
                var newINotifyCollectionChanged = listView.ItemsSource as INotifyCollectionChanged;
                if (null != newINotifyCollectionChanged)
                {
                    newINotifyCollectionChanged.CollectionChanged +=
                        new NotifyCollectionChangedEventHandler(ListViewItemsSourceCollectionChanged);
                    listViewState.INotifyCollectionChanged = newINotifyCollectionChanged;
                }

                // Get a reference to the ListView's GridView
                var gridView = listView.View as GridView;
                if (null != gridView)
                {
                    // Update the ListView's column widths
                    UpdateColumnWidths(gridView);
                }
            }
        }
    }

    /// <summary>
    /// Handles changes to the ListView's ItemsSource's CollectionChanged event and updates the column widths.
    /// </summary>
    /// <param name="sender">Event source.</param>
    /// <param name="e">Event args.</param>
    private static void ListViewItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // Update the corresponding GridView by looking up the ListView by ItemsSource
        foreach (var gridView in _listViewStates
                .Select(lvs => lvs.ListView)
                .Where(lv => (null != lv) && (sender == lv.ItemsSource) && (lv.View is GridView))
                .Select(lv => (GridView)(lv.View)))
        {
            // Update the ListView's column widths
            UpdateColumnWidths(gridView);
        }
    }

    /// <summary>
    /// Stores a collection of ListViewState instances.
    /// </summary>
    private static List<ListViewState> _listViewStates = new List<ListViewState>();

    /// <summary>
    /// Stores state information about a ListView that allows ListViewItemsSourceCollectionChanged
    /// to map from a collection to the corresponding ListView owner.
    /// </summary>
    private class ListViewState
    {
        /// <summary>
        /// Weakly references the ListView.
        /// </summary>
        private WeakReference _listViewReference = new WeakReference(null);

        /// <summary>
        /// Gets or sets the ListView.
        /// </summary>
        public ListView ListView
        {
            get { return (ListView)(_listViewReference.Target); }
            private set { _listViewReference.Target = value; }
        }

        /// <summary>
        /// Weakly references the INotifyCollectionChanged.
        /// </summary>
        private WeakReference _iNotifyCollectionChangedReference = new WeakReference(null);

        /// <summary>
        /// Gets or sets the INotifyCollectionChanged.
        /// </summary>
        public INotifyCollectionChanged INotifyCollectionChanged
        {
            get { return (INotifyCollectionChanged)(_iNotifyCollectionChangedReference.Target); }
            set { _iNotifyCollectionChangedReference.Target = value; }
        }

        /// <summary>
        /// Creates a new instance of the ListViewState class.
        /// </summary>
        /// <param name="listView">Corresponding ListView.</param>
        public ListViewState(ListView listView)
        {
            ListView = listView;
        }
    }
}
Tags: WPF

Scrolling so smooth like the butter on a muffin [How to: Animate the Horizontal/VerticalOffset properties of a ScrollViewer]

Recently, I got email from two different people with the same question: How do I animate the HorizontalOffset/VerticalOffset properties of a Silverlight/WPF ScrollViewer control? Just in case you've never tried this yourself, I'll tell you that it's not quite as easy as you'd think; those two properties are both read-only and therefore can't be animated by a DoubleAnimation. The official way to accomplish this task is to call the ScrollToHorizontalOffset or ScrollToVerticalOffset method - but of course that can't be done by a DoubleAnimation either...

Aside: Why are HorizontalOffset and VerticalOffset read-only in the first place? Good question; I don't know! I don't see why they couldn't be writable in today's world, but I bet there was a good reason - once upon a time. :)

While I didn't have the time to implement a solution myself, I did have an idea for what to do. It was inspired by a rather quirky business plan and went like this:

  1. Create an attached DP for ScrollViewer of type double called SettableHorizontalOffset
  2. In its change handler, call scrollViewerInstance.ScrollToHorizontalOffset(newValue)
  3. Animate the attached DP on the ScrollViewer of your choice
  4. ???
  5. Profit

I cautioned at the time that my idea might not work - and sure enough, when I tried it myself, it didn't! It turns out that neither Silverlight nor WPF much like the idea of pointing Storyboard.TargetProperty at an attached DependencyProperty. So much for my clever idea; I needed a different way to solve the problem... And before long I realized that I could use a Mediator just like I did for a similar situation with LayoutTransformer (see previous posts).

 

So I created a simple class, ScrollViewerOffsetMediator, which exposes a ScrollViewer property that you point at a ScrollViewer instance (hint: use a Binding with ElementName to make this easy) and a VerticalOffset property which is conveniently writable. :) Any changes to the VerticalOffset property are automatically applied to the relevant ScrollViewer via a call to its ScrollToVerticalOffset method - so you can animate the mediator and it'll be just like you were animating the ScrollViewer itself!

That solves the original problem and it's all well and good - but it doesn't quite address a pretty common scenario: wanting to animate a ScrollViewer from top to bottom. You see, VerticalOffset is expressed in pixels and it's not easy to pass the (unknown) height of an arbitrary ScrollViewer to an animation in static XAML. So I added another property called ScrollableHeightMultiplier which takes a value between 0.0 and 1.0, multiplies it by the ScrollViewer's current ScrollableHeight and sets the VerticalOffset to that value. In this manner, animations can be written in general, pixel-independent terms, and it's easy to get the animated scrolling effect everybody wants!

Aside: I've implemented support for only the vertical direction - doing the same thing for the horizontal direction is left as an exercise to the reader.

 

The sample application uses ScrollableHeightMultiplier and an EasingFunction (just to show off!) and looks like this:

AnimatingScrollViewerOffsets sample

 

[Click here to download the complete AnimatingScrollViewerOffsets sample.]

 

The XAML for the sample is quite straightforward:

<Grid Height="200" Width="150">

    <!-- Trigger to start the animation when loaded -->
    <Grid.Triggers>
        <EventTrigger RoutedEvent="Grid.Loaded">
            <BeginStoryboard>
                <BeginStoryboard.Storyboard>
                    <!-- Animate back and forth forever -->
                    <Storyboard
                        AutoReverse="True"
                        RepeatBehavior="Forever">
                        <!-- Animate from top to bottom -->
                        <DoubleAnimation
                            Storyboard.TargetName="Mediator"
                            Storyboard.TargetProperty="ScrollableHeightMultiplier"
                            From="0"
                            To="1"
                            Duration="0:0:1">
                            <DoubleAnimation.EasingFunction>
                                <!-- Ease in and out -->
                                <ExponentialEase EasingMode="EaseInOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </BeginStoryboard.Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Grid.Triggers>

    <!-- ScrollViewer that will be animated -->
    <ScrollViewer
        x:Name="Scroller">
        <!-- Arbitrary content... -->
        <StackPanel>
            <ItemsControl
                ItemsSource="{Binding}"
                FontSize="32"/>
        </StackPanel>
    </ScrollViewer>

    <!-- Mediator that forwards the property changes -->
    <local:ScrollViewerOffsetMediator
        x:Name="Mediator"
        ScrollViewer="{Binding ElementName=Scroller}"/>

</Grid>

 

Here's the complete implementation of ScrollViewerOffsetMediator which naturally works perfectly well on WPF:

/// <summary>
/// Mediator that forwards Offset property changes on to a ScrollViewer
/// instance to enable the animation of Horizontal/VerticalOffset.
/// </summary>
public class ScrollViewerOffsetMediator : FrameworkElement
{
    /// <summary>
    /// ScrollViewer instance to forward Offset changes on to.
    /// </summary>
    public ScrollViewer ScrollViewer
    {
        get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
        set { SetValue(ScrollViewerProperty, value); }
    }
    public static readonly DependencyProperty ScrollViewerProperty =
        DependencyProperty.Register(
            "ScrollViewer",
            typeof(ScrollViewer),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(OnScrollViewerChanged));
    private static void OnScrollViewerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        var scrollViewer = (ScrollViewer)(e.NewValue);
        if (null != scrollViewer)
        {
            scrollViewer.ScrollToVerticalOffset(mediator.VerticalOffset);
        }
    }

    /// <summary>
    /// VerticalOffset property to forward to the ScrollViewer.
    /// </summary>
    public double VerticalOffset
    {
        get { return (double)GetValue(VerticalOffsetProperty); }
        set { SetValue(VerticalOffsetProperty, value); }
    }
    public static readonly DependencyProperty VerticalOffsetProperty =
        DependencyProperty.Register(
            "VerticalOffset",
            typeof(double),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(OnVerticalOffsetChanged));
    public static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        if (null != mediator.ScrollViewer)
        {
            mediator.ScrollViewer.ScrollToVerticalOffset((double)(e.NewValue));
        }
    }

    /// <summary>
    /// Multiplier for ScrollableHeight property to forward to the ScrollViewer.
    /// </summary>
    /// <remarks>
    /// 0.0 means "scrolled to top"; 1.0 means "scrolled to bottom".
    /// </remarks>
    public double ScrollableHeightMultiplier
    {
        get { return (double)GetValue(ScrollableHeightMultiplierProperty); }
        set { SetValue(ScrollableHeightMultiplierProperty, value); }
    }
    public static readonly DependencyProperty ScrollableHeightMultiplierProperty =
        DependencyProperty.Register(
            "ScrollableHeightMultiplier",
            typeof(double),
            typeof(ScrollViewerOffsetMediator),
            new PropertyMetadata(OnScrollableHeightMultiplierChanged));
    public static void OnScrollableHeightMultiplierChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var mediator = (ScrollViewerOffsetMediator)o;
        var scrollViewer = mediator.ScrollViewer;
        if (null != scrollViewer)
        {
            scrollViewer.ScrollToVerticalOffset((double)(e.NewValue) * scrollViewer.ScrollableHeight);
        }
    }
}

 

PS - Thanks to Homestar Runner's Strong Bad for inspiring the title of this post.

Simple column labels you can create at home! [Re-Templating the Silverlight/WPF Data Visualization ColumnDataPoint to add annotations]

A customer contacted me over the weekend asking how to add labels (also known as annotations) to a ColumnSeries. My reply was that we don't support annotations in Silverlight/WPF Charting yet, but it's possible to create some pretty simple ones for limited scenarios just by editing the default ColumnDataPoint Template. And because it's so quick, I thought I'd write up a brief example on the bus!

Aside: For some more examples of basic DataPoint Template changes, please have a look at my earlier post on customizing ToolTips.

 

In this case, the customer wanted to add a label showing the column's value at the bottom of the column, just above its axis label. (This is a nice place to put labels because it makes it easy for viewers to associate the category with its value no matter how high each column is.) The obvious approach is to add a TextBlock to the body of the default Template, but the problem with that is that the text can get clipped or even disappear for small columns... So the trick is to add a negative Margin to pull the text "outside" the normal clipping region. Fortune must have been smiling upon me, because when I tried this on Silverlight, it worked just like I wanted! :)

Aside: My other idea was to use a Canvas because it doesn't clip by default; maybe someone else will need to use that approach for their scenario.

 

Here's how the resulting chart looks:

Simple column annotations (on bottom)

The XAML's nothing special - aside from the negative Margin, it's all standard stuff:

<charting:Chart
    Title="Simple Column Annotations - Bottom">
    <charting:ColumnSeries
        DependentValuePath="Value"
        IndependentValuePath="Key"
        ItemsSource="{Binding}">
        <charting:ColumnSeries.DataPointStyle>
            <Style TargetType="charting:ColumnDataPoint">
                <Setter Property="Background" Value="Yellow"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="charting:ColumnDataPoint">
                            <Grid>
                                <Rectangle
                                    Fill="{TemplateBinding Background}"
                                    Stroke="Black"/>
                                <Grid
                                    Background="#aaffffff"
                                    Margin="0 -20 0 0"
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Bottom">
                                    <TextBlock
                                        Text="{TemplateBinding FormattedDependentValue}"
                                        FontWeight="Bold"
                                        Margin="2"/>
                                </Grid>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </charting:ColumnSeries.DataPointStyle>
    </charting:ColumnSeries>
</charting:Chart>

 

Just for fun, I thought I'd try the same trick to put the annotations on top of the columns, too. This is the more traditional location - and that also works pretty nicely:

Simple column annotations (on top)

The only change from the previous XAML is switching the VerticalAlignment to Top:

<Grid
    Background="#aaffffff"
    Margin="0 -20 0 0"
    HorizontalAlignment="Center"
    VerticalAlignment="Top">
    <TextBlock
        Text="{TemplateBinding FormattedDependentValue}"
        FontWeight="Bold"
        Margin="2"/>
</Grid>

 

And there you have it - a simple technique for simple column annotations!

Aside: Of course, these aren't "real" annotations - they'll eventually break in more complicated scenarios. But hey, if you've got simple needs, here's a simple solution for you. :)

 

PS - For people playing along at home, here's how I created the data for the samples:

public MainPage()
{
    InitializeComponent();
    var items = new List<KeyValuePair<string, double>>();
    items.Add(new KeyValuePair<string,double>("Apples", 0));
    items.Add(new KeyValuePair<string,double>("Oranges", 0.1));
    items.Add(new KeyValuePair<string,double>("Pears", 1));
    DataContext = items;
}

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

In the time since posting my last collection of Silverlight/WPF Charting links, there's been some great activity! Of particular significance, the June 2009 WPF Toolkit includes the same Data Visualization goodness that was introduced for Silverlight and the July 2009 release of the Silverlight Toolkit added a TreeMap control. Without further ado, 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! :)

Bringing the Silverlight Toolkit's TreeMap to WPF [Silverlight/WPF Data Visualization Development Release 0]

Now that Data Visualization is part of the Silverlight Toolkit and the WPF Toolkit, it has an official release vehicle for both platforms of interest. This is great because it means that any customers who need to be running signed bits from an official release have a place to get what they need. Of course, because the two Toolkits are not on the same ship schedule, there will probably always be a delta between them: some features or fixes that are present for one platform but not the other. Fortunately, the release cadence for the Toolkits is pretty short (on the order of months), so it won't take long for features to make their way into both releases.

But for folks who always want to be running the latest-and-greatest stuff, that wait can feel like an eternity... :) The good news is that both Toolkits are Ms-Pl open source, so anyone can migrate code between the two. (And, in fact, I made some small tweaks recently to make that even easier!) But why should everyone have to reinvent the wheel? Wouldn't it be better if one person simplified the process and shared the results with everybody? Yeah, I thought so too...

 

Announcing Silverlight/WPF Data Visualization Development Release 0!

The Silverlight/WPF Data Visualization Development Release is a side project of mine to make it easier for Silverlight and WPF customers to get their hands on the latest Data Visualization code for both platforms. From time to time, I plan to release a single, comprehensive ZIP file with the complete Data Visualization source code, projects for all supported platforms, a unifying solution, and pre-compiled release-mode binaries for all supported platforms (unsigned). These development releases will contain the latest internal source code changes with all the fixes and features that are in progress. Interested parties can evaluate the newest stuff and provide early feedback on what works and what's broken - feedback that can help ensure the things you care about get some love and attention.

The catch - and there's always a catch - is that I'm not committing to any kind of schedule for these releases - and they won't be as thoroughly tested as what's in the official Toolkit releases. But if that doesn't bother you, I'd love to have your feedback!

 

[Please click here to download the complete SilverlightWpfDataVisualization solution.]

 

Release Notes

  • The source code for Development Release 0 is identical to what's included with the recent Silverlight Toolkit July 09 release, so there are no new features for the Silverlight assembly. However, the WPF assembly includes the new TreeMap control and a better performing BubbleSeries! (Please see my release announcement and notes for more information about what TreeMap is and how to use it.)
  • Please remember that whenever you add a project reference to the Data Visualization assembly on WPF, you also need to add a reference to WPFToolkit.dll or you'll get weird runtime errors because the Visual State Manager (VSM) isn't available.
  • The file structure of the archive looks like this (highlighted items are files; everything else is directories):
    SilverlightWpfDataVisualization
    |   SilverlightWpfDataVisualization.sln
    +---Binaries
    |   +---Silverlight3
    |   |       System.Windows.Controls.DataVisualization.Toolkit.dll
    |   |       
    |   \---WPF35
    |           System.Windows.Controls.DataVisualization.Toolkit.dll
    |           WPFToolkit.dll
    +---Platforms
    |   +---Silverlight3
    |   |   |   Core.Silverlight3.csproj
    |   |   +---Properties
    |   |   \---Themes
    |   \---WPF35
    |       |   Core.WPF35.csproj
    |       +---Properties
    |       \---Themes
    \---SourceCode
        \---Core
            +---Charting
            |   +---Axis
            |   +---Chart
            |   +---DataPoint
            |   +---Primitives
            |   \---Series
            +---Collections
            +---Legend
            +---Properties
            +---Title
            \---TreeMap
                +---Interpolators
                \---Layout
    The pre-compiled release-mode binaries are located under the Binaries directory. All the common source code is under the SourceCode directory. Platform-specific project and source files are under the Platforms directory. The unified Visual Studio solution is in the root.
  • Design-time assemblies are not part of this release because I don't expect the target audience to make changes to them and also because they add a lot of complexity. However, if there's strong interest in having the design-time assemblies included with future development releases, I'm open to doing so.
  • I haven't updated my DataVisualizationDemos application for this unofficial release, but it's fairly easy to file-link the included TreeMap sample page into the WPF project to get those TreeMap samples running on WPF. (And that's exactly what I did to make sure TreeMap still works well on WPF!)

 

This whole idea of doing development releases is a bit of an experiment for me and I'm interested to see how it works out... If folks really love the idea, I'll try to do development releases more frequently. Or if it's simply too much churn for everyone to keep up with, people can always just wait for the next official Silverlight/WPF Toolkit release to come around. But however people get at it, I hope the Silverlight/WPF Data Visualization project helps customers continue to do great things with Silverlight and WPF!!