The blog of dlaa.me

Posts tagged "WPF"

Know your place in life [Free PlaceImage control makes it easy to add placeholder images to any WPF, Silverlight, or Windows Phone application!]

One of the challenges with referencing online content is that you never know just how long it will take to download... On a good day, images show up immediately and your application has exactly the experience you want. On a bad day, images take a looong time to load - or never load at all! - and your application's interface is full of blank spaces. Applications that make use of remote images need to be prepared for variability like this and should have "placeholder" content to display when the desired image isn't available.

Of course, there are a variety of ways to deal with this; I thought it would be neat to create a reusable, self-contained class and share it here. I envisioned a simple control that "looked" like a standard Image element (i.e., had the same API), but that seamlessly handled the work of displaying placeholder content before an image loaded and getting rid of it afterward. Naturally, I also wanted code that would run on WPF, Silverlight, and Windows Phone! :)

 

<delay:PlaceImage
 PlaceholderSource="PlaceholderPhoto.png"
 Source="{Binding ImageUri}"/>

The result of this exercise is something I've called PlaceImage. PlaceImage has the same API as the framework's Image and can be dropped in pretty much anywhere an Image is used. To enable the "placeholder" effect, simply set the PlaceholderSource property to a suitable image. (Aside: While you could specify another remote image for the placeholder, the most sensible thing to do is to reference an image that's bundled with the application (e.g., as content or a resource).) PlaceImage immediately shows your placeholder image and waits for the desired image to load - at which point, PlaceImage swaps it in and gets rid of the placeholder!

 

I've written a sample application for each of the supported platforms that displays contact cards of imaginary employees. When the sample first runs, none of the remote images have loaded, so each card shows the "?" placeholder image:

PlaceImageDemo on Windows Phone

After a while, some of the remote images will have loaded:

PlaceImageDemo on Silverlight

Eventually, all the remote images load:

PlaceImageDemo on WPF

Thanks to placekitten for the handy placeholder images!

 

Making use of online content in an application is easy to do and a great way to enrich an application. However, the unpredictable nature of the network means content might not always be available when it's needed. PlaceImage makes it easy to add placeholder images to common scenarios and helps keep the user interface free of blank spaces. With easy support for WPF, Silverlight, and Windows Phone, you can add it to pretty much any XAML-based application!

 

[Click here to download the PlaceImageDemo project which includes PlaceImage.cs and sample applications for WPF, Silverlight, and Windows Phone.]

 

Notes:

  • Just like Image, PlaceImage has properties for Source, Stretch, and StretchDirection (the last being available only on WPF). PlaceImage's additional PlaceholderSource property is used just like Source and identifies the placeholder image to be displayed before the Source image is available. (So set it to a local image!)

  • Changes to the Source property of a loaded Image immediately clear its contents. Similarly, changing the Source of a loaded PlaceImage immediately switches to its placeholder image while the new remote content loads. You can trigger this behavior in the sample application by clicking any kitten.

  • Because the Silverlight version of the demo application references web content, it needs to be run from the PlaceImageDemoSL.Web project. (Although running PlaceImageDemoSL will show placeholders, the kitten pictures never load.) The MSDN article URL Access Restrictions in Silverlight has more information on Silverlight's "cross-scheme access" limitations.

  • Control subclasses typically live in a dedicated assembly and define their default Style/Template in Generic.xaml. This is a great, general-purpose model, but I wanted PlaceImage to be easy to add to existing projects in source code form, so it does everything in a single file. All you need to do is include PlaceImage.cs in your project, and PlaceImage will be available in the Delay namespace.

  • The absence of the StretchDirection property on Silverlight and Windows Phone isn't the only platform difference PlaceImage runs into: whereas Silverlight and Windows Phone offer the handy Image.ImageOpened event, WPF has only the (more cumbersome) BitmapSource.DownloadCompleted event. The meaning of these two events isn't quite identical, but for the purposes of PlaceImage, they're considered equivalent.

Invisible pixels are just as clickable as real pixels! [Tip: Use a Transparent brush to make "empty" parts of a XAML element respond to mouse and touch input]

Tip

Use a Transparent brush to make "empty" parts of a XAML element respond to mouse and touch input

Explanation

I got a question yesterday and thought the answer would make a good addition to my Development Tips series. As you probably know, WPF, Silverlight, and Windows Phone support a rich, hierarchical way of laying out an application's UI. Elements can be created in XAML or in code and respond to input by firing the relevant events (MouseLeftButtonDown, Click, etc.). Input events bubble from the element "closest" to the user all the way up to the root element (stopping if an event is marked Handled). Every now and then someone finds that an element they expect to be getting input is not (and they've made sure none of its children are "eating" the event). The most common reason is that the element doesn't have any pixels for the user to click on! For example, in a 100x100 panel containing a short message, only the text pixels are considered part of the panel and respond to mouse input - everything else passes "through" the empty area and bubbles up to the parent. This behavior enables the creation of elements with any shape, but sometimes it's not what you want. Fortunately, it's simple to get empty parts of an element to respond to input: just draw some pixels! And while a Brush of any color will do the trick, painting with Transparent pixels is a fantastic way to keep empty space looking empty while also being clickable!

Good Example

<Grid
    Background="Transparent"
    MouseLeftButtonDown="Grid_MouseLeftButtonDown">
    <TextBlock
        Text="You can click anywhere in the Grid!"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"/>
</Grid>

More information

"Sort" of a follow-up post [IListExtensions class enables easy sorting of .NET list types; today's updates make some scenarios faster or more convenient]

Recently, I wrote a post about the IListExtensions collection of extension methods I created to make it easy to maintain a sorted list based on any IList(T) implementation without needing to create a special subclass. In that post, I explained why I implemented IListExtensions the way I did and outlined some of the benefits for scenarios like using ObservableCollection(T) for dynamic updates on Silverlight, WPF, and Windows Phone where the underlying class doesn't intrinsically support sorting. A couple of readers followed up with some good questions and clarifications which I'd encourage having a look for additional context.

 

During the time I've been using IListExtensions in a project of my own, I have noticed two patterns that prompted today's update:

  1. It's easy to get performant set-like behavior from a sorted list. Recall that a set is simply a collection in which a particular item appears either 0 or 1 times (i.e., there are no duplicates in the collection). While this invariant can be easily maintained with any sorted list by performing a remove before each add (recall that ICollection(T).Remove (and therefore IListExtensions.RemoveSorted) doesn't throw if an element is not present), it also means there are two searches of the list every time an item is added: one for the call to RemoveSorted and another for the call to AddSorted. While it's possible to be a bit more clever and avoid the extra search sometimes, the API doesn't let you to "remember" the right index between calls to *Sorted methods, so you can't get rid of the redundant search every time.

    Therefore, I created the AddOrReplaceSorted method which has the same signature as AddSorted (and therefore ICollection(T).Add) and implements the set-like behavior of ensuring there is at most one instance of a particular item (i.e., the IComparable(T) search key) present in the collection at any time. Because this one method does everything, it only ever needs to perform a single search of the list and can help save a few CPU cycles in relevant scenarios.

  2. It's convenient to be able to call RemoveSorted/IndexOfSorted/ContainsSorted with an instance of the search key. Recall from the original post that IListExtensions requires items in the list to implement the IComparable(T) interface in order to define their sort order. This is fine most of the time, but can require a bit of extra overhead in situations where the items' sort order depends on only some (or commonly just one) of their properties.

    For example, note that the sort order the Person class below depends only on the Name property:

    class Person : IComparable<Person>
    {
        public string Name { get; set; }
        public string Details { get; set; }
    
        public int CompareTo(Person other)
        {
            return Name.CompareTo(other.Name);
        }
    }

    In this case, using ContainsSorted on a List(Person) to search for a particular name would require the creation of a fake Person instance to pass as the parameter to ContainsSorted in order to match the type of the underlying collection. This isn't usually a big deal (though it can be if the class doesn't have a public constructor!), but it complicates the code and seems like it ought to be unnecessary.

    Therefore, I've added new versions of RemoveSorted/IndexOfSorted/ContainsSorted that take a key parameter and a keySelector Func(T, K). The selector is passed an item from the list and needs to return that item's sort key (the thing that its IComparable(T).CompareTo operates on). Not surprisingly, the underlying type of the keys must implement IComparable(T); keys are then compared directly (instead of indirectly via the containing items). In this way, it's possible to look up (or remove) a Person in a List(Person) by passing only the person's name and not having to bother with the temporary Person object at all!

 

