The blog of dlaa.me

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

Some great content has been published since I posted my previous collection of Silverlight/WPF Charting links. What's more, the November 2009 release of the Silverlight Toolkit and the February 2010 release of the WPF Toolkit have both been released, so please have a look at them if you haven't already!

Now, without further ado, here are all links that are fit to print (FYI: previously published links are gray):

Overviews (100 level)

Scenarios (200 level)

Internals (300 level)

Team Member posts (Partner level)

My posts (Ego level)

My many thanks go out to everyone who has spent time helping people 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 great content! :)

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

Turn your head and check out this post [How to: Easily rotate the axis labels of a Silverlight/WPF Toolkit chart]

When someone asked me how to rotate the axis labels of a chart from the Data Visualization package of the Silverlight Toolkit/WPF Toolkit earlier today, I realized it was time for a quick blog post. Because when I've answered a question two or three times, it's usually a pretty good sign that I'll keep on answering it for some time. I usually try to head that kind of thing off at the pass, so here's my post on the topic for the benefit of future generations. :)

The typical scenario here is that someone has a chart and it's working well, but their axis labels are very long and end up overlapping - even after the default axis behavior of putting them in alternating rows to prevent such a problem kicks in:

Overlapping axis labels

 

The typical solution is to rotate the axis labels - and it's easy once you know where to look. The key here is to customize the Template of the AxisLabel instances that are used to render the labels. And it's quite simple to do so by providing a Style with a Template Setter for the AxisLabelStyle property of the Axis subclass in question:

Rotated axis labels on WPF

Yeah, it looks great on paper; but that description was a mouthful...

It's probably easier to understand in XAML - here's the complete code for the sample above with the interesting part highlighted:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:charting="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:spec="clr-namespace:System.Collections.Specialized;assembly=System"
        Title="RotatedAxisLabelsWPF"
        Width="500"
        Height="350">
    <Grid>
        <charting:Chart
            Title="Animals With Long Names">
            <charting:ColumnSeries
                Title="Character count"
                DependentValueBinding="{Binding Length}"
                IndependentValueBinding="{Binding}">
                <charting:ColumnSeries.ItemsSource>
                    <spec:StringCollection>
                        <sys:String>Bumblebee</sys:String>
                        <sys:String>Caterpillar</sys:String>
                        <sys:String>Hippopotamus</sys:String>
                        <sys:String>Rhinoceros</sys:String>
                        <sys:String>Velociraptor</sys:String>
                    </spec:StringCollection>
                </charting:ColumnSeries.ItemsSource>
                <charting:ColumnSeries.IndependentAxis>
                    <charting:CategoryAxis
                        Orientation="X">
                        <charting:CategoryAxis.AxisLabelStyle>
                            <Style TargetType="charting:AxisLabel">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="charting:AxisLabel">
                                            <TextBlock Text="{TemplateBinding FormattedContent}">
                                                <TextBlock.LayoutTransform>
                                                    <RotateTransform Angle="-60"/>
                                                </TextBlock.LayoutTransform>
                                            </TextBlock>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </charting:CategoryAxis.AxisLabelStyle>
                    </charting:CategoryAxis>
                </charting:ColumnSeries.IndependentAxis>
            </charting:ColumnSeries>
        </charting:Chart>
    </Grid>
</Window>

Like I said, it's all pretty standard stuff once you know where to look. Of course, you can rotate the labels all the way to 90 degrees if you want them to take the least amount of space possible. But 60 degrees seemed like a suitably rakish angle. ;)

 

Unfortunately, we can't declare "Mission Accomplished" quite yet... While the Data Visualization assembly itself works exactly the same on WPF and Silverlight, the platforms themselves aren't identical quite yet. Specifically, there's no support for LayoutTransform in Silverlight (and RenderTransform is simply not appropriate here). Fortunately, I've filled the LayoutTransform gap with my LayoutTransformer class - and it's already part of the Silverlight Toolkit!

The syntax changes just a bit, but the concept is exactly the same:

