Delay's Blog

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

Posts
  • Delay's Blog

    When you live on the bleeding edge, be prepared for a few nicks [Minor update to the Delay.Web.Helpers ASP.NET assembly download to avoid a NuGet packaging bug affecting Razor installs]

    I had a surprise last week when coworker Bilal Aslam mentioned he was using my Delay.Web.Helpers assembly but wasn't able to install the 1.1.0 version with the ASP.NET Razor "_Admin" control panel. (Fortunately, the previous version (1.0.0) did install and had the functionality he needed, so he was using that one for the time being.) I quickly told Bilal he was crazy because I remembered testing with the NuGet plugin for Visual Studio and knew it installed successfully. At which point he demonstrated the problem for me - and I was forced to admit defeat. :)

    Aside: Delay.Web.Helpers is a collection of ASP.NET web helpers that provide access to Amazon Simple Storage Service (S3) buckets and blobs as well as easy ways to create "data URIs". (And eventually more stuff as I get time to add it...)

    Naturally, the first thing I did was to repeat my previous testing in Visual Studio - and it worked fine just like I remembered. So I tried with the Razor administration interface and it failed just like Bilal showed me: "System.InvalidOperationException: The 'schemaVersion' attribute is not declared.". Because the previous version (1.0.0) didn't have this problem, I was a little confused; I'd built everything from the same .nuspec file, so it wasn't clear why the Razor/1.1.0 scenario would be uniquely broken.

    At that point, I contacted a couple folks on the NuGet team and got a quick answer: for some (short) period of time, the official version of nuget.exe created packages with a schemaVersion attribute on the package/metadata element of the embedded .nuspec file and the presence of this attribute causes the Razor install implementation to fail with the exception we were seeing. I'd created 1.0.0 with a good version of nuget.exe, but apparantly created 1.1.0 with the broken version. :(

    The team's recommendedation was to re-create my packages with the current nuget.exe and re-deploy them to the NuGet servers. I did that and the result is version 1.1.1 of the Delay.Web.Helpers package and its associated Delay.Web.Helpers.SampleWebSite package. "Once bitten, twice shy", so I verified the install in both Visual Studio and Razor now that I know they're different and can fail independently.

    Aside: There are no changes to the Delay.Web.Helpers assembly or samples in this release. The only changes are the necessary tweaks to the NuGet metadata for both packages to install successfully under Razor. Therefore, if you've already installed 1.1.0 successfully, there's no need to upgrade.
    Further aside: The standalone ZIP file with the assembly, source code, automated tests, and sample web site is unaffected by this update.

     

    To sum things up, if you created a NuGet package sometime around early April and you expect it to be installable with the Razor administration panel, I'd highly recommend trying it out to be sure! :)

  • Delay's Blog

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

    • 4 Comments

    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!

  • Delay's Blog

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

    • 5 Comments

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

  • Delay's Blog

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

    • 10 Comments

    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!

  • Delay's Blog

    Images in a web page: meh... Images *in* a web page: cool! [Delay.Web.Helpers assembly now includes an ASP.NET web helper for data URIs (in addition to Amazon S3 blob/bucket support)]

    • 2 Comments
    Delay.Web.Helpers DataUri sample page

    The topic of "data URIs" came up on a discussion list I follow last week in the context of "I'm in the process of creating a page and have the bytes of an image from my database. Can I deliver them directly or must I go through a separate URL with WebImage?" And the response was that using a data URI would allow that page to deliver the image content inline. But while data URIs are fairly simple, there didn't seem to be a convenient way to use them from an ASP.NET MVC/Razor web page.

    Which was kind of fortuitous for me because I've been interested in learning more about data URIs for a while and it seemed that creating a web helper for this purpose would be fun. Better yet, I'd already released the Delay.Web.Helpers assembly (with support for Amazon S3 blob/bucket access), so I had the perfect place to put the new DataUri class once I wrote it! :)

    Aside: For those who aren't familiar, the WebImage class provides a variety of handy methods for dealing with images on the server - including a Write method for sending them to the user's browser. However, the Write method needs to be called from a dedicated page that serves up just the relevant image, so it isn't a solution for the original scenario.

     

    In case you've not heard of them before, data URIs are a kind of URL scheme documented by RFC 2397. They're quite simple, really - here's the relevant part of the specification:

    data:[<mediatype>][;base64],<data>
    
    dataurl    := "data:" [ mediatype ] [ ";base64" ] "," data
    mediatype  := [ type "/" subtype ] *( ";" parameter )
    data       := *urlchar
    parameter  := attribute "=" value
    

    It takes two pieces of information to create a data URI: the data and its media type (ex: "image/png"). (Although the media type appears optional above, it defaults to "text/plain" when absent - which is unsuitable for most common data URI scenarios.) Pretty much the only interesting thing you can do with data URIs on the server is write them, so the DataUri web helper exposes a single Write method with five flavors. The media type is always passed as a string (feel free to use the MediaTypeNames class to help here), but the data can be provided as a file name string, byte[], IEnumerable<byte>, or Stream. That's four methods; the fifth one takes just the file name string and infers the media type from the file's extension (ex: ".png" -> "image/png").

    Aside: Technically, it would be possible for the other methods to infer media type as well by examining the bytes of data. However, doing so would require quite a bit more work and would always be subject to error. On the other hand, inferring media type from the file's extension is computationally trivial and much more likely to be correct in practice.

     

    For an example of the DataUri helper in action, here's the Razor code to implement the "image from the database" scenario that started it all:

    @{
        IEnumerable<dynamic> databaseImages;
        using (var database = Database.Open("Delay.Web.Helpers.Sample.Database"))
        {
            databaseImages = database.Query("SELECT * FROM Images");
        }
    
        // ...
    
        foreach(var image in databaseImages)
        {
            <p>
                <img src="@DataUri.Write(image.Content, image.MediaType)" alt="@image.Name"
                     width="@image.Width" height="@image.Height" style="vertical-align:middle"/>
                @image.Name
            </p>
        }
    }

    Easy-peasy lemon-squeezy!

     

    And here's an example of using the file name-only override for media type inference:

    <script src="@DataUri.Write(Server.MapPath("Sample-Script.js"))" type="text/javascript"></script>

    Which comes out like this in the HTML that's sent to the browser:

    <script src="data:text/javascript;base64,77u/ZG9j...PicpOw==" type="text/javascript"></script>
    Aside: This particular example (using a data URI for a script file) doesn't render in all browsers. Specifically, Internet Explorer 8 (and earlier) blocks script delivered like this because of security concerns. Fortunately, Internet Explorer 9 has addressed those concerns and renders as expected. :)

     

    [Click here to download the Delay.Web.Helpers assembly, complete source code, automated tests, and the sample web site.]

    [Click here to go to the NuGet page for Delay.Web.Helpers which includes the DLL and its associated documentation/IntelliSense XML file.]

    [Click here to go to the NuGet page for the Delay.Web.Helpers.SampleWebSite which includes the sample site that demonstrates everything.]

     

    Data URIs are pretty neat things - though it's important to be aware they have their drawbacks as well. Fortunately, the Wikipedia article does a good job discussing the pros and cons, so I highly recommend looking it over before converting all your content. :) Creating a data URI manually isn't rocket science, but it is the kind of thing ASP.NET web helpers are perfectly suited for. If you're a base-64 nut, maybe you'll continue doing this by hand - but for everyone else, I hope the new DataUri class in the Delay.Web.Helpers assembly proves useful!

  • Delay's Blog

    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]

    • 13 Comments

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

  • Delay's Blog

    What next, a DOS version?? [Free tool and source code to temporarily prevent a computer from entering sleep mode - 32-bit and 64-bit versions now support Windows XP!]

    • 11 Comments

    I used .NET 3.5 to write the first version of Insomnia about a year and a half ago. Its purpose in life is to make it easy for anyone to temporarily prevent their computer from going to sleep without having to futz with system-wide power options every time. Insomnia did its job well and people used it in ways I didn't expect: there were requests to allow the window to be minimized to the notification area, so I posted an update a few months later which allowed that. As a result, some people leave Insomnia running for extended periods of time (e.g., many days) and seemed likely to benefit from a version that didn't include the overhead of the .NET Framework, so I created new, native-code 32- and 64-bit versions of Insomnia late last year. These new versions did exactly what they were intended to and were also well received - but there's a catch...

    Every couple of weeks or so, Insomnia gets featured by one of those "cool app of the day" sites (which is awesome, so thanks for that!). I never know when it's going to happen, but I can tell exactly when it does because I suddenly get a flurry of comments telling me the native-code versions don't run on Windows XP...

    Aside: Many of you may be too young to remember; Windows XP is an operating system Microsoft released about ten years ago - practically an eternity in computing terms! And yet, a lot of people are still running XP... ;)

     

    Although I didn't set out to not support Windows XP, I also didn't make a specific effort to support it - and as the saying goes, "if you don't test it, it won't work". Well it didn't work, so I investigated and summarized my findings in a reply on my blog:

    I've looked into the Windows XP (SP3) issue just now - the "ordinal 380" error is due to the fact that the LoadIconMetric API isn't supported on OSes prior to Windows Vista. It's easy enough to use the more general LoadIcon API and then native Insomnia starts successfully on XP - but it doesn't show any text. The missing text is because the LM_GETIDEALSIZE message isn't supported prior to Vista, either - and in this case the simpler LM_GETIDEALHEIGHT message isn't a direct substitute. What's more, the current icon doesn't seem to be recognized by XP, either (it shows the generic application icon in Explorer and nothing in the tray). Neither of these issues (text measurement and icon) are overly difficult to fix, but they're also not trivial and I'm not sure how worthwhile it is to spend time and effort to support native Insomnia on Windows XP when the .NET version is reported to work just fine...

    That said, I'm open to input here. If a lot of people think this is useful, I'll look into doing the work - but if everyone's happy to use the .NET version on XP, I'm happy to let them. :)

    For what it's worth, I don't recall anybody coming back with a compelling reason why XP support is necessary - but after getting another round of XP feedback recently, I decided it was something I should do simply because it comes up so often. Therefore, I'm happy to announce that Insomnia's native 32-bit and 64-bit versions now support Windows XP!

    Insomnia running on Windows XP

     

    Note: With today's update (and in lieu of feedback to the contrary), the native versions of Insomnia are believed to run everywhere the .NET version does. Because there's no functional difference between the native- and managed-code implementations, I'll probably deprecate the .NET version soon. (Please don't get me wrong: I love the significant productivity benefits of using .NET! They're just moot here because I'm already doing the work to maintain the native implementations.)

     

    Notes:

    • By far the biggest change with this release is the replacement of the SysLink control with one I wrote called IdealSizeStatic. Recall from the previous post that there were two things I liked about SysLink: its ability to return an "ideal size" and its ability to render hyperlinks. Both of these features worked like I wanted, but the lack of support for LM_GETIDEALSIZE on XP was a deal-breaker. When creating IdealSizeStatic, I kept things as simple as possible while also being consistent with how SysLink operates (ex: the use of WM_CTLCOLORSTATIC and WM_SETFONT) just in case I decide to switch back some day. IdealSizeStatic ends up being a pretty general-purpose control that offers WM_GETIDEALSIZE for querying the bounds (width and height) of its text - which is handy for the WPF-like layout pass I implemented in Insomnia.

      Despite my goal of keeping IdealSizeStatic as simple as possible, I didn't want to give up on the hyperlink functionality Insomnia already used, so I did the work to support that scenario. Specifically, if the first character of its window title is '_', IdealSizeStatic assumes it's showing a link, strips off the '_' prefix, and renders the control text in blue. Assuming WS_TABSTOP has also been set, it will show a focus rectangle when relevant and respond to mouse left-button clicks and space bar presses by calling ShellExecuteEx to open the link in the user's preferred web browser.

      IdealSizeStatic won't win any awards for "Win32 control of the year", but it seems to do just enough to be an adequate replacement for SysLink. :)

    • Having purged the SysLink control, Insomnia no longer has a dependency on the COMCTL32 common control DLL and the corresponding code to initialize it and incorporate the necessary manifest has been removed.

    • The other problem with running on XP was the use of the LoadIconMetric API to retrieve the application icon for the notification area. Fortunately, it's pretty simple to fall back to the LoadImage API instead - though it doesn't offer the same "auto-scaling" magic the original function does.

    • Speaking of icons, one curious problem with XP was that Windows Explorer showed the default application icon instead of the custom one embedded in the Insomnia executable. This ends up being because the custom ICO file generator I wrote/use outputs icon images in the 32-bit alpha-blended PNG format and that format isn't supported on Windows XP. This time around, I've also embedded 16x16 and 32x32 icons with the older 24-bit image+mask format. Because this type is recognized by XP, the icon shows up correctly.

      Aside: Unfortunately, XP still has a bit of trouble rendering the bottom-most pixels of the 32x32 icon in the application's property page. But that property page seems to have a variety of troubles with icons - there was a similar issue with the previous version even under Windows 7!
    • I've also fixed a couple of minor issues I noticed along the way - none of which should matter much in practice. The new version of the 32-bit and 64-bit builds of Insomnia is 2011-03-19. The version number of the .NET build has not changed and remains 2010-01-29.

     

    [Click here to download the 32-bit, 64-bit, and .NET builds of Insomnia along with the complete source code for everything.]

     

    I initially put off supporting XP because I figured very few people still used it. However, the steady stream of feedback from XP users finally convinced me there's enough interest to make the effort worthwhile. Windows 7 and Windows Vista users won't see functional differences with this release, but they will benefit from the slightly reduced footprint that comes from breaking the ties to COMCTL32.dll. My IdealSizeStatic control isn't a perfect replacement for the SysLink control, but it gets close enough - and does so without being overly complex.

    Windows XP users: I hope you like the new support. Thanks for your patience! :)

  • Delay's Blog

    MEF lab [How to: Keep implementation details out of a MEF contract assembly by implementing an interface-based event handler]

    • 4 Comments

    In the previous post, I showed how to combine .NET 4's "type embedding" capability with the Managed Extensibility Framework (MEF) to create an application that can be upgraded without breaking existing extensions. In that post, I described the notion of a MEF "contract assembly" thusly:

    The contract assembly is the place where the public interfaces of an application live. Because interfaces are generally the only thing in a contract assembly, both the application and its extensions can reference it and it can be published as part of an SDK without needing to include implementation details, too.

    The idea is pretty straightforward: an application's public API should be constant even if the underlying implementation changes dramatically. Similarly, the act of servicing an application (e.g., patching it, fixing bugs, etc.) should never require updates to its public contract assembly.

     

    Fortunately it's pretty easy to ensure a contract assembly contains only interfaces and .NET Framework classes (which are safe because they're automatically versioned for you). But things get a little tricky when you decide to expose a custom event...

    Imagine your application defines an event that needs to pass specific information in its EventArgs. The first thing you'd do is create a subclass - something like MyEventArgs - and store the custom data there. Because the custom event is part of a public interface, it's part of the contract assembly - and because the contract assembly is always self-contained, the definition of MyEventArgs needs to be in there as well. However, while MyEventArgs is probably quite simple, its implementation is... um... an implementation detail [ :) ] and does not belong in the contract assembly.

    Okay, no problem, create an IMyEventArgs interface for the custom properties/methods (which is perfectly acceptable for a contract assembly) and then add something like the following to the application (or an extension):

    class MyEventArgs : EventArgs, IMyEventArgs

    The public interface (in the contract assembly) only needs to expose the following and all will be well:

    event EventHandler<IMyEventArgs> MyEvent;

    Oops, sorry! No can do:

    The type 'IMyEventArgs' cannot be used as type parameter 'TEventArgs' in
    the generic type or method 'System.EventHandler<TEventArgs>'. There is no
    implicit reference conversion from 'IMyEventArgs' to 'System.EventArgs'.

     

    The compiler is complaining there's no way for it to know that an arbitrary implementation of IMyEventArgs can always be converted to an EventArgs instance (which is what the TEventArgs generic type parameter of EventHandler<TEventArgs> is constrained to). Because there's not an obvious way to resolve this ambiguity (the implicit keyword doesn't help because "user-defined conversions to or from an interface are not allowed"), you might be tempted to move the definition of MyEventArgs into the contract assembly and pass that for TEventArgs instead. That will certainly work (and it's not the worst thing in the world) but it feels like there ought to be a better way...

    And there is! Instead of using a new-fangled generic event handler, we could instead use a classic .NET 1.0-style delegate in our contract assembly:

    /// <summary>
    /// Interface for extensions to MyApplication.
    /// </summary>
    public interface IMyContract
    {
        /// <summary>
        /// Simple method.
        /// </summary>
        void MyMethod();
    
        /// <summary>
        /// Simple event with custom interface-based EventArgs.
        /// </summary>
        event MyEventHandler MyEvent;
    }
    
    /// <summary>
    /// Delegate for events of type IMyEventArgs.
    /// </summary>
    /// <param name="o">Event source.</param>
    /// <param name="e">Event arguments.</param>
    public delegate void MyEventHandler(object o, IMyEventArgs e);
    
    /// <summary>
    /// Interface for custom EventArgs.
    /// </summary>
    public interface IMyEventArgs
    {
        /// <summary>
        /// Simple property.
        /// </summary>
        string Message { get; }
    }

    With this approach, the contract assembly no longer needs to include the concrete MyEventArgs implementation: the delegate above is strongly-typed and doesn't have the same constraint as EventHandler<TEventArgs>, so it works great! With that in place, the implementation details of the MyEventArgs class can be safely hidden inside the extension (or application) assembly like so:

    /// <summary>
    /// Implementation of a custom extension for MyApplication.
    /// </summary>
    [Export(typeof(IMyContract))]
    public class MyExtension : IMyContract
    {
        /// <summary>
        /// Simple method outputs the extension's name and invokes its event.
        /// </summary>
        public void MyMethod()
        {
            Console.WriteLine("MyExtension.MyMethod");
            var handler = MyEvent;
            if (null != handler)
            {
                handler(this, new MyEventArgs("MyEventArgs"));
            }
        }
    
        /// <summary>
        /// Simple event.
        /// </summary>
        public event MyEventHandler MyEvent;
    }
    
    /// <summary>
    /// Implementation of a custom interface-based EventArgs.
    /// </summary>
    class MyEventArgs : EventArgs, IMyEventArgs
    {
        /// <summary>
        /// Simple property.
        /// </summary>
        public string Message { get; private set; }
    
        /// <summary>
        /// Initializes a new instance of the MyEventArgs class.
        /// </summary>
        /// <param name="message">Property value.</param>
        public MyEventArgs(string message)
        {
            Message = message;
        }
    }

     

    The sample application I've created to demonstrate this practice in action (a simple executable/contract assembly/extension assembly trio) is quite simple and works just as you'd expect. Here's the output:

    MyApplication.Run
    MyExtension.MyMethod
    MyEventArgs

     

    [Click here to download the complete source code for the MefContractTricks sample.]

     

    Keeping a MEF application's contract assemblies as pure and implementation-free as possible is a good and noble goal. There may be times when it is necessary to make compromises and allow implementation details to sneak into the contract assembly, but the use of custom event arguments does not need to be one of them!

    So instead of being generic - and failing - go retro for the win! :)

  • Delay's Blog

    MEF addict [Combining .NET 4's type embedding and MEF to enable a smooth upgrade story for applications and their extensions]

    • 2 Comments

    One of the neat new features in version 4 of the .NET Framework is something called "type equivalence" or "type embedding". The basic idea is to embed at compile time all the type information about a particular reference assembly into a dependent assembly. Once this is done, the resulting assembly no longer maintains a reference to the other assembly, so it does not need to be present at run time. You can read more about type embedding in the MSDN article Type Equivalence and Embedded Interop Types.

    Although type equivalence was originally meant for use with COM to make it easier to work against multiple versions of a native assembly, it can be used successfully without involving COM at all! The MSDN article Walkthrough: Embedding Types from Managed Assemblies (C# and Visual Basic) explains more about the requirements for this along with explicit steps to use type embedding with an assembly.

     

    Here's a simple interface that is enabled for type embedding:

    using System.Runtime.InteropServices;
    
    [assembly:ImportedFromTypeLib("")]
    
    namespace MyNamespace
    {
        [ComImport, Guid("1F9BD720-DFB3-4698-A3DC-05E40EDC69F1")]
        public interface MyInterface
        {
            string Name { get; }
            string GetVersion();
        }
    }

     

    Another thing that's new with .NET 4 (though it had previously been available on CodePlex) is the Managed Extensibility Framework (MEF). MEF makes it easy to implement a "plug-in" architecture for applications where assemblies are loosely coupled and can be added or removed without explicit configuration. While there have been a variety of not-so-successful attempts to create a viable extensibility framework before this, there's general agreement that MEF is a good solution and it's already being used by prominent applications like Visual Studio.

     

    Here's a simple MEF-enabled extension that implements - and exports - the interface above:

    [Export(typeof(MyInterface))]
    public class MyExtension : MyInterface
    {
        public string Name
        {
            get { return "MyExtension"; }
        }
    
        ...
    }

    And here's a simple MEF-enabled application that uses that extension by importing its interface:

    class MyApplication
    {
        [ImportMany(typeof(MyInterface))]
        private IEnumerable<MyInterface> Extensions { get; set; }
    
        public MyApplication()
        {
            var catalog = new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
            var container = new CompositionContainer(catalog);
            container.SatisfyImportsOnce(this);
        }
    
        private void Run()
        {
            Console.WriteLine("Application: Version={0}", Assembly.GetEntryAssembly().GetName().Version.ToString());
            foreach (var extension in Extensions)
            {
                Console.WriteLine("Extension: Name={0} Version={1}", extension.Name, extension.GetVersion());
            }
        }
    
        ...
    }

     

    The resulting behavior is just what you'd expect:

    P:\MefAndTypeEmbedding>Demo
    
    Building...
    Staging V1...
    Running V1 scenario...
    
    Application: Version=1.0.0.0
    Extension: Name=MyExtension Version=1.0.0.0
    
    ...

     

    However, it's important to note that MEF does not isolate an application from versioning issues! Ideally, extensions written for version 1 of an application will automatically load and run under version 2 of that application without needing to be recompiled - but you don't get that for free. There is an easy way to do this, though: avoid making any changes to the contract assembly after v1 is released. :)

    Aside: The contract assembly is the place where the public interfaces of an application live. Because interfaces are generally the only thing in a contract assembly, both the application and its extensions can reference it and it can be published as part of an SDK without needing to include implementation details, too.

    But because the whole point of version 2 is to improve upon version 1, it's quite likely the contract assembly will undergo some changes along the way. This is where problems come up: assuming the contract assembly was strongly-named and its assembly version updated (as it should be if its contents have changed!), v1 extensions will not load for the v2 application because they won't be able to find the same version of the contract assembly they were compiled against...

    Aside: If the contract assembly was not strongly-named, then v1 extensions might be able to load the v2 version - but it won't be what they're expecting and that can lead to problems.

     

    Here's an updated version of the original interface with a new Author property for version 2:

    using System.Runtime.InteropServices;
    
    [assembly:ImportedFromTypeLib("")]
    
    namespace MyNamespace
    {
        [ComImport, Guid("1F9BD720-DFB3-4698-A3DC-05E40EDC69F1")]
        public interface MyInterface
        {
            string Name { get; }
            string GetVersion();
            string Author { get; }
        }
    }

     

    One way to solve the versioning problem is to ship the v1 contract assembly and the v2 contract assembly along with the v2 application. (Of course, this can be tricky if both assemblies have the same file name, so you'll probably also want to name them uniquely.) Shipping multiple versions of a contract assembly works well enough (it's pretty typical for COM components), but it can also cause some confusion for Visual Studio when it sees multiple same-named interfaces referenced by the v2 application - not to mention the developer burden of managing multiple distinct versions of the "same" interface...

    Fortunately, there's another way that doesn't require the v2 application to include the v1 contract assembly at all: type embedding. If the contract assembly is enabled for type embedding and v1 extension authors enable that when compiling, all the relevant bits of the contract assembly will be included with the v1 extension and there will be no need for the v1 contract assembly to be present. What that means is "reasonable" interface changes during development of the v2 application will automatically be handled by .NET and v1 extensions will work properly without any need to recompile/upgrade/etc.!

    Aside: By "reasonable" interface changes, I mean removing properties or methods (and therefore not calling the v1 implementations) or adding them (which will throw MissingMethodException for v1 extensions that don't support the new property/method). Changes to existing properties and methods are trickier and probably best avoided as a general rule.

     

    The v2 version of the sample application uses the Author property when it's present (for v2 extensions), but gracefully handles the case where it's not (as for v1 extensions):

    private void Run()
    {
        Console.WriteLine("Application: Version={0}", Assembly.GetEntryAssembly().GetName().Version.ToString());
        foreach (var extension in Extensions)
        {
            string author;
            try
            {
                author = extension.Author;
            }
            catch (MissingMethodException)
            {
                author = "[Undefined]";
            }
            Console.WriteLine("Extension: Name={0} Version={1} Author={2}", extension.Name, extension.GetVersion(), author);
        }
    }

     

    Here's the v2 version in action:

    ...
    
    Staging V2...
    Running V2 scenario...
    
    Application: Version=2.0.0.0
    Extension: Name=MyExtension Version=1.0.0.0 Author=[Undefined]
    Extension: Name=MyExtension Version=2.0.0.0 Author=Me

     

    [Click here to download the complete source code for the sample application/contract assembly/extensions and demo script used here.]

     

    Type embedding and MEF are both fairly simple concepts that add a layer of flexibility to enable some pretty powerful scenarios. As is sometimes the case, the whole is greater than the sum of its parts and combining these two technologies provides an elegant solution to the tricky problem of upgrading an application without breaking existing plug-ins.

    If you aren't already familiar with MEF or type embedding, maybe now is a good time to learn! :)

     

    PS - My thanks go out to Kevin Ransom on the CLR team for providing feedback on a draft of this post. (Of course, any errors are entirely my own!)

  • Delay's Blog

    "Your feedback is important to us; please stay on the line..." [Improving Windows Phone 7 application performance is even easier with these LowProfileImageLoader and DeferredLoadListBox updates]

    • 21 Comments

    A few months ago I began a similar post about LowProfileImageLoader/DeferredLoadListBox updates by saying:

    Windows Phone 7 applications run on hardware that's considerably less powerful than what drives typical desktop and laptop machines. Therefore, tuning phone applications for optimum performance is an important task - and a challenging one! To help other developers, I previously coded and blogged about two classes: LowProfileImageLoader (pushes much of the cost of loading images off the UI thread) and DeferredLoadListBox (improves the scrolling experience for long lists). These two classes can be used individually or together and have become a regular part of the recommendations for developers experiencing performance issues.

     

    In the time since, I've continued to hear from people who are benefitting from LowProfileImageLoader and DeferredLoadListBox - and the code has even been incorporated into the WP7Contrib project! Along the way, I've also collected some great feedback, so I recently dedicated time to make a few improvements:

    PhonePerformance List Scrolling sample
    • The most significant change is that I've removed the use of the UIElement.TransformToVisual platform-level method from DeferredLoadListBox because it has proven to be unreliable on Windows Phone 7 by throwing exceptions unexpectedly. Because this is not the first time I've had to fix crashes due to random ArgumentExceptions ("The parameter is incorrect."), I recommend not using the TransformToVisual method in Windows Phone 7 applications until/unless the underlying problem is fixed. In the meantime, it has been my experience that the LayoutInformation.GetLayoutSlot method can often be used as a substitute with just a little bit of extra effort.

      I'd like to thank Tore Lervik, Baldelli Gabriele, and Holger Schmeken for reporting this problem.

      Aside: Another time I had to remove TransformToVisual was for the Silverlight for Windows Phone Toolkit's ContextMenu control. (This fix was part of the November 2010 release).
    • I've previously explained why DeferredLoadListBox requires every container to have a height (note: each height can be different!). However, there are some scenarios where the Windows Phone 7 platform will report ActualHeight to be 0 for a container even though its height has been explicitly and correctly set (ex: via ItemContainerStyle). (Note: This seems to occur most often during scrolling.) Fortunately, I found an easy workaround that appears to resolve this problem in cases where the platform is misbehaving: a call to the UpdateLayout API is sufficient to correct the value of ActualHeight.

      I'd like to thank Rich Griffin and Michael James for reporting this problem.

    • LowProfileImageLoader originally used a Queue to implement "first in, first out" (FIFO) behavior of the image downloads it performs. This is a "fair" implementation and is ideal for slowly scrolling up/down a list that uses LowProfileImageLoader and DeferredLoadListBox together. However, for the scenario of quickly scrolling such a list in a single direction, FIFO behavior means the images you see on the screen will be among the last to load. The "obvious" fix is to switch from a Queue to a Stack which gives "last in, first out (LIFO) behavior instead. But while that's better for the second scenario, it's worse for the first one - and it leads to a weird visual effect in apps like my ImageLoading sample (part of the download) because the "wall" of images loads bottom-to-top instead of "top-to-bottom" as people expect.

      Clearly, there's no perfect answer here, so the solution is to do well on average! The classic way of amortizing unpredictable cost is to introduce randomness (ex: the QuickSort algorithm) - so instead of processing FIFO or LIFO, LowProfileImageLoader now works through its queue of pending work in random order. As a result, both the fast and the slow scrolling scenarios show images quickly and the application appears more responsive overall!

      Aside: The way I've implemented randomization is a slight variation of the solution to a classic programming puzzle: How do you sort a deck of N cards in linear time and constant space? If you haven't seen this one before, take a minute to think about it before following this link to a description of the Fisher-Yates/Knuth shuffle.
    • Though I initially meant for LowProfileImageLoader and DeferredLoadListBox to be used together, there's no reason LowProfileImageLoader can't be used on its own. In fact, I previously ensured that it works fine when used with the default ListBox/VirtualizingStackPanel combination. However, when the user is scrolling such a list very quickly, the default container recycling behavior means there will be multiple data bindings applied to a particular container in rapid succession. Every one of these will enqueue a request for LowProfileImageLoader to download the corresponding image - but only the most recent one matters. Any previous requests are "stale" and although it's safe to satisfy them, it's also unnecessary. Therefore, I've made a change with this update to detect stale requests and discard them before making an expensive web request on their behalf. This difference doesn't matter in non-virtualizing scenarios, but for virtualizing scenarios the amount of unnecessary work it saves can quickly add up!

    • Another consequence of using LowProfileImageLoader in the presence of container recycling is that re-used Image elements kept their old content until new content had been downloaded. This could lead to temporarily misleading UI where images show up alongside content they aren't associated with. It happens because LowProfileImageLoader didn't previously "null-out" the Source property when a new request was made. I've modified the code so it does now - and the virtualizing experience is nicer because of it.

    • When implementing the worker thread logic for LowProfileImageLoader, I intended for it to process WorkItemQuantum number of items each time through the loop until the queue of requests was exhausted. I wrote the following code:

      for (var i = 0; (i < pendingRequests.Count) && (i < WorkItemQuantum); i++)

      I'd like to thank Ashish Gupta for pointing out a bug here; what I meant was:

      for (var i = 0; (0 < pendingRequests.Count) && (i < WorkItemQuantum); i++)

      Coding errors in loops can cause serious problems if they result in an attempt to process too many or too few items. I got lucky here because there's no functional bug due to the original typo - the only downside is that performance might be a little worse because it takes a couple of extra passes through the loop to complete once the count drops below WorkItemQuantum. Fortunately, the value of WorkItemQuantum is only 5, so the real-world impact of this is minimal. However, the whole point of this code is to help improve performance, so I've fixed the oversight. :)

    • And finally, because I recently became a NuGet publishing "expert", I've created a package for the PhonePerformance assembly to make it easy to reference for all the NuGet fans out there. It contains the same binary you'd download below, but it contains only the assembly (and its XML IntelliSense file) - the three sample projects are available only with the ZIP download. This split seems like a reasonable compromise to me: reference from the NuGet gallery if you know what you're doing and just need to add the binary to your project - or - read the relevant blog posts and download the samples if you're getting started.

     

    [Click here to download the compiled PhonePerformance assembly, sample applications, and full source code for everything.]

    -OR-

    [Click here to visit the NuGet gallery page for a package containing the PhonePerformance assembly.]

     

    Windows Phone 7 developers must pay attention to performance because otherwise it's easy to end up with a slow, badly-behaved application. The PhonePerformance assembly focuses on two common scenarios (image loading and list scrolling) and attempts to improve upon the default experience by making it easy to avoid known problem areas. As with any performance technique, results can vary greatly depending on the specifics of each scenario, so it's important to take measurements and test everything on real phone hardware.

    Many developers have told me they had success with the PhonePerformance assembly - I hope you do, too! :)

Page 3 of 28 (277 items) 12345»