In addition to the code changes discussed above, I've updated the automated test project that comes with IListExtensions to cover all the new scenarios. Conveniently, the new implementation of AddOrReplaceSorted is nearly identical to that of AddSorted and can be easily validated with SortedSet(T). Similarly, the three new key-based methods have all been implemented as variations of the pre-existing methods and those have been modified to call directly into the new methods. Aside from a bit of clear, deliberate redundancy for AddOrReplaceSorted, there's hardly any more code in this release than there was in the previous one - yet refactoring the implementation slightly enabled some handy new scenarios!

 

[Click here to download the IListExtensions implementation and its complete unit test project.]

 

Proper sorting libraries offer a wide variety of ways to sort, compare, and work with sorted lists. IListExtensions is not a proper sorting library - nor does it aspire to be one. :) Rather, it's a small collection of handy methods that make it easy to incorporate sorting into some common Silverlight, WPF, and Windows Phone scenarios. Sometimes you're forced to use a collection (like ObservableCollection(T)) that doesn't do everything you want - but if all you're missing is basic sorting functionality, then IListExtensions just might be the answer!

Something "sort" of handy... [IListExtensions adds easy sorting to .NET list types - enabling faster search and removal, too!]

If you want to display a dynamically changing collection of items in WPF, Silverlight, or Windows Phone, there are a lot of collection classes to pick from - but there's really just one good choice: ObservableCollection(T). Although nearly all the IList(T)/ICollection(T)/IEnumerable(T) implementations work well for static data, dynamic data only updates automatically when it's in a collection that implements INotifyCollectionChanged. And while it's possible to write your own INotifyCollectionChanged code, doing a good job takes a fair amount of work. Fortunately, ObservableCollection(T) does nearly everything you'd want and is a great choice nearly all of the time.

Unless you want your data sorted...

By design, ObservableCollection(T) doesn't sort data - that's left to the CollectionView class which is the officially recommended way to sort lists for display (for more details, please refer to the Data Binding Overview's "Binding to Collections" section). The way CollectionView works is to add an additional layer of indirection on top of your list. That gets sorted and the underlying collection isn't modified at all. This is a fine, flexible design (it enables a variety of other scenarios like filtering, grouping, and multiple views), but sometimes it'd be easier if the actual collection were sorted and the extra layer wasn't present (in addition to imposing a bit of overhead, working with CollectionView requires additional code to account for the indirection).

 

So it would be nice if there were a handy way to sort an ObservableCollection(T) - something like the List(T).Sort method. Unfortunately, ObservableCollection(T) doesn't derive from List(T), so it doesn't have that method... Besides, it'd be better if adding items to the list put them in the right place to begin with - instead of adding them to the wrong place and then re-sorting the entire list after the fact. Along the same lines, scenarios that could take advantage of sorting for faster look-ups would benefit from something like List(T).BinarySearch - which also doesn't exist on ObservableCollection(T).

All we really need to do here is provide custom implementations of add/remove/contains/index-of for ObservableCollection(T) and we'd have the best of both worlds. One way of doing that is to subclass - but that ties the code to a specific base class and limits its usefulness somewhat (just like Sort and BinarySearch for List(T) above). What we can do instead is implement these helper methods in a standalone class and enable them to target the least common denominator, IList(T), and therefore apply in a variety of scenarios (i.e., all classes that implement that interface). What's more, these helpers can be trivially written as extension methods so they'll look just like APIs on the underlying classes!

 

This sounds promising - let's see how it might work by considering the complete IList(T) interface hierarchy:

public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
    T this[int index] { get; set; }         // Good as-is
    int IndexOf(T item);                    // Okay as-is; could be faster if sorted
    void Insert(int index, T item);         // Should NOT be used with a sorted collection (might un-sort it)
    void RemoveAt(int index);               // Good as-is
}
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    int Count { get; }                      // Good as-is
    bool IsReadOnly { get; }                // Good as-is
    void Add(T item);                       // Needs custom implementation that preserves sort order
    void Clear();                           // Good as-is
    bool Contains(T item);                  // Okay as-is; could be faster if sorted
    void CopyTo(T[] array, int arrayIndex); // Good as-is
    bool Remove(T item);                    // Okay as-is; could be faster if sorted
}
public interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();         // Good as-is
}
public interface IEnumerable
{
    IEnumerator GetEnumerator();            // Good as-is
}

To create a sorted IList(T), there's only one method that needs to be written (add) and three others that should be written to take advantage of the sorted collection for better performance (remove, contains, and index-of). (Aside: If you know a list is sorted, finding the right location changes from an O(n) problem to an O(log n) problem. Read more about "big O" notation here.) The only additional requirement we'll impose is that the elements of the collection must have a natural order. One way this is commonly done is by implementing the IComparable(T) interface on the item class. Basic .NET types already do this, as do other classes in the framework (ex: DateTime, Tuple, etc.). Because this interface has just one method, it's easy to add - and can often be implemented in terms of IComparable(T) for its constituent parts!

 

So here's what the IListExtensions class I've created looks like:

static class IListExtensions
{
    public static void AddSorted<T>(this IList<T> list, T item) where T : IComparable<T> { ... }
    public static bool RemoveSorted<T>(this IList<T> list, T item) where T : IComparable<T> { ... }
    public static int IndexOfSorted<T>(this IList<T> list, T item) where T : IComparable<T> { ... }
    public static bool ContainsSorted<T>(this IList<T> list, T item) where T : IComparable<T> { ... }
}

You can use it to create and manage a sorted ObservableCollection(T) simply by adding "Sorted" to the code you already have!

 

[Click here to download the IListExtensions implementation and its complete unit test project.]

 

One downside to the extension method approach is that the existing List(T) methods remain visible and can be called by code that doesn't know to use the *Sorted versions instead. For Contains, IndexOf, and Remove, this is inefficient, but will still yield the correct answer - but for Add and Insert it's a bug because these two methods are likely to ruin the sorted nature of the list when used without care. Once a list becomes unsorted, the *Sorted methods will return incorrect results because they optimize searches based on the assumption that the list is correctly sorted. Subclassing would be the obvious "solution" to this problem, but it's not a good option here because the original methods aren't virtual on ObservableCollection(T)...