<UserControl x:Class="SilverlightApplication1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:charting="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
    xmlns:layout="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Layout.Toolkit">
    <Grid>
        <charting:Chart
            Title="Animals With Long Names">
            <charting:ColumnSeries
                Title="Character count"
                DependentValueBinding="{Binding Length}"
                IndependentValueBinding="{Binding}">
                <charting:ColumnSeries.ItemsSource>
                    <toolkit:ObjectCollection>
                        <sys:String>Bumblebee</sys:String>
                        <sys:String>Caterpillar</sys:String>
                        <sys:String>Hippopotamus</sys:String>
                        <sys:String>Rhinoceros</sys:String>
                        <sys:String>Velociraptor</sys:String>
                    </toolkit:ObjectCollection>
                </charting:ColumnSeries.ItemsSource>
                <charting:ColumnSeries.IndependentAxis>
                    <charting:CategoryAxis
                        Orientation="X">
                        <charting:CategoryAxis.AxisLabelStyle>
                            <Style TargetType="charting:AxisLabel">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="charting:AxisLabel">
                                            <layout:LayoutTransformer>
                                                <layout:LayoutTransformer.LayoutTransform>
                                                    <RotateTransform Angle="-60"/>
                                                </layout:LayoutTransformer.LayoutTransform>
                                                <TextBlock Text="{TemplateBinding FormattedContent}"/>
                                            </layout:LayoutTransformer>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </charting:CategoryAxis.AxisLabelStyle>
                    </charting:CategoryAxis>
                </charting:ColumnSeries.IndependentAxis>
            </charting:ColumnSeries>
        </charting:Chart>
    </Grid>
</UserControl>

Mission accomplished:

Rotated axis labels on Silverlight

 

There you have it: AxisLabelStyle is your new best friend. A friend with benefits, one might say, because there are other cool things you can do by customizing the AxisLabel Style.

So please: go forth and enjoy your new friend!

Highlighting a "weak" contribution [Enhancements make preventing memory leaks with WeakEventListener even easier!]

It was back in March of last year that I explained the motivation for the WeakEventListener class I'd started using in the Silverlight Toolkit's Data Visualization assembly. Since then, a few other Toolkit controls have added WeakEventListener where necessary - but otherwise not much has changed...

Then, a few days ago, I saw that Beat Kiener had written a post detailing the specifics of how WeakEventListener really works. Where I focused more on saying why WeakEventListener is necessary, Beat does a great job of showing why - with lots of pretty diagrams and a detailed explanation. He even identifies a mistake users of WeakEventListener might make and outlines a simple tweak to the original implementation to prevent it! Beat's post is a good read, and I recommend it for anyone who's interested in this stuff.

But wait, there's more! Beat followed with a post about a wrapper to simplify the common usage pattern shared by every WeakEventListener consumer. Whereas WeakEventListener is a little tricky to use correctly, Beat's wrapper is easy to get right - and still gets the job done! So if you've wanted to make use of WeakEventListener, but were intimidated by the technical details, please have a look at these two posts because I think you'll find it's really quite approachable. :)

 

PS - If you think you have a memory leak, but aren't sure, this post I wrote about using WinDbg, SOS, and GCRoot to diagnose .NET memory leaks may be helpful.

This is what happens when two Toolkits fall in love... [The February 2010 release of the WPF Toolkit is now available!]

The WPF Toolkit team has just published the February 2010 release of the WPF Toolkit! In addition to a variety of bug fixes for DataGrid, DatePicker, Calendar, and Visual State Manager, this release also includes the latest changes to the Data Visualization assembly for Silverlight and WPF, bringing the official WPF implementation up to date with the November 2009 release of the Silverlight Toolkit. And because sharing the Silverlight Toolkit's Data Visualization assembly with WPF Toolkit customers has worked out so well, this release of the WPF Toolkit introduces three other controls from the Silverlight Toolkit: AutoCompleteBox (SDK documentation), Accordion, and Rating!

 

WPF Toolkit February 2010 installer

 

Just as with the Data Visualization assembly, the WPF Toolkit implementation of AutoCompleteBox, Accordion, and Rating are almost identical copies of the latest Silverlight Toolkit implementations - we've even kept the same assembly names and structure. Therefore, everything you already know and love about these controls on Silverlight (including your code and XAML) should translate seamlessly to WPF! Even the Visual Studio 2008 design-time experience for these controls should be the same; we've brought the Visual Studio design-time assemblies over, too!

These controls have been part of the Silverlight Toolkit long enough that there are plenty of great resources on the web for information, examples, and more. I expect WPF developers will come up to speed quickly and be able to start taking advantage of them in WPF applications in no time! :)

 

Aside: This release of the WPF Toolkit targets the version of WPF that is part of .NET 3.5 (just as previous releases did). There isn't a version of the WPF Toolkit specifically for .NET 4 - largely because many of the controls in the WPF Toolkit were folded into WPF 4 itself!

 

Those of you who follow the progress of the Data Visualization project already know that I maintain a collection of helpful links from all over the web here: http://cesso.org/r/DVLinks. Another handy resource for new users is my DataVisualizationDemos sample project which runs on Silverlight 3, Silverlight 4, WPF 3.5, and WPF 4 and provides examples of a bunch of different things made possible by the Data Visualization project. Finally, there are some WPF Toolkit-specific notes that are part of my WPF Toolkit June 2009 announcement and which may be worth reviewing. (In particular, the "Build Notes" section covers an issue that occasionally trips people up when trying to compile the design-time assemblies themselves.)

 

Release notes: For a summary of the changes to the WPF version of Data Visualization since the June 2009 WPF Toolkit and numerous code/XAML examples, please have a look at my announcements for the July 2009, October 2009, and November 2009 releases of the Silverlight Toolkit.

 

It's always exciting when there's a new Toolkit release - and a bit of a relief because there's a lot of work that goes on behind the scenes to make these possible. I'd like to thank the WPF Toolkit team for their continued support of Data Visualization and for their willingness to include additional controls from the Silverlight Toolkit. In particular, I'd like to thank Patrick Danino who did nearly all the work to port the three new controls over to WPF, test them, and resolve a few platform incompatibilities that came up! His efforts went well beyond what I expected when he and I first talked about the idea and it has been a pleasure working with him on this. If any bugs happened to sneak through, it's not for lack of effort on Patrick's part. :) Please don't blame him - blame me instead!

No trees were harmed in the making of this blog post [How to: Successfully print a Chart with the Silverlight 4 Beta]

One of the big requests people have had for Silverlight was the ability to print, so one thing that's new in the Silverlight 4 Beta is the PrintDocument class which enables applications to print anything they want! There isn't an excess of fancy bells and whistles quite yet, but the support Silverlight provides is enough to do just about everything - it's just that sometimes you might need to do a little more than you expect. :) It's the classic "crawl, walk, run" approach to feature delivery, and this is a great first step that will be a welcome addition for everyone who's been wanting to print with Silverlight.

Okay, so that's all well and good and people can print any content they want and it all looks beautiful and works perfectly, right? Pretty much, yes! Well, except for certain scenarios involving charts from the Silverlight/WPF Data Visualization assembly. :( To be clear, if you try to print a Chart that's already in the visual tree, it works just fine and what you see really is what you get. But if you've tried to create custom content specifically for printing (for example: a different layout, maybe some page headers and footers, etc.) and that content included a Chart instance, you probably noticed a minor issue: none of the data points showed up on the page. Darn, it sure is hard to tell what's going on without those data points...

 

Fortunately, a bit of mental debugging reveals what's going on here. Charting's data points fade in by default - from Opacity 0 to 1 over the course of 0.5 seconds via a Visual State Manager transition. That's cool and it works great in the visual tree - however, when you're creating print-specific visuals and immediately printing them, those data points don't have a chance to do much fading in... So they're actually all there on the printed page, but they're at Opacity 0, so you can't see them!

What to do? Well, the most convenient thing would be for the Chart code to somehow know it was going to be printed and SkipToFill those pesky animations. But there's no official way for a control to know it's being created for printing. There are a few tricks the Chart could use to make an educated guess, but it doesn't do so yet - and I'm not sure that's the right answer anyway. So the next most obvious thing would be to re-Template the DataPoints to replace the default fading Template with one that doesn't fade, but instead starts fully visible. And as you'd expect, this works just fine - the data points show up and look great!

However, that's a lot of work that you probably don't want to have to do. It would be nice if there were a middle ground - some technique that was maybe a little bit of a hack, but that was really easy to implement and worked just about all the time...

 

And the good news is that there is such a hack! The idea builds upon the observation that printing a Chart from the visual tree works correctly - so let's start by popping our new, print-ready chart into the visual tree to give it a chance to get all pretty, then print it once that's done. That seems easy enough, let's try it:

// Create customized content for printing
var chart = new Chart { Title = "My Printed Chart" };
var series = new LineSeries
{
    ItemsSource = (PointCollection)Resources["Items"],
    DependentValuePath = "Y",
    IndependentValuePath = "X",
};
chart.Series.Add(series);
var content = new Border
{
    BorderBrush = new SolidColorBrush(Colors.Magenta),
    BorderThickness = new Thickness(10),
    Child = chart,
};

// Create container for putting content in the visual tree invisibly
var visualTreeContainer = new Grid
{
    Width = 1000,
    Height = 1000,
    Opacity = 0,
};

// Put content in the visual tree to initialize
visualTreeContainer.Children.Add(content);
LayoutRoot.Children.Add(visualTreeContainer);

// Prepare to print
var printDocument = new PrintDocument();
printDocument.PrintPage += delegate(object obj, PrintPageEventArgs args)
{
    // Unparent the content for adding to PageVisual
    visualTreeContainer.Children.Remove(content);
    args.PageVisual = content;
};