I'm not aware of a good way to make things foolproof without giving up on the nice generality benefits of the current approach, so this seems like one of those times where you just need to be careful about what you're doing. Fortunately, most programs probably only call the relevant methods a couple of times, so it's pretty easy to visit all the call sites and change them to use the corresponding *Sorted method instead. [Trust me, I've done this myself. :) ]

Aside: There's a subtle ambiguity regarding what to do if the collection contains duplicate items (i.e., multiple items that sort to the same location). It doesn't seem like it will matter most of the time, so IListExtensions takes the performant way out and returns the first correct answer it finds. It's important to note this is not necessarily the first of a group of duplicate items, nor the last of them - nor will it always be the same one of them! Basically, if the items' IComparable(T) implementation says two items are equivalent, then IListExtensions assumes they are and that they're equally valid answers. If the distinction matters in your scenario, please feel free to tweak this code and take the corresponding performance hit. :) (Alternatively, if the items' IComparable(T) implementation can be modified to distinguish between otherwise "identical" items, the underlying ambiguity will be resolved and things will be deterministic again.)

 

It's usually best to leverage platform support for something when it's available, so please look to CollectionView for your sorting needs in WPF, Silverlight, and Windows Phone applications. But if you end up in a situation where it'd be better to maintain a sorted list yourself, maybe IListExtensions is just what you need!

Don't shoot the messenger [A WebBrowserExtensions workaround for Windows Phone and a BestFitPanel tweak for infinite layout bounds on Windows Phone/Silverlight/WPF]

One of the neat things about sharing code with the community is hearing how people have learned from it or are using it in their own work. Of course, the more people use something, the more likely they are to identify problems with it - which is great because it provides an opportunity to improve things! This blog post is about addressing two issues that came up around some code I published recently.

 

The Platform Workaround (WebBrowserExtensions)

WebBrowserExtensions on Windows Phone

Roger Guess contacted me a couple of days after I posted the WebBrowserExtensions code to report a problem he saw when using it on Windows Phone 7 with the platform's NavigationService in a scenario where the user could hit the Back button to return to a page with a WebBrowser control that had its content set by the WebBrowserExtensions.StringSource property. (Whew!) Instead of seeing the content that was there before, the control was blank! Sure enough, I was able to duplicate the problem after I knew the setup...

My initial theory was that the WebBrowser was discarding its content during the navigation and not being reinitialized properly when it came back into view. Sure enough, some quick testing confirmed this was the case - and what's more, the same problem happens with the official Source property as well! That made me feel a little better because it suggests a bug with the platform's WebBrowser control rather than my own code. :)

The workaround I came up with for StringSource (and that was kindly verified by Roger) should work just as well for the official Source property: I created an application-level event handler for the Loaded event on the WebBrowser and use that event to re-apply the correct content during the "back" navigation. I updated the Windows Phone sample application and added a new button/page to demonstrate the fix in action.

If this scenario is possible with your application, please consider applying a similar workaround!

Aside: Although it should be possible to apply the workaround to the WebBrowserExtensions code itself, I decided that wasn't ideal because of the event handler: the entire StringSource attached dependency property implementation is static, and tracking per-instance data from static code can be tricky. In this case, it would be necessary to ensure the Loaded event handler was added only once, that it was removed when necessary, and that it didn't introduce any memory leaks. Because such logic is often much easier at the application level and because the same basic workaround is necessary for the official WebBrowser.Source property and because it applies only to Windows Phone, it seemed best to leave the core WebBrowserExtensions implementation as-is.
Further aside: This same scenario works fine on Silverlight 4, so it's another example of a Windows Phone quirk that needs to be worked around. (Recall from the previous post that it was already necessary to work around the fact that the Windows Phone WebBrowser implementation can't be touched outside the visual tree.) That's a shame because the scenario itself is reasonable and consistent with the platform recommendation to use NavigationService for everything. The fact that it seems broken for the "real" Source property as well makes me think other people will run into this, too. :(

[Click here to download the WebBrowserExtensions class and samples for Silverlight, Windows Phone, and WPF.]

 

The Layout Implementation Oversight (BestFitPanel)

Eitan Gabay contacted me soon after I posted my BestFitPanel code to report an exception he saw when using one of the BestFitPanel classes as the ItemsPanel of a ListBox at design-time. I hadn't tried that particular configuration, but once I did, I saw the same message: "MeasureOverride of element 'Delay.MostBigPanel' should not return PositiveInfinity or NaN as its DesiredSize.". If you've dealt much with custom Panel implementations, this probably isn't all that surprising... Although coding layout is often straightforward, there can be a variety of edge cases depending on how the layout is done. (For example: only one child, no children, no available size, nested inside different kinds of parent containers, etc..)

In this case, it turns out that the constraint passed to MeasureOverride included a value of double.PositiveInfinity and BestFitPanel was returning that same value. That isn't allowed because the MeasureOverride method of an element is supposed to return the smallest size the element can occupy without clipping - and nothing should require infinite size! (If you think about it, though, the scenario is a little wacky for BestFitPanel: what does it mean to make the best use of an infinite amount of space?)

There are two parts to my fix for this problem. The first part is to skip calling the CalculateBestFit override for infinite bounds (it's unlikely to know what to do anyway) and to Measure all the children at the provided size instead. This ensures all children get a chance to measure during the measure pass - which some controls require in order to render correctly. The second part of the fix is to return a Size with the longest width and height of any child measured when infinite bounds are passed in. Because children are subject to the same rule about not returning an infinite value from Measure, this approach means BestFitPanel won't either and that the Panel will occupy an amount of space that's related to the size of its content (instead of being arbitrary like 0x0, 100x100, etc.).

The combined effect of these changes is to fix the reported exception, provide a better design-time experience, and offer an more versatile run-time experience as well!

All BestFitPanels overlapped

[Click here to download the source code for all BestFitPanels along with sample projects for Silverlight, WPF, and Windows Phone.]

 

The more a piece of code gets looked at and used, the more likely it is that potential problems are uncovered. It can be difficult to catch everything on your own, so it's fantastic to have a community of people looking at stuff and providing feedback when something doesn't work. Thanks again to Robert and Eitan for bringing these issues to my attention and for taking the time to try out early versions of each fix!

I'm always hopeful people won't have problems with my code - but when they do, I really appreciate them taking the time to let me know! :)

"Those who cannot remember the past are condemned to repeat it." [WebBrowserExtensions.StringSource attached dependency property makes Silverlight/Windows Phone/WPF's WebBrowser control more XAML- and binding-friendly]

The WebBrowser control is available in Silverlight 4, Windows Phone 7, and all versions of WPF. It's mostly the same everywhere, though there are some specific differences to keep in mind when using it on Silverlight-based platforms. WebBrowser offers two ways to provide its content: by passing a URI or by passing a string with HTML text:

  1. If you have a URI, you can set the Source (dependency) property in code or XAML or you can call the Navigate(Uri) method from code.

    Aside: It's not clear to me what the Navigate(Uri) method enables that the Source property doesn't, but flexibility is nice, so I won't dwell on this. :)
  2. On the other hand, if you have a string, your only option is to call the NavigateToString(string) method from code.

    XAML and data-binding support for strings? Nope, not so much...

 

I'm not sure why all three platforms have the same limitation, but I suspect there was a good reason at some point in time and maybe nobody has revisited the decision since then. Be that as it may, the brief research I did before writing this post suggests that a good number of people have been inconvenienced by the issue. Therefore, I've written a simple attached dependency property to add support for providing HTML strings in XAML via data binding!

<phone:WebBrowser delay:WebBrowserExtensions.StringSource="{Binding MyProperty}"/>

As you can see above, this functionality is made possible by the StringSource property which is exposed by the WebBrowserExtensions class. It's a fairly simple attached property that just passes its new value on to the WebBrowser's NavigateToString method to do the real work. For everyone's convenience, I've tried to make sure my StringSource implementation works on Silverlight 4, Windows Phone 7, and WPF.

Aside: The StringSource property is read/write from code and XAML, but does not attempt to detect WebBrowser navigation by other means (and somehow "transform" the results into a corresponding HTML string). Therefore, if you're interleaving multiple navigation methods in the same application, reading from StringSource may not be correct - but writing to it should always work!

Aside: Things are more complicated on Windows Phone because the WebBrowser implementation there throws exceptions if it gets touched outside the visual tree. Therefore, if WINDOWS_PHONE is defined (and by default it is for phone projects), this code catches the possible InvalidOperationException and deals with it by creating a handler for the WebBrowser's Loaded event that attempts to re-set the string once the control is known to be in the visual tree. If the second attempt fails, the exception is allowed to bubble out of the method. This seems to work nicely for the typical "string in XAML" scenario, though it's possible more complex scenarios will require a more involved workaround.

My thanks go out to Roger Guess for trying an early version of the code and reminding me of this gotcha!

 

To prove to ourselves that StringSource behaves as we intend, let's create the world's simplest RSS reader! All it will do is download a single RSS feed, parse it for the titles and content of each post, and display those titles in a ListBox. There'll be a WebBrowser control using StringSource to bind to the ListBox's SelectedItem property (all XAML; no code!), so that when a title is clicked, its content will automatically be displayed by the WebBrowser!

 

Here's what it looks like on Silverlight (note that the sample must be run outside the browser because of network security access restrictions in Silverlight):

WebBrowserExtensions on Silverlight

 

And here's the same code running on Windows Phone:

WebBrowserExtensions on Windows Phone

 

And on WPF:

WebBrowserExtensions on WPF

 

[Click here to download the WebBrowserExtensions class and the samples above for Silverlight, Windows Phone, and WPF.]

 

The StringSource attached dependency property is simple code for a simple purpose. It doesn't have a lot of bells and whistles, but it gets the job done nicely and fills a small gap in the platform. You won't always deal with HTML content directly, but when you do, StringSource makes it easy to combine the WebBrowser control with XAML and data binding!

Each one is the best - for different definitions of "best" [The BestFitPanel collection of layout containers provides flexible, easy-to-use options for Silverlight, WPF, and Windows Phone applications]

Just over a year ago, a couple of readers asked me about a WPF/Silverlight Panel that arranged things to make "best use" of available space without requiring the developer to set a bunch of stuff up in advance or know how many child elements there would be. Interestingly, this is not a scenario the default Panel implementations handle particularly well...

  • Grid [WPF/SL/WP] is capable of pretty much anything, but requires the developer to explicitly specify how everything lines up relative to the rows and columns they must manually define.

  • StackPanel [WPF/SL/WP] arranges an arbitrary number of items in a tightly-packed line, but overflows when there are too many and leaves empty space when there are too few.

  • Canvas [WPF/SL/WP] provides the ultimate in flexibility, but contains absolutely no layout logic and pushes all that overhead onto the developer.

  • WrapPanel [WPF/SLTK/WPTK] flows its elements "book-style" left-to-right, top-to-bottom, but runs content off the screen when there's not enough room and can size things surprisingly unless you tell it how big items should be.

    Aside: When scrolling content that doesn't fit is acceptable, WrapPanel can be quite a good choice. And if you like the idea, but want something a little more aesthetically pleasing, please have a look at my BalancedWrapPanel implementation... :)
    Further aside: On the other hand, if you're looking for something more like a StackPanel but with multiple columns (or rows), you might instead be interested in my BandedStackPanel implementation.
  • DockPanel [WPF/SLTK] crams everything against the edge of its layout slot and leaves a big "chunk" in the center for whatever element is lucky enough to end up there.

  • UniformGrid [WPF] does okay at sensible layout without a lot of fuss - but its default behavior can leave a lot of blank space and so it's best if you tell it in advance how many items there are.

 

That said, please don't get me wrong: I'm not complaining about the default set of layout containers - I think they're all good at what they do! However, in the context of the original "just do the right thing for me" scenario, none of them quite seems ideal.

So when this question came up before, I mentioned I'd written some code that seemed pretty relevant, but that it was for Windows Forms and therefore didn't map cleanly to the different layout model used by Silverlight and WPF. Soon thereafter, I created a sample project to implement a "best fit" panel for Silverlight and WPF (and got nearly all the code written!) - but then found myself distracted by other topics and never managed to write it up formally...

 

Until now!

Today I'm sharing the three Panel classes I originally wrote for Silverlight and WPF, two abstract base classes they're built on, an extra Panel I wrote just for this post and a Windows Phone 7 sample application! (Because this code supports Silverlight 3, it works just as well on the phone as on the desktop.) Hopefully the extra goodness in today's release will offset the delay in posting it... :)

 

The foundation for everything, BestFitPanel is an abstract base class that implements MeasureOverride and ArrangeOverride to arrange its children in a grid that's M columns wide and N rows high. What's nice is that the values of M and N are left to subclasses to define by overriding the CalculateBestFit method. Therefore, a subclass only needs to worry about columns/rows and the base class only needs to worry about handling layout.

 

MostBigPanel is a BestFitPanel subclass that figures out which values of M and N maximize the length of the smaller dimension (be it width or height) of each item. In other words, it avoids long, skinny rectangles in favor of more evenly proportioned ones.

MostBigPanel

 

MostFullPanel is a BestFitPanel subclass that maximizes the total area occupied by the Panel's children. Specifically, an arrangement without any empty cells will be preferred over one with an empty cell or two - even if the shape of the resulting items is less balanced.

MostFullPanel

 

Sometimes it's nice to optimize for the "shape" of individual items - and for that there's the BestAnglePanel abstract base class which chooses the combination of M and N that yields items with a diagonal closest to some angle A determined by the GetIdealAngle override.

 

MostSquarePanel is a BestAnglePanel subclass that uses a value of 45° for A and therefore prefers arrangements where items are closest to being square.

MostSquarePanel

 

MostGoldenPanel, on the other hand, is a BestAnglePanel subclass that uses a value for A that matches that of a horizontally-oriented golden rectangle. Golden rectangles are said to be among the most aesthetically pleasing shapes, and this class makes it easy to create layouts based around them.

MostGoldenPanel

 

Of course, there are very few values of M and N to choose from, so it's not uncommon that all the implementations above choose the same values. The interesting differences tend to show up at various "special" sizes where each BestFitPanel selects a different layout. This is why the sample application allows you to enable all the panels at once: the sample content is translucent, so you can see where things differ and how each implementation is handling a particular configuration. I made sure all the arrangements above were unique - here's how it looks when they're all shown at once:

All BestFitPanels overlapped

 

For a real-world example of BestFitPanel in action, I've adapted the "ImageLoading" sample from my Windows Phone 7 PhonePerformance project to use MostBigPanel (which is what I would have used if I'd written this post beforehand!). If you're not familiar with that sample, it finds all the followers of an arbitrary Twitter account and shows their images. Because it's impossible to know in advance how many followers an account has, trying to use one of the "in-box" Panel implementations is likely to be tricky or require writing code to configure things at run-time. But BestFitPanel makes this scenario easy by automatically showing all the items and optimizing for the most important attribute ("bigness" in this case). Here's the same code/XAML with different numbers of followers (400, 200, and 100) to show how things "just work":