// Print the content
printDocument.Print();

// Remove container from the visual tree
LayoutRoot.Children.Remove(visualTreeContainer);

Note that the sample application is creating a completely new Chart instance here (with a print-specific title and custom magenta frame) - and that the printed output looks just like it should:

ChartPrinting sample output

 

[Click here to download the source code for the ChartPrinting sample for the Silverlight 4 Beta as a Visual Studio 2010 solution]

 

The Chart is added to the visual tree before the print dialog is shown - so it has plenty of time for those 0.5 second animations to play to completion while the user interacts with the dialog. The only sketchy bit is that an exceedingly fast user could possibly manage to print a Chart before the animations had run all the way to completion. Yes, well, I said it was a hack up front, didn't I? :) If you want rock-solid printing behavior, you probably ought to be re-templating the data points in order to ensure the initial visuals are exactly as you want them. But if you're just interested in printing something attractive with a minimum of fuss or inconvenience - and your users aren't hopped up on performance enhancing drugs - you'll probably be quite successful with this technique instead.

 

PS - The XPS Printer driver is a fantastic tool for testing printing scenarios like this one. By printing to a file and opening that file in the XPS Viewer application that comes with Windows, I was able to avoid wasting a single sheet of paper during the course of this investigation. Highly recommended!

This one time, at band camp... [A banded StackPanel implementation for Silverlight and WPF!]

Recently, I came across this article in the Expression Newsletter showing how to create a three-column ListBox. In it, Victor Gaudioso shows a Blend-only technique for creating a Silverlight/WPF ListBox with three columns. [And I award him extra credit for using the Silverlight Toolkit to do so! :) ] The technique described in that article is a great way to custom the interface layout without writing any code.

But it can get a little harder when the elements in the ListBox aren't all the same size: a row with larger elements may only be able to fit two items across before wrapping, while a row with smaller elements may fit four items or more! Such things can typically be avoided by specifying the exact size of all the items and the container itself (otherwise the user might resize the window and ruin the layout). But while hard-coding an application's UI size can be convenient at times, it's not always an option.

 

For those times when it's possible to write some code, specialized layout requirements can usually be implemented more reliably and more accurately with a custom Panel subclass. Victor started by looking for a property he could use to set the number of columns for the ListBox, and there's nothing like that by default. So I've written some a custom layout container to provide it - and because I've created a new Panel, this banded layout can be applied anywhere in an application - not just inside a ListBox!

The class I created is called BandedStackPanel and it behaves like a normal StackPanel, except that it includes a Bands property to specify how many vertical/horizontal bands to create. When Bands is set to its default value of 1, BandedStackPanel looks the same as StackPanel - when the value is greater than 1, additional bands show up automatically! And because BandedStackPanel is an integral part of the layout process, it's able to ensure that there are always exactly as many bands as there should be - regardless of how big or small the elements are and regardless of how the container is resized.

 

Vertical BandedStackPanel on Silverlight

 

[Click here to download the source code for BandedStackPanel and the Silverlight/WPF sample application in a Visual Studio 2010 solution]

 

The sample application shown here has a test panel on the left to play around with and a ListBox at the right to demonstrate how BandedStackPanel can be used to satisfy the original requirements of the article. The ComboBoxes at the top of the window allow you to customize the behavior of both BandedStackPanel instances. And, of course, BandedStackPanel works great on WPF as well as on Silverlight. :)

 

Horizontal BandedStackPanel on WPF

 

The way to use BandedStackPanel with a ListBox is the same as any other ItemsControl layout customization: via the ItemsControl.ItemsPanel property:

<ListBox>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <delay:BandedStackPanel Bands="3"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBoxItem Content="ListBoxItem 0"/>
    <ListBoxItem Content="ListBoxItem 1"/>
    <ListBoxItem Content="ListBoxItem 2"/>
    ...
</ListBox>

 

That's pretty much all there is to it! The Silverlight/WPF layout system is very powerful and can be made to do all kinds of weird and wonderful things without writing a single line of code. But for those times when your needs are very specific, nothing beats a custom Panel for really nailing the scenario!

 

PS - The implementation of BandedStackPanel is fairly straightforward. The only real complexity is abstracting out the handling of both the horizontal and vertical orientations so the same code can be used for both. I followed much the same approach the Silverlight Toolkit's WrapPanel uses and introduced a dedicated OrientedLength class that allows the code to deal in terms of primary/secondary growth directions instead of specifically manipulating width and height. Once the abstraction of OrientedLength is in place, the implementations of MeasureOverride and ArrangeOverride are pretty much what you'd expect. Here they are for those who might be interested:

/// <summary>
/// Implements custom measure logic.
/// </summary>
/// <param name="constraint">Constraint to measure within.</param>
/// <returns>Desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
    var bands = Bands;
    var orientation = Orientation;
    // Calculate the Size to Measure children with
    var constrainedLength = new OrientedLength(orientation, constraint);
    constrainedLength.PrimaryLength = double.PositiveInfinity;
    constrainedLength.SecondaryLength /= bands;
    var availableLength = constrainedLength.Size;
    // Measure each child
    var band = 0;
    var levelLength = new OrientedLength(orientation);
    var usedLength = new OrientedLength(orientation);
    foreach (UIElement child in Children)
    {
        child.Measure(availableLength);
        // Update for the band/level of this child
        var desiredLength = new OrientedLength(orientation, child.DesiredSize);
        levelLength.PrimaryLength = Math.Max(levelLength.PrimaryLength, desiredLength.PrimaryLength);
        levelLength.SecondaryLength += desiredLength.SecondaryLength;
        // Move to the next band
        band = (band + 1) % bands;
        if (0 == band)
        {
            // Update for the complete level; reset for the next one
            usedLength.PrimaryLength += levelLength.PrimaryLength;
            usedLength.SecondaryLength = Math.Max(usedLength.SecondaryLength, levelLength.SecondaryLength);
            levelLength.PrimaryLength = 0;
            levelLength.SecondaryLength = 0;
        }
    }
    // Update for the partial level at the end
    usedLength.PrimaryLength += levelLength.PrimaryLength;
    usedLength.SecondaryLength = Math.Max(usedLength.SecondaryLength, levelLength.SecondaryLength);
    // Return the used size
    return usedLength.Size;
}

/// <summary>
/// Implements custom arrange logic.
/// </summary>
/// <param name="arrangeSize">Size to arrange to.</param>
/// <returns>Used size.</returns>
protected override Size ArrangeOverride(Size arrangeSize)
{
    var bands = Bands;
    var orientation = Orientation;
    var count = Children.Count;
    // Prepare the Rect to arrange children with
    var arrangeLength = new OrientedLength(orientation, arrangeSize);
    arrangeLength.SecondaryLength /= bands;
    // Arrange each child
    for (var i = 0; i < count; i += bands)
    {
        // Determine the length of the current level
        arrangeLength.PrimaryLength = 0;
        arrangeLength.SecondaryOffset = 0;
        for (var band = 0; (band < bands) && (i + band < count); band++)
        {
            var desiredLength = new OrientedLength(orientation, Children[i + band].DesiredSize);
            arrangeLength.PrimaryLength = Math.Max(arrangeLength.PrimaryLength, desiredLength.PrimaryLength);
        }
        // Arrange each band within the level
        for (var band = 0; (band < bands) && (i + band < count); band++)
        {
            Children[i + band].Arrange(arrangeLength.Rect);
            arrangeLength.SecondaryOffset += arrangeLength.SecondaryLength;
        }
        // Update for the next level
        arrangeLength.PrimaryOffset += arrangeLength.PrimaryLength;
    }
    // Return the arranged size
    return arrangeSize;
}

The customer is always right [Updated free tool and source code to prevent a machine from going to sleep!]

It was a few months back that I released Insomnia, a simple utility to prevent a computer from entering sleep mode. (For more on why that can be desirable (or other details about what Insomnia is and how it works), please refer to the original post.) Insomnia is a very simple program (it boils down to a single Win32 API call), but it fills a need many of us have - and the feedback I've gotten has been much appreciated!

 

Insomnia application

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

 

My original post explains why Insomnia makes its window Topmost: "so it's always visible and people will be less likely to accidentally leave their computers sleep-less". My reasoning made plenty of sense to me (obviously!), but I received some public and private requests (like these) to add the ability to minimize Insomnia to the tray. I'm not one to argue with customers, so I decided to spend a commute adding the requested feature. Fortunately, I've already written and shared a WPF-based "minimize to tray" implementation, so I added that file to the Insomnia project and pasted the single line of code it took to enable "minimize to tray". And though I was done, I still had most of the bus ride left... :)

 

Insomnia minimized to the tray

 

So I figured I'd address the other popular request while I was at it: the ability to start Insomnia already minimized. To do that, I added the following code to the constructor which looks for a "-minimize" argument on the command-line and starts Insomnia minimized when it's present:

// Start minimized if requested via the command-line
foreach (var arg in Environment.GetCommandLineArgs())
{
    if (0 == string.Compare(arg, "-minimize", true))
    {
        Loaded += delegate { WindowState = WindowState.Minimized; };
    }
}
Aside: Because I'm using the Loaded event, there can be a brief instant where Insomnia shows up on the screen before minimizing itself. While my initial implementation actually set the Minimized state directly from the constructor, there's enough going on at that point (and enough non-standard window settings in Insomnia) that WPF got confused and showed a small, empty window even though the application was minimized. I probably could have added more code to suppress that, but this implementation is so clean and obvious about what it's doing that I didn't want to trade it in for an alternate one that would be more complex and hacky.

 

You know, when everything is said and done, I find that I really like Insomnia's new ability to get out of the way by minimizing to the tray. :) So thanks for everyone's feedback here - and please keep the great suggestions coming!

 

 

PS - If you want a simple way to start Insomnia minimized, create a customized shortcut:

  1. Right-click on the Insomnia program
  2. Choose "Create shortcut"
  3. Right-click on "Insomnia - Shortcut"
  4. Choose "Properties"
  5. Add  -minimize to the end of the command line
  6. Press OK
  7. Optionally: Move that shortcut to the "Startup" folder in the Start Menu to have Insomnia start minimized every time you log in to Windows
Editing Insomnia shortcut

The Code Not Taken [Comparing two ways of creating a full-size Popup in Silverlight - then picking the best]

Update on 2010-06-17: Silverlight 4's behavior is different than Silverlight 3's (which is described below); I examine the difference in this follow-up post.

 

When it comes time to display a Popup that overlays the entire Silverlight plug-in, most people do it the same way. Unfortunately, although the typical approach appears correct at first glance (and may even survive a test pass!), there are some problems that are just about guaranteed to surface once the application is released. The purpose of this post is to educate people about those issues - and suggest an alternative approach that is easier to get right.

 

[Click here to download the complete source code for the sample (a Visual Studio 2010 (Beta 2) project targeting Silverlight 3)]

 

Let's start with a pretty typical first attempt at creating a full-plug-in Popup. This code uses Application.Current.Host.Content to get the size of the plug-in, creates a Popup with some simple content, sizes the content to match the plug-in's dimensions, and opens the Popup:

var root = Application.Current.Host.Content;
var popup = new Popup();
var child = CreateChildElement();
child.Width = root.ActualWidth;
child.Height = root.ActualHeight;
popup.Child = child;
popup.IsOpen = true;
Aside: I'm following Raymond Chen's convention that code in italics is wrong. So please don't start copy/pasting quite yet... :)

Here's how the sample application looks when we run it:

Initial state

When you press the top button to run the snippet above, the CreateChildElement method creates an Image element (showing the Silverlight logo) set to stretch horizontally and vertically. Everything looks fine immediately after pressing the button, but here's what happens if you make the browser a little bigger while the Popup is still on screen:

Resize not handled

Oops, we forgot to handle the resize event. No big deal - except that it's actually worse than that... Here's what things look like if you instead press the top button when the browser's zoom setting is set to 50% (as indicated by the magnifying glass at the bottom right of the browser window):

Zoom not respected

Ouch! As it turns out, this browser zoom problem is what seems to trip people up the most. When the browser is zoomed, some of the coordinates Silverlight exposes are affected - and Host.Content is one of them. Unless the browser zoom is at exactly 100%, basing anything on Host.Content's ActualWidth/ActualHeight without manually compensating for the zoom results in sizes that are too small (like we see here) or too large.

 

Okaaay, let's correct the resize thing first because it's easy and then we'll worry about zoom. Here's a simple modification of the code above to handle the Host.Content's Resized event:

var root = Application.Current.Host.Content;
var popup = new Popup();
var child = CreateChildElement();
EventHandler rootResized = delegate
{
    child.Width = root.ActualWidth;
    child.Height = root.ActualHeight;
};
root.Resized += rootResized;
rootResized(null, null);
popup.Child = child;
popup.IsOpen = true;
Aside: You may be wondering why I didn't use a Binding to synchronize the Width and Height properties instead of futzing around with an event handler like this. Well, I would have liked to, but there's the small issue that Silverlight doesn't provide property change notifications for ActualWidth/ActualHeight changes. So while I do recommend using Binding whenever you can, that's not really an option here.

So let's run the sample application and press the middle button to see how those changes worked out:

Zoom undone