BestFitPanel with 400 items BestFitPanel with 200 items BestFitPanel with 100 items

 

[Click here to download the complete source code for all the BestFitPanels along with sample projects for Silverlight, WPF, and Windows Phone 7.]

 

The concept of a reusable, container-agnostic Panel for layout is tremendously powerful. The "stock" implementations for Silverlight, WPF, and Windows Phone are all quite useful, but sometimes you'll find that writing a custom Panel is the only way to get exactly the layout you're looking for. Fortunately, layout code is pretty straightforward - and classes like BestFitPanel and BestAnglePanel make it even easier. So the next time you're looking for a flexible container that works sensibly without requiring a bunch of prior knowledge or hand-holding, I hope you'll remember this post and consider using a BestFitPanel - or a custom subclass! :)

Night of the Living WM_DEADCHAR [How to: Avoid problems with international keyboard layouts when hosting a WinForms TextBox in a WPF application]

There was a minor refresh for the Microsoft Web Platform last week; you can find a list of fixes and instructions for upgrading in this forum post. The most interesting change for the WPF community is probably the fix for a WebMatrix problem brought to my attention by @radicalbyte and @jabroersen where Ctrl+C, Ctrl+X, and Ctrl+V would suddenly stop working when an international keyboard layout was in use. Specifically, once one of the prefix keys (ex: apostrophe, tilde, etc.) was used to type an international character in the editor, WebMatrix's copy/cut/paste shortcuts would stop working until the application was restarted. As it happens, the Ribbon buttons for copy/cut/paste continued to work correctly - but the loss of keyboard shortcuts was pretty annoying. :(

Fortunately, the problem was fixed! This is the story of how...

 

To begin with, it's helpful to reproduce the problem on your own machine. To do that, you'll need to have one of the international keyboard layouts for Windows installed. If you spend a lot of time outside the United States, you probably already do, but if you're an uncultured American like me, you'll need to add one manually. Fortunately, there's a Microsoft Support article that covers this very topic: How to use the United States-International keyboard layout in Windows 7, in Windows Vista, and in Windows XP! Not only does it outline the steps to enable an international keyboard layout, it also explains how to use it to type some of the international characters it is meant to support.

Aside: Adding a new keyboard layout is simple and unobtrusive. The directions in the support article explain what to do, though I'd suggest skipping the part about changing the "Default input language": if you stop at adding the new layout, then your default experience will remain the same and you'll be able to selectively opt-in to the new layout on a per-application basis.

With the appropriate keyboard layout installed, the other thing you need to reproduce the problem scenario is a simple, standalone sample application, so...

[Click here to download the InternationalKeyboardBugWithWpfWinForms sample application.]

 

Compiling and running the sample produces a .NET 4 WPF application with three boxes for text. The first is a standard WPF TextBox and it works just like you'd expect. The second is a standard Windows Forms TextBox (wrapped in a WindowsFormsHost control) and it demonstrates the problem at hand. The third is a trivial subclass of that standard Windows Forms TextBox that includes a simple tweak to avoid the problem and behave as you'd expect. Here's how it looks with an international keyboard layout selected:

InternationalKeyboardBugWithWpfWinForms sample

Although the sample application doesn't reproduce the original problem exactly, it demonstrates a related issue caused by the same underlying behavior. To see the problem in the sample application, do the following:

  1. Make the InternationalKeyboardBugWithWpfWinForms window active.
  2. Switch to the United States-International keyboard layout (or the corresponding international keyboard layout for your country).
  3. Press Ctrl+O and observe the simple message box that confirms the system-provided ApplicationCommands.Open command was received.
  4. Type one of the accented characters into the WinForms TextBox (the middle one).
  5. Press Ctrl+O again and notice that the message box is no longer displayed (and the Tab key has stopped working).

 

As you can see, the InternationalTextBox subclass avoids the problem its WinForms TextBox base class exposes. But how? Well, rather simply:

/// <summary>
/// Trivial subclass of the Windows Forms TextBox to work around problems
/// that occur when hosted (by WindowsFormsHost) in a WPF application.
/// </summary>
public class InternationalTextBox : System.Windows.Forms.TextBox
{
    /// <summary>
    /// WM_DEADCHAR message value.
    /// </summary>
    private const int WM_DEADCHAR = 0x103;

    /// <summary>
    /// Preprocesses keyboard or input messages within the message loop
    /// before they are dispatched.
    /// </summary>
    /// <param name="msg">Message to pre-process.</param>
    /// <returns>True if the message was processed.</returns>
    public override bool PreProcessMessage(ref Message msg)
    {
        if (WM_DEADCHAR == msg.Msg)
        {
            // Mark message as handled; do not pass it on
            return true;
        }

        // Call base class implementation
        return base.PreProcessMessage(ref msg);
    }
}

The underlying problem occurs when the WM_DEADCHAR message is received and processed by both WPF and WinForms, so the InternationalTextBox subclass shown above prevents that by intercepting the message in PreProcessMessage, marking it handled (so nothing else will process it), and skipping further processing. Because the underlying input-handling logic that actually maps WM_DEADCHAR+X to an accented character still runs, input of accented characters isn't affected - but because WPF doesn't get stuck in its WM_DEADCHAR mode, keyboard accelerators like Ctrl+O continue to be processed correctly!

Aside: The change above is not exactly what went into WebMatrix... However, this change appears to work just as well and is a bit simpler, so I've chosen to show it here. The actual change is similar, but instead of returning true, maps the WM_DEADCHAR message to a WM_CHAR message (ex: msg.MSG = WM_CHAR;) and allows the normal base class processing to handle it. I'm not sure the difference matters in most scenarios, but it's what appeared to be necessary at the time, it's what the QA team signed off on, and it's what the WPF team reviewed. :) I'm optimistic the simplified version I show here will work well everywhere, but if you find somewhere it doesn't, please try the WM_CHAR translation instead. (And let me know how it works out for you!)

 

Hosting Windows Forms controls in a WPF application isn't the most common thing to do, but sometimes it's necessary (or convenient!), so it's good to ensure everything works well together. If your application exposes any TextBox-like WinForms classes, you might want to double-check that things work well with an international keyboard layout. And if they don't, a simple change like the one I show here may be all it takes to resolve the problem! :)

PS - I mention above that the WPF team had a look at this workaround for us. They did more than that - they fixed the problem for the next release of WPF/WinForms so the workaround will no longer be necessary! Then QA checked to make sure an application with the workaround wouldn't suddenly break when run on a version of .NET/WPF/WinForms with the actual fix - and it doesn't! So apply the workaround today if you need it - or wait for the next release of the framework to get it for free. Either way, you're covered. :)

sudo localize --crossplatform [Free PseudoLocalizer class makes it easy to identify localization issues in WPF, Silverlight, and Windows Phone 7 applications!]

Two posts ago, I explained the benefits of pseudo-localization and showed an easy way to implement it for WPF - then said I'd outline how to do the same for Silverlight and Windows Phone 7. In my previous post, I went off on the seeming diversion of implementing a PNG encoder for Silverlight. With this post, I'll fulfill my original promise and unify the previous two posts! As you'll see, the basic principles of my approach to WPF localization translate fairly directly to Silverlight - though some limitations in the latter platform make achieving the same result more difficult. Even though more code and manual intervention are required for Silverlight and Windows Phone 7, the benefits are the same and pseudo-localization remains a great way to identify potential problems early in the development cycle.

For completeness I'll show examples and techniques for all platforms below...

 

Normal WPF application PseudoLocalizer in a WPF application

Please see the original post for an explanation of the changes shown above.

 

Adding pseudo-localization of RESX resources to a WPF application

  1. Add the PseudoLocalizer.cs file from the sample download to the project.

  2. Add PSEUDOLOCALIZER_ENABLED to the (semi-colon-delimited) list of conditional compilation symbols for the project (via the Project menu, Properties item, Build tab in Visual Studio).

  3. Add the following code somewhere it will be run soon after the application starts (for example, add a constructor for the App class in App.xaml.cs):

    #if PSEUDOLOCALIZER_ENABLED
        Delay.PseudoLocalizer.Enable(typeof(ProjectName.Properties.Resources));
    #endif
  4. If necessary: Add a project reference to System.Drawing (via Project menu, Add Reference, .NET tab) if building the project now results in the error "The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)".

  5. If necessary: Right-click Resources.resx and choose Run Custom Tool if running the application under the debugger (F5) throws the exception "No matching constructor found on type 'ProjectName.Properties.Resources'. You can use the Arguments or FactoryMethod directives to construct this type.".

 

Adding pseudo-localization of RESX resources to a Silverlight or Windows Phone 7 application

  1. Add the PseudoLocalizer.cs and PngEncoder.cs files from the sample download to the project.

  2. Add PSEUDOLOCALIZER_ENABLED to the (semi-colon-delimited) list of conditional compilation symbols for the project (via the Project menu, Properties item, Build tab in Visual Studio).

  3. Make the following update to the auto-generated resource wrapper class by editing Resources.Designer.cs directly (the highlighted portion is the primary change):

    #if PSEUDOLOCALIZER_ENABLED
        global::System.Resources.ResourceManager temp =
            new Delay.PseudoLocalizerResourceManager("PseudoLocalizerSL.Resources", typeof(Resources).Assembly);
    #else
        global::System.Resources.ResourceManager temp =
            new global::System.Resources.ResourceManager("PseudoLocalizerSL.Resources", typeof(Resources).Assembly);
    #endif

    In case it's not clear, this change simply duplicates the existing line of code that creates an instance of ResourceManager, modifies it to create an instance of Delay.PseudoLocalizerResourceManager instead, and wraps the two versions in an appropriate #if/#else/#endif so pseudo-localization can be completely controlled by whether or not PSEUDOLOCALIZER_ENABLED is #defined.

    Important: This change will be silently overwritten the next time (and every time!) you make a change to Resources.resx with the Visual Studio designer. Please see my notes below for more information on this Silverlight-/Windows Phone-specific gotcha.

 

PseudoLocalizer in a Silverlight application

 

Adding a pseudo-localizable string (all platforms)

  1. Double-click Resources.resx to open the resource editor.

  2. Add the string by name and value.

  3. Reference it from code/XAML.

  4. Silverlight/Windows Phone 7: Re-apply the Delay.PseudoLocalizerResourceManager change to Resources.Designer.cs which was silently undone when the new resource was added.

 

Adding a pseudo-localizable image (WPF only)

  1. Double-click Resources.resx to open the resource editor.

  2. Click the "expand" arrow for Add Resource and choose Add Existing File....

  3. Open the desired image file.

  4. Reference it from code/XAML (possibly via BitmapToImageSourceConverter.cs from the sample ZIP).

 

Adding a pseudo-localizable image (all platforms)

  1. Rename the image file from Picture.jpg to Picture.jpg-bin.

  2. Double-click Resources.resx to open the resource editor.

  3. Click the "expand" arrow for Add Resource and choose Add Existing File....

  4. Open the desired image file.

  5. Reference it from code/XAML (probably via ByteArrayToImageSourceConverter.cs from the sample ZIP).

  6. Silverlight/Windows Phone 7: Re-apply the Delay.PseudoLocalizerResourceManager change to Resources.Designer.cs which was silently undone when the new resource was added.

  7. Optionally: Restore the image's original file name in the Resources folder of the project and manually update its file name in Resources.resx using a text editor like Notepad. (I've done this for the sample project; it makes things a little clearer and it's easier to edit the image resource without having to rename it each time.)

 

PseudoLocalizer in a Windows Phone 7 application

 

There you have it - simple text and image pseudo-localization for WPF, Silverlight, and Windows Phone 7 applications is within your grasp! :) The basic concept is straightforward, though limitations make it a bit more challenging for Silverlight-based platforms. Nevertheless, the time you're likely to save by running PseudoLocalizer early (and often) should far outweigh any inconvenience along the way. By finding (and fixing) localization issues early, your application will be more friendly to customers - no matter what language they speak!

 

[Click here to download the complete source code for PseudoLocalizer, various helper classes, and the WPF, Silverlight, and Windows Phone 7 sample applications shown above.]

 