Well, things look right at first. Though I don't demonstrate it here, we've successfully fixed the browser resize issue and that's good. But something is still wrong in the image above... If you look carefully, you can see that the browser zoom is set at 50% - yet somehow the image is sized correctly despite us not doing any work to handle the browser's zoom setting yet. How can that be? Hold on, Sherlock, there's another clue in the image: look at the size of the buttons. Yeah, those buttons are not the size they should be for the 50% zoom setting that's active (refer back to the previous image if you don't believe me). Those buttons are at the 100% size - wha??

Aside: Hey, don't feel bad, it weirded me out, too. :)

It turns out that when you attach an event handler to the Resized event, Silverlight disables its support for browser zoom. The reason being that Silverlight assumes the application has chosen to handle that event because it wants full control over the zoom experience (via ZoomFactor and Zoomed, perhaps). Now that's really kind of thoughtful of it and everything - but in this case it's not what we want. In fact, that behavior introduces a somewhat jarring experience because the graphics visibly snap between 50% and 100% as the Resized event handler is attached and detached. Our sample application is perfectly happy to respect the browser's zoom settings; it just wants to know when it has been resized so it can update the Popup's dimensions. Irresistible force, meet immovable object...

 

Like I said at the beginning, most people seem to gravitate to the approach above. But there's another option - one which I'll suggest is better because it doesn't suffer from either of these problems: use Application.Current.RootVisual instead!

var root = Application.Current.RootVisual as FrameworkElement;
var popup = new Popup();
var child = CreateChildElement();
SizeChangedEventHandler rootSizeChanged = delegate
{
    child.Width = root.ActualWidth;
    child.Height = root.ActualHeight;
};
root.SizeChanged += rootSizeChanged;
rootSizeChanged(null, null);
popup.Child = child;
popup.IsOpen = true;

As you can see, the code looks nearly identical to what we had before (except it's not in italics!). But by keeping all the math inside the domain of the plug-in, the need to account for browser zoom or hook the troublesome Resized event goes away completely! Instead, this code responds to the RootVisual's SizeChanged event and behaves correctly at first, when the browser is resized, for different zoom settings, and even when the zoom setting is changed while the Popup is visible!

Success!

What more could you ask for?

 

There are just two things worth calling out:

  1. The RootVisual must be an instance of a FrameworkElement-based class (which it is by default and 99.9% of the rest of the time; MainPage is a UserControl and that derives from FrameworkElement).
  2. If the size of the RootVisual is explicitly set for some reason, the Popup will match that size and not the size of the plug-in itself (though I can't imagine why you'd do this).

Those small caveats aside, it's been my experience that the RootVisual approach is more naturally correct - by which I mean that it tends to be right by virtue of its simplicity and the fact that it hooks up to the right thing. I don't promise that it's the best choice for all scenarios, but I'll suggest that it's worth considering first for most scenarios. There are probably ways to make either approach do what you want - but I prefer to start with the one that's less troublesome! :)

Flattery will get you everywhere [Html5Canvas source code now available on CodePlex!]

A few months ago, I described a proof-of-concept project and learning exercise I'd worked on to implement some of the basics of the HTML 5 <canvas> specification using Silverlight as the underlying platform: HTML 5 <canvas> announcement, fix for other cultures. As I explain in the introductory post, I didn't set out to come up with the most efficient, most complete implementation - just to get some familiarity with the <canvas> specification and see what it would be like to implement it with Silverlight. Html5Canvas was a lot of fun and even generated a small amount of buzz when I posted it...

Sample application in Firefox

Earlier today, fellow programmer Jon Davis emailed me to ask if he could put that sample up on CodePlex to share with others in the community. I replied that he was welcome to do so (all the code I post is under the OSI-approved Ms-PL license) - and soon got a reply from Jon pointing me to:

http://slcanvas.codeplex.com/

Jon has written about his motivations here - I encourage interested parties to have a read. [And I swear I didn't bribe him to say nice things! :) ] This was not the first time I'd been asked about putting the source code for HTML 5 <canvas> on CodePlex, so if you've been waiting for an opportunity like this, please follow up with Jon to see how you might be able to help contribute to the effort!

Looks the same - with half the overhead! [Update to free ConvertClipboardRtfToHtmlText tool and source code gives more compact output; Can you do better?]

I recently updated my ConvertClipboardRtfToHtmlText tool to work with Visual Studio 2010 (Beta 2). This utility takes the RTF clipboard format Visual Studio puts on the clipboard, converts it into HTML, and substitutes the converted text for pasting into web pages, blog posts, etc.. It works great and I use it all the time for my blog.

In the comments to that post, kind reader Sameer pointed out that the converted HTML was more verbose than it needed to be - and I quickly replied that it wasn't my fault. :) Here's the example Sameer gave (which is particularly inefficient):

public partial class

And here's the corresponding HTML (on multiple lines because it's so long):

<pre>
<span style='color:#000000'></span>
<span style='color:#0000ff'>public</span>
<span style='color:#000000'> </span>
<span style='color:#0000ff'>partial</span>
<span style='color:#000000'> </span>
<span style='color:#0000ff'>class</span>
</pre>

Yup, that's almost obnoxiously inefficient: there's a useless black span at the beginning and a bunch of pointless color swapping for both of the space characters. Something more along the lines of the following would be much better:

<pre style='color:#0000ff'>public partial class</pre>

The HTML for both examples ends up looking exactly the same in a web browser, so wouldn't it be nice if the tool produced the second, more compact form?

I thought so, too!

 

[Click here to download the ConvertClipboardRtfToHtmlText tool along with its complete source code.]

 

I had a bit of spare time the other night and decided to make a quick attempt at optimizing the output of ConvertClipboardRtfToHtmlText according to some ideas I'd been playing around with. Specifically, instead of outputting the converted text as it gets parsed, the new code builds an in-memory representation of the entire clipboard contents and associated color changes. After everything has been loaded, it performs some basic optimization steps to remove unnecessary color changes by ignoring whitespace and collapsing text runs. Once that's been done, the optimized HTML is placed on the clipboard just like before.

Here's what the relevant code looks like (recall that this tool compiles for .NET 2.0, so it's can't use Linq):

int j = runs.Count - 1;
while (0 <= j)
{
    Run run = runs[j];

    // Remove color changes for whitespace runs
    if (0 == run.Text.Trim().Length)
    {
        runs.RemoveAt(j);
        if (j < runs.Count)
        {
            runs[j].Text = run.Text + runs[j].Text;
        }
        else
        {
            j--;
        }
        continue;
    }

    // Remove redundant color changes
    if ((j + 1 < runs.Count) && (run.Color == runs[j + 1].Color))
    {
        runs.RemoveAt(j);
        runs[j].Text = run.Text + runs[j].Text;
    }

    j--;
}

// Find most common color
Dictionary<Color, int> colorCounts = new Dictionary<Color, int>();
foreach (Run run in runs)
{
    if (!colorCounts.ContainsKey(run.Color))
    {
        colorCounts[run.Color] = 0;
    }
    colorCounts[run.Color]++;
}
Color mostCommonColor = Color.Empty;
int mostCommonColorCount = 0;
foreach (Color color in colorCounts.Keys)
{
    if (mostCommonColorCount < colorCounts[color])
    {
        mostCommonColor = color;
        mostCommonColorCount = colorCounts[color];
    }
}

...

// Build HTML for run stream
sb.Length = 0;
sb.AppendFormat("<pre style='color:#{0:x2}{1:x2}{2:x2}'>", mostCommonColor.R, mostCommonColor.G, mostCommonColor.B);
foreach (Run run in runs)
{
    if (run.Color != mostCommonColor)
    {
        sb.AppendFormat("<span style='color:#{0:x2}{1:x2}{2:x2}'>", run.Color.R, run.Color.G, run.Color.B);
    }
    sb.Append(run.Text);
    if (run.Color != mostCommonColor)
    {
        sb.Append("</span>");
    }
}
sb.Append("</pre>");

 

The code comments explain what's going on and it's all pretty straightforward. The one sneaky thing is the part that finds the most commonly used color and makes that the default color of the entire block. By doing so, the number of span elements can be reduced significantly: switching to that common color becomes as simple as exiting the current span (which needed to happen anyway).

So was this coding exercise worth the effort? Is the resulting HTML noticeably smaller, or was this all just superficial messing around? To answer that, let's look at some statistics for converting the entire ConvertClipboardRtfToHtmlText.cs file:

Normal Optimized Change
Character count of .CS file 11,996 11,996 N/A
Character count converted HTML 32,091 21,158 -34%
Extra characters for HTML representation 20,095 9,162 -54%

Hey, those are pretty good results for just an hour's effort! And not only is the new representation significantly smaller, it's also less cluttered and easier to read - so it's easier to deal with, too. I'm happy with the improvement and switched to the new version of ConvertClipboardRtfToHtmlText a couple of posts ago. So if you notice my blog posts loading slightly faster than before, this could be why... :)

 

A challenge just for fun: I haven't thought about it too much (which could be my downfall), but I'll suggest that the output of the new approach is just about optimal for what it's doing. Every color change is now necessary, and they're about as terse as they can be. Unless I decide to throw away some information (ex: by using the 3-character HTML color syntax) or change the design (ex: by creating a bunch of 1-character CSS classes), I don't think things can get much better than this and still accurately reproduce the appearance of the original content in Visual Studio. Therefore, if you can reduce the overhead for this version of ConvertClipboardRtfToHtmlText.cs by an additional 5% (without resorting to invalid HTML), I will credit you and your technique in a future blog post! :)