Notes

  • For a brief overview of using RESX resources in a WPF, Silverlight, or Windows Phone 7 application, please see the "Notes" section of my original PseudoLocalizer post. You'll want to be sure the basic stuff is all hooked up and working correctly before adding PseudoLocalizer into the mix.

  • The act of using RESX-style resources in a Silverlight application is more difficult than it is in a WPF application (independent of pseudo-localization). WPF allows you to directly reference the generated resources class directly from XAML:

    <Window.Resources>
        <properties:Resources x:Key="Resources" xmlns:properties="clr-namespace:PseudoLocalizerWPF.Properties"/>
    </Window.Resources>
    ...
    <TextBlock Text="{Binding Path=Message, Source={StaticResource Resources}}"/>

    However, that approach doesn't work on Silverlight (or Windows Phone 7) because the generated constructor is internal and Silverlight's XAML parser refuses to create instances of such classes. Therefore, most people create a wrapper class (as Tim Heuer explains here):

    /// <summary>
    /// Class that wraps the generated Resources class (for Resources.resx) in order to provide access from XAML on Silverlight.
    /// </summary>
    public class ResourcesWrapper
    {
        private Resources _resources = new Resources();
        public Resources Resources
        {
            get { return _resources; }
        }
    }

    And reference that instead:

    <UserControl.Resources>
        <local:ResourcesWrapper x:Key="Resources" xmlns:local="clr-namespace:PseudoLocalizerSL"/>
    </UserControl.Resources>
    ...
    <TextBlock Text="{Binding Path=Resources.Message, Source={StaticResource Resources}}"/>

    Obviously, the extra level of indirection adds overhead to every place resources are used in XAML - but that's a small price to pay for dodging the platform issue. :)

  • WPF supports private reflection and PseudoLocalizer takes advantage of that to enable a simple, seamless, "set it and forget it" hook-up (via the call to Enable above). Unfortunately, private reflection isn't allowed on Silverlight, so the same trick doesn't work there. I considered a variety of different ways around this, and ultimately settled on editing the generated wrapper class code because it applies exactly the same customization as on WPF. And while it's pretty annoying to have this tweak silently overwritten every time the RESX file is edited, it's simple enough to re-apply and it's easy to spot when reviewing changes before check-in.

  • I explained what's wrong with the default behavior of adding an image to a RESX file in my PngEncoder post:

    [...] the technique I used for [WPF] (reading the System.Drawing.Bitmap instance from the resources class and manipulating its pixels before handing it off to the application) won't work on Silverlight. You see, the System.Drawing namespace/assembly doesn't exist for Silverlight! So although the RESX designer in Visual Studio will happily let you add an image to a Silverlight RESX file, actually doing so results in an immediate compile error [...].

    Fortunately, the renaming trick I use above works well for Silverlight and Windows Phone - and WPF, too. So if you're looking to standardize on a single technique, this is the one. :)

    Even if you're devoted to WPF and don't care about Silverlight, you should still consider the byte[] approach: although System.Drawing.Bitmap is easier to deal with, it's not the right format. (Recall from the original PseudoLocalizer post that I wrote an IValueConverter to convert from it to System.Windows.Media.ImageSource.) Instead of loading images as System.Drawing.Bitmap and converting them with BitmapToImageSourceConverter, why not load them as byte[] and convert them with ByteArrayToImageSourceConverter.cs - and save a few CPU cycles by not bouncing through an unnecessary format?

  • In addition to the renaming technique for accessing RESX images from Silverlight, there's a similar approach (courtesy of Justin Van Patten) that renames to .wav instead and exposes the resource as a System.IO.Stream. For the purposes of pseudo-localization, the two renaming approaches should be basically equivalent - which led me to go as far as hooking everything up and writing StreamToImageSourceConverter.cs before I realized why the Stream approach isn't viable...

    What it comes down to is an unfortunate API definition - the thing that's exposed by the wrapper class isn't a Stream, it's an UnmanagedMemoryStream! And while that would be perfectly fine as an implementation detail, it's not: the type of the auto-generated property is UnmanagedMemoryStream and the type returned by ResourceManager.GetStream is also UnmanagedMemoryStream. But UnmanagedMemoryStream can't be created by user code in Silverlight (and requires unsafe code in WPF), so this breaks PseudoLocalizer's approach of decoding/pseudo-localizing/re-encoding the image because it means the altered bytes can't be wrapped back up in a UnmanagedMemoryStream to maintain the necessary pass-through behavior!

    If only the corresponding RESX interfaces had used the Stream type (a base class of UnmanagedMemoryStream), it would have been possible to wrap the altered image in a MemoryStream and return that - a technique supported by all three platforms. Without digging into this too much more, it seems to me that the Stream type could have been used with no loss of generality - though perhaps there's a subtlety I'm missing.

    Aside: As a general API design guideline, always seek to expose the most general type that makes sense for a particular scenario. That does not mean everything should expose the Object type and cast everywhere - but it does mean that (for example) APIs exposing a stream should use the Stream type and thus automatically work with MemoryStream, UnmanagedMemoryStream, NetworkStream, etc.. Only when an API needs something from a specific subclass should it use the more specific subclass.

    Be that as it may, I didn't see a nice way of wrapping images in a UnmanagedMemoryStream, and therefore recommend using the byte[] approach instead!

What it lacks in efficiency, it makes up for in efficiency! [Silverlight-ready PNG encoder implementation shows one way to use .NET IEnumerables effectively]

At the end of my previous post about easily pseudo-localizing WPF applications, I said this post would show how to apply those concepts to a Silverlight application. Unfortunately, I seem to have made an off-by-one error: while this post is related to that topic, it is not the post I advertised. But it seems interesting in its own right, so I hope you enjoy it. :)

Okay, so what does a PNG (Portable Network Graphics) image encoder have to do with pseudo-localizing on the Silverlight platform? Almost nothing - except for the fact that I went above and beyond with my last post and showed how to pseudo-localize not just text, but images as well. It turns out the technique I used for that (reading the System.Drawing.Image instance from the resources class and manipulating its pixels before handing it off to the application) won't work on Silverlight. You see, the System.Drawing namespace/assembly doesn't exist for Silverlight! So although the RESX designer in Visual Studio will happily let you add an image to a Silverlight RESX file, actually doing so results in an immediate compile error: The type or namespace name 'Drawing' does not exist in the namespace 'System' (are you missing an assembly reference?)...

 

But all is not lost - there are other ways of adding an image to a RESX file! True, the process is a little wonky and cumbersome, but at least it works. However, the resulting resource is exposed as either a byte[] or a Stream instance - both of which are basically just a sequence of bytes. And because there's no SetPixel method for byte arrays, this is a classic "Houston, we have a problem" [sic; deliberately misquoting] moment for my original approach of pseudo-localizing the image by manipulating its pixels... Hum, what's a girl to do?

Well, those bytes do correspond to an encoded image, so it ought to be possible to decode the image - at which point we could wrap it in a WriteableBitmap and do the pixel manipulation via its Pixels property. After that, we could re-encode the altered pixels back to an image file (remember that the resource data is expected to be the encoded bytes of an image) and things should work as seamlessly as they did for the original scenario. And they actually do! Well, on WPF, at least...

 

PngEncoder sample image

Sample PNG file encoded by PngEncoder

 

Not on Silverlight, though. Silverlight will get you all the way to the last step, but that's the end of the line: the platform doesn't expose a way to do the re-encoding. :( As you might guess, this is hardly the first time this issue has come up - a quick web search turns up plenty of examples of people wanting to encode images under Silverlight. The typical recommendation is to find (or write) your own image encoder, and so there are a number of examples of Silverlight-compatible image encoders to be found!

So why did I write my own??

Well, because none of the examples I found was quite what I wanted. The most obvious candidate just flat out didn't work; it crashed on every input I gave it. The runner-up (deliberately) took shortcuts for speed and didn't produce valid output. The third option was released under a license I'm not able to work with. And the fourth was part of a much larger image encoding library I didn't feel like pulling apart. Besides, I release full source code on my blog, and I don't want to be in the business of distributing other peoples' code with my samples. A lot of times it's just easier and safer to code something myself - and I've had a lot of great learning experiences as a result! :)

 

When choosing an image format for re-encoding on the fly, Silverlight makes the decision fairly easy because it supports only two image formats: JPEG and PNG. Because JPEG is a lossy format, it's pretty much a non-starter (we don't want to degrade image quality) and therefore lossless PNG is the obvious choice. Conveniently, the PNG image format is fairly simple - especially if you're willing to punt on (losslessly) compressing the image! All you need to encode a PNG file is a format prefix (8 bytes), a header chunk (5 bytes), an image chunk with the properly encoded pixels, and an end chunk (0 bytes). There's a decent amount of bookkeeping to be done along the way (scanline filtering, compression block creation, two different hash algorithms, etc.), but it's all fairly straightforward. And the PNG specification is well-written and fairly easy to follow - what more could you ask for?

Aside: Giving up on compression may seem like a big deal, but I don't think it is for the scenario at hand. While small file size is important for making the best use of long-term storage, the pseudo-localization scenario creates its PNG file on the fly, loads it, and immediately discards it. The encoded image simply isn't around for very long and so its large size shouldn't matter.

Okay, so PNG encoding isn't rocket science - but I still feel that if I'm going to reinvent the wheel, then at least I should try to contribute something new or interesting to the mix! :)

 

What I've done here is to use IEnumerable everywhere:

namespace Delay
{
    /// <summary>
    /// Class that encodes a sequence of pixels to a sequence of bytes representing a PNG file (uncompressed).
    /// </summary>
    /// <remarks>
    /// Reference: http://www.w3.org/TR/PNG/.
    /// </remarks>
    static class PngEncoder
    {
        /// <summary>
        /// Encodes the specified pixels to a sequence of bytes representing a PNG file.
        /// </summary>
        /// <param name="width">Width of the image.</param>
        /// <param name="height">Height of the image.</param>
        /// <param name="pixels">Pixels of the image in ARGB format.</param>
        /// <returns>Sequence of bytes representing a PNG file.</returns>
        public static IEnumerable<byte> Encode(int width, int height, IEnumerable<int> pixels) { /* ... */ }
    }
}

And IEnumerable isn't just for the public API, it's used throughout the code, too! This is the meaning behind the title of this post: giving up on compression is inefficient from a storage space perspective, but operating exclusively on enumerations is very efficient from a memory consumption and a computational efficiency perspective! Because of that, this class can encode a 20-megabyte PNG file without allocating any more memory than it takes to encode a 1-byte PNG file. What's more, no work is done before it needs to be: if you're streaming a PNG file across a slow transport, the file will be read and encoded only as quickly as the receiver consumes the bytes - and if encoding is aborted mid-way for some reason, no unnecessary effort has been wasted!

This efficiency is possible thanks to the way IEnumerable works and the convenience of C#'s yield return which makes it easy to write code that returns an IEnumerable without explicitly implementing IEnumerator. As a result, the code for PngEncoder is clear and linear with no hint (or visible complexity) of the allocation savings or deferred processing that occur under the covers. Instead, everything communicates in terms of byte sequences - translating one sequence to another inline as necessary. For example, every horizontal line of an encoded PNG image is prefixed with a byte indicating which filtering algorithm was used. These filter bytes aren't part of the original pixels that are passed into the Encode call, so they need to be added. What's cool is that it's easy to write a method to do so - and because it takes an IEnumerable input parameter and returns an IEnumerable result, such a method can be trivially "injected" into any data flow!

 

One particularly handy realization of this technique is demonstrated by a class I called WrappedEnumerable - here's what it looks like to the developer:

/// <summary>
/// Class that wraps an IEnumerable(T) or IEnumerator(T) in order to do something with each byte as it is enumerated.
/// </summary>
/// <typeparam name="T">Type of element being enumerated.</typeparam>
abstract class WrappedEnumerable<T> : IEnumerable<T>, IEnumerator<T>
{
    /// <summary>
    /// Method called to initialize for a new (or reset) enumeration.
    /// </summary>
    protected virtual void Initialize() { }

    /// <summary>
    /// Method called for each byte output by the underlying enumerator.
    /// </summary>
    /// <param name="value">Next value.</param>
    protected abstract void OnValueEnumerated(T value);
}

WrappedEnumerable is useful for PngEncoder because the encoding process makes use of two different hash algorithms: CRC-32 and Adler-32. Long-time readers of this blog know I'm no stranger to hash functions - in fact, I've previously shared code to implement CRC-32 based on the very same PNG specification! But as much as I love .NET's HashAlgorithm, using it here seemed like it might be overkill. HashAlgorithm is ideal for processing large chunks of data at a time, but the sequence-oriented nature of PngEncoder deals with a single byte at a time. So what I did was to create WrappedEnumerable subclasses Crc32WrappedEnumerable and Adler32WrappedEnumerable which override the two methods above to calculate their hash values as the bytes flow through the system! These are both simple classes and work quite well for "injecting" hash math into a data flow. The PNG format puts stores its hash values after the corresponding data, so by the time that value is needed, the relevant data has already flowed through the WrappedEnumerable and the final hash is ready to be retrieved.

 

However, the same cannot be said of the length fields in the PNG specification... Lengths are stored before the corresponding data, and that poses a distinct problem when you're trying to avoid looking ahead: without knowing what data is coming, it's hard to know how much there is! But I'm able to cheat here: because PngEncoder doesn't compress, it turns out that all the internal lengths can be calculated from the original width and height values passed to the call to Encode! So while this is a bit algorithmically impure, it completely sidesteps the length issue and avoids the need to buffer up arbitrarily long sequences of data just to know how long they are.

Aside: After spending so much time celebrating the IEnumerable way of life, this is probably a good time to highlight one of its subtle risks: multiple enumeration due to deferred execution. Multiple enumeration is something that comes up a lot in the context of LINQ - an IEnumerable<byte> is not the same thing as a byte[]. Whereas it's perfectly reasonable to pass a byte[] off to two different functions to deal with, doing the same thing with an IEnumerable<byte> usually results in that sequence being created and enumerated twice! Depending on where the sequence comes from, this can range from inefficient (a duplication of effort) to catastrophic (it may not be possible to generate the sequence a second time). The topic of multiple enumeration is rich enough to merit its own blog post (here's one and here's another and here's another), and I won't go into it further here. But be on the look-out, because it can be tricky!
Further aside: To help avoid this problem in PngEncoder, I created the TrackingEnumerable and TrackingEnumerator classes in the sample project. These are simple IEnumerable/IEnumerator implementations except that they output a string with Debug.WriteLine whenever a new enumeration is begun. For the purposes of PngEncoder, seeing any more than one of these outputs represents a bug!

 

In an attempt to ensure that my PngEncoder implementation behaves well and produces valid PNG files, I've written a small collection of automated tests (included with the download). Most of them are concerned with parameter validation and correct behavior with regard to the IEnumerable idiosyncrasies I mentioned earlier, but the one called "RandomImages" is all about output verification. That test creates 25 PNG files of random size and contents, encodes them with PngEncoder, then decodes them with two different decoder implementations (System.Drawing.Bitmap and System.Windows.Media.Imaging.PngBitmapDecoder) and verifies the output is identical to the input. I've also verified that PngEncoder's PNG files can be opened in a variety of different image viewing/editing applications. (Interesting tidbit: Internet Explorer seems to be the strictest about requiring valid PNG files!) While none of this is a guarantee that all images will encode successfully, I'm optimistic typical scenarios will work well for people. :)

 

[Click here to download the source code for the complete PngEncoder.cs implementation, a simple test application, and the automated test suite.]

 

At the end of the day, the big question is whether my focus (obsession?) with an IEnumerable-centric implementation was justified. From a practical point of view, you could argue this either way - I happen to think things worked out pretty elegantly, but others might argue the code would be clearer with explicit allocations. I'll claim the memory/computational benefits I describe here are pretty compelling (especially in resource-constrained scenarios like Windows Phone), but others could reasonably argue that naïve consumers of PngEncoder are likely to write their code in a way that negates those benefits (ex: by calling ToArray).

However, I'm not going to spend a lot of time second-guessing myself; I think it was a great experience. :) The following sums up my thoughts on the matter pretty nicely:

The road of life twists and turns and no two directions are ever the same. Yet our lessons come from the journey, not the destination. - Don Williams, Jr.