Delay's Blog

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

Posts
  • Delay's Blog

    So long, and thanks for all the fish! [Moving my blog to a new location - and hosting it on Node.js!]

    After blogging for many years here on MSDN, I'm switching to a custom blog implementation on my own server.

    I know, right?

    Well, it's not as crazy as it sounds; I explain everything in the introductory post.

    Summarizing briefly: All future posts/comments happen on the new blog. This blog is now read-only.

    Those of you interested in continuing the journey will please join me at "The blog of dlaa.me" and/or subscribe to the RSS feed.

    Thank you!

  • Delay's Blog

    Is it still cheating if you come up with the cheat yourself? [Simple code to solve a "sliding pieces" puzzle]

    I was playing around with one of those "rearrange the pieces on the board" puzzles recently and realized I was stumped after unsuccessfully making the same moves over and over and over...:|

    Aside: If you're not familiar with sliding puzzles, the 15-puzzle and Klotski puzzle are both classic examples.

     

    For the particular puzzle I was stuck on, the board looks like:

      ######
     #      #
     #      #
     #      #
    #        #
     #  ##  #
     #  ##  #
      ##  ##

    And uses the following seven pieces:

    AA  BB  CC  DD   E  FF   G
    AA  BB  CC  D   EE   F   GG

    The challenge is to get the 'A' piece from here:

      ######
     #      #
     #      #
     #      #
    #        #
     #AA##  #
     #AA##  #
      ##  ##

    To here:

      ######
     #      #
     #      #
     #      #
    #        #
     #  ##AA#
     #  ##AA#
      ##  ##

    With the starting state:

      ######
     #DDBBFF#
     #DEBBGF#
     #EECCGG#
    #   CC   #
     #AA##  #
     #AA##  #
      ##  ##

    Go ahead and give it a try if you want a challenge! You can make your own puzzle out of paper cutouts or build something with interlocking cubes (which I can say from experience works quite well).

    Aside: I'm not going to reveal where the original puzzle came from because I don't want to spoil it for anyone. But feel free to leave a comment if it looks familiar!

     

    Now, maybe the puzzle's solution is/was obvious to you, but like I said, I was stuck. As it happens, I was in a stubborn mood and didn't want to admit defeat, so I decided to write a simple program to solve the puzzle for me!

    I'd done this before (many years ago), and had a decent sense of what was involved; I managed to bang out the solution below pretty quickly. My goal was to solve the puzzle with minimal effort on my part - so the implementation favors simplicity over performance and there's a lot of room for improvement. Still, it finds the solution in about a second and that's more than quick enough for my purposes.:)

    I've included the complete implementation below. The code should be fairly self-explanatory, so read on if you're interested in one way to solve something like this. Two things worth calling out are that this approach is guaranteed to find the solution with the fewest number of moves and it can handle arbitrarily-shaped pieces and boards - both of which flow pretty naturally from the underlying design.

    There's not much more to say - except that you should feel free to reuse the code for your own purposes!

     

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    
    // Quick (minimally-optimized) solver for a typical "slide the pieces" puzzle.
    // * Implements breadth-first search to guarantee it finds the optimal solution
    // * Detects already-seen states and avoids analyzing them again
    // * Handles arbitrarily shaped pieces and irregular/non-contiguous boards
    // Performance notes:
    // * Board.GetHashCode is called *very* often; keeping it fast is ideal
    // * Board.IsValid is also called frequently and should be quick to run
    // * Board._locations could have the board location removed (it's always 0)
    // * It may be more efficient check validity before creating Board instances
    class SlidePuzzleSolver
    {
        public static void Main()
        {
    #if DEBUG
            // Quick sanity check of the Board class
            var test = new Board();
            Debug.Assert(test.IsValid);
            Debug.Assert(!test.IsSolved);
    #endif
    
            // Stopwatch is handy for measuring/improving performance
            var stopwatch = new Stopwatch();
            stopwatch.Start();
    
            // Initialize
            Board start = new Board();
            Board solved = null;
            var seen = new HashSet<Board>(start); // IEqualityComparer<Board>
            var todo = new Queue<Board>();
            todo.Enqueue(start);
            seen.Add(start);
    
            // Keep going as long as there are unseen states...
            while (0 < todo.Count)
            {
                // Get the next board and process its moves
                var board = todo.Dequeue();
                foreach (var move in board.GetMoves())
                {
                    if (move.IsSolved)
                    {
                        // Solved!
                        solved = move;
                        todo.Clear();
                        break;
                    }
                    if (!seen.Contains(move))
                    {
                        // Enqueue the new state
                        todo.Enqueue(move);
                        seen.Add(move);
                    }
                }
            }
    
            // Write elapsed time to debug window
            stopwatch.Stop();
            Debug.WriteLine("Elapsed time: {0}ms", stopwatch.ElapsedMilliseconds);
    
            // Reverse the solved->start parent chain
            Debug.Assert(null != solved);
            var solution = new Stack<Board>();
            while (null != solved)
            {
                solution.Push(solved);
                solved = solved.Parent;
            }
    
            // Display the solution start->solved
            foreach (var board in solution)
            {
                board.Show();
            }
        }
    
        // Representation of a board and the arrangement of pieces on it
        // (IEqualityComparer<Board> is used by HashSet<Board> above)
        private class Board : IEqualityComparer<Board>
        {
            // Constants for board size
            private const int Width = 10;
            private const int Height = 8;
    
            // Statics for pieces and moves
            private static readonly IReadOnlyList<Piece> _pieces;
            private static readonly IReadOnlyList<int> _deltas;
    
            static Board()
            {
                // Initialize (shared) piece instances
                // Pieces are defined by cells they occupy as deltas from their origin (top-left)
                // Cells are numbered 0..N going from top-left to bottom-right
                // The board is treated as a piece, but never allowed to move
                var pieces = new List<Piece>();
                pieces.Add(new Piece('A', 0, 1, 10, 11)); // Square
                pieces.Add(new Piece('B', 0, 1, 10, 11)); // Square
                pieces.Add(new Piece('C', 0, 1, 10, 11)); // Square
                pieces.Add(new Piece('D', 0, 1, 10));     // 'L'
                pieces.Add(new Piece('E', 1, 10, 11));    // 'L'
                pieces.Add(new Piece('F', 0, 1, 11));     // 'L'
                pieces.Add(new Piece('G', 0, 10, 11));    // 'L'
                pieces.Add(new Piece('#', 2, 3, 4, 5, 6, 7, 11, 18, 21, 28, 31, 38, 40, 49, 51, 54, 55, 58, 61, 64, 65, 68, 72, 73, 76, 77)); // Irregular board shape
                _pieces = pieces.AsReadOnly();
                // Initialize move deltas (each represents one cell left/right/up/down)
                _deltas = new int[] { -1, 1, -Width, Width };
            }
    
            // Piece locations
            private readonly int[] _locations;
    
            // Parent board
            public Board Parent { get; private set; }
    
            // Create starting state of the puzzle
            public Board()
            {
                // Board piece (last element) is always at offset 0
                _locations = new int[] { 52, 14, 34, 12, 22, 16, 26, 0 };
            }
    
            // Create a board from its parent
            private Board(Board parent, int[] locations)
            {
                Parent = parent;
                _locations = locations;
            }
    
            // Get the valid moves from the current board state
            public IEnumerable<Board> GetMoves()
            {
                // Try to move each piece (except for the board)...
                for (var p = 0; p < _pieces.Count - 1; p++)
                {
                    // ... in each direction...
                    foreach (var delta in _deltas)
                    {
                        // ... to create the corresponding board...
                        var locations = (int[])_locations.Clone();
                        locations[p] += delta;
                        var board = new Board(this, locations);
                        // ... and return it if it's valid
                        if (board.IsValid)
                        {
                            yield return board;
                        }
                    }
                }
            }
    
            // Checks whether a board is valid (i.e., has no overlapping cells)
            public bool IsValid
            {
                get
                {
                    // Array to track occupied cells
                    var locations = new bool[Width * Height];
                    // For each piece (including the board)...
                    for (var p = 0; p < _pieces.Count; p++)
                    {
                        var piece = _pieces[p];
                        var offsets = piece.Offsets;
                        // ... for each cell it occupies...
                        for (var o = 0; o < offsets.Length; o++)
                        {
                            // ... check if the cell is occupied...
                            var location = _locations[p] + offsets[o];
                            if (locations[location])
                            {
                                // Already occupied; invalid board
                                return false;
                            }
                            // ... and mark it occupied
                            locations[location] = true;
                        }
                    }
                    return true;
                }
            }
    
            // Checks if the board is solved
            public bool IsSolved
            {
                get
                {
                    // All that matters in *this* puzzle is whether the 'A' piece is at its destination
                    return (56 == _locations[0]);
                }
            }
    
            // Show the board to the user
            public void Show()
            {
                // Clear the console
                Console.Clear();
                // For each piece (including the board)...
                for (var p = 0; p < _pieces.Count; p++)
                {
                    var piece = _pieces[p];
                    // ... for each offset...
                    foreach (var offset in piece.Offsets)
                    {
                        // ... determine the x,y of the cell...
                        var location = _locations[p] + offset;
                        var x = location % Width;
                        var y = location / Width;
                        // ... and plot it on the console
                        Console.SetCursorPosition(x, y);
                        Console.Write(piece.Marker);
                    }
                }
                // Send the cursor to the bottom and wait for a key
                Console.SetCursorPosition(0, Height);
                Console.ReadKey();
            }
    
            // IEqualityComparer<Board> implemented on this class for convenience
    
            // Checks if two boards are identical
            public bool Equals(Board x, Board y)
            {
                return Enumerable.SequenceEqual(x._locations, y._locations);
            }
    
            // Gets a unique-ish hash code for the board
            // XORs the shifted piece locations into an int
            public int GetHashCode(Board b)
            {
                var hash = 0;
                var shift = 0;
                foreach (var i in b._locations)
                {
                    hash ^= (i << shift);
                    shift += 4;
                }
                return hash;
            }
        }
    
        // Representation of a piece, its visual representation, and the cells it occupies
        private class Piece
        {
            public char Marker { get; private set; }
            public int[] Offsets { get; private set; }
    
            public Piece(char marker, params int[] offsets)
            {
                Marker = marker;
                Offsets = offsets;
            }
        }
    }
  • Delay's Blog

    Setting a value to null might be more dangerous than you think [Simple ToolTipServiceExtensions class avoids a runtime exception in Windows Store apps]

    I was playing around with a Windows Store app this weekend and ran into a pretty annoying problem. At first, I couldn't tell what was going on; it seemed the app would crash at random times for no apparent reason. But after a bit of debugging to isolate the problem, I figured out what was going on.

    Problem: If you have a ToolTipService.ToolTip data binding in a Windows Store app and the value of that binding transitions from non-null to null while it's being displayed, the next time the tooltip is shown, the platform will throw a NullReferenceException from native code and terminate the application.

    This is a surprisingly severe consequence for something that's likely to happen with some regularity, so you'd expect someone to have run into it before now. And indeed, someone did: tkrasinger reported this problem in November of last year. It's unclear where things went from there, but I've verified the problem still occurs with fully-patched Windows 8 and also with the Windows 8.1 preview released last week.

    At first glance, the situation seems pretty dire because there's no clear way to intercept the exception. And while changing the code to use an empty string instead of null does avoid the crash, it also results in an ugly little white square when you hover. Fortunately, if you know a bit about how tooltips work, you know there's a ToolTip class that gets injected in this scenario to host the bound content. What if we intercepted the binding and made sure to always provide a non-null ToolTip instance? Would that avoid the crash?

    Spoiler alert: It does. :)

     

    There are various ways you might go about implementing this workaround - I chose to use a custom attached property because the result looks the same in XAML and neatly encapsulates all the code in one simple, standalone class.

    Let's say you were using a tooltip like so:

    <Border ToolTipService.ToolTip="{Binding BindingThatCanBecomeNull}">
        <TextBlock Text="Watch out, ToolTipService might crash your app..."/>
    </Border>

    As we've established, if that binding goes null while the user is mouse-ing around, the app is likely to crash soon afterward.

     

    So let's use my ToolTipServiceExtensions class to avoid the problem! First, download ToolTipServiceExtensions.cs from the link below and add it to your Windows Store app project. Next, add the corresponding namespace declaration to the top the XAML:

    xmlns:delay="using:Delay"

    And lastly, tweak the XAML to use ToolTipServiceExtensions instead of ToolTipService:

    <Border delay:ToolTipServiceExtensions.ToolTip="{Binding BindingThatCanBecomeNull}">
        <TextBlock Text="ToolTipServiceExtensions saves the day!"/>
    </Border>

    That's it - you're done! Random crashes from null-going tooltips should be a thing of the past. :)

     

    [Click here to open ToolTipServiceExtensions.cs or right-click/save-as to download it to your machine]

     

    Aside: If you're using any of the other ToolTipService properties in your code, they are unaffected by this change. All ToolTipServiceExtensions does is wrap the content in a ToolTip before deferring to the existing ToolTipService implementation.

     

    For the curious, here's what the code looks like:

    namespace Delay
    {
        /// <summary>
        /// Class containing a replacement for ToolTipService.SetToolTip that works
        /// around a Windows 8 platform bug where NullReferenceException is thrown
        /// from native code the next time a ToolTip is displayed if its Binding
        /// transitions from non-null to null while on screen.
        /// </summary>
        public static class ToolTipServiceExtensions
        {
            /// <summary>
            /// Gets the value of the ToolTipServiceExtensions.ToolTip XAML attached property for an object.
            /// </summary>
            /// <param name="obj">The object from which the property value is read.</param>
            /// <returns>The object's tooltip content.</returns>
            [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Underlying method will validate.")]
            public static object GetToolTip(DependencyObject obj)
            {
                return (object)obj.GetValue(ToolTipProperty);
            }
    
            /// <summary>
            /// Sets the value of the ToolTipServiceExtensions.ToolTip XAML attached property.
            /// </summary>
            /// <param name="obj">The object to set tooltip content on.</param>
            /// <param name="value">The value to set for tooltip content.</param>
            [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Underlying method will validate.")]
            public static void SetToolTip(DependencyObject obj, object value)
            {
                obj.SetValue(ToolTipProperty, value);
            }
    
            /// <summary>
            /// Gets or sets the object or string content of an element's ToolTip.
            /// </summary>
            [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Stardard attached property implementation.")]
            public static readonly DependencyProperty ToolTipProperty =
                DependencyProperty.RegisterAttached(
                    "ToolTip",
                    typeof(object),
                    typeof(ToolTipServiceExtensions),
                    new PropertyMetadata(null, ToolTipPropertyChangedCallback));
    
            /// <summary>
            /// Method called when the value of an element's ToolTipServiceExtensions.ToolTip XAML attached property changes.
            /// </summary>
            /// <param name="element">Element for which the property changed.</param>
            /// <param name="args">Event arguments.</param>
            private static void ToolTipPropertyChangedCallback(DependencyObject element, DependencyPropertyChangedEventArgs args)
            {
                // Capture the new value
                var newValue = args.NewValue;
    
                // Create a ToolTip instance to display the new value
                var toolTip = new ToolTip { Content = newValue };
    
                // Hide the ToolTip instance if the new value is null
                // (Prevents the display of a small white rectangle)
                if (null == newValue)
                {
                    toolTip.Visibility = Visibility.Collapsed;
                }
    
                // Defer to ToolTipService.SetToolTip for the actual implementation
                ToolTipService.SetToolTip(element, toolTip);
            }
        }
    }
  • Delay's Blog

    64 bits ought to be enough for everybody [TextAnalysisTool.NET update for .NET 2.0 and 64-bit enables the analysis of larger files!]

    • 12 Comments

    TextAnalysisTool.NET is a free program designed to excel at viewing, searching, and navigating large files quickly and efficiently.

    I wrote the first version of TextAnalysisTool back in 2000 using C++ and Win32. In 2003, I rewrote it using the new .NET 1.0 Framework - and upgraded to .NET 1.1 later that year. There were a variety of improvements between then and 2006, the date of the last update to TextAnalysisTool.NET. Along the way, I've heard from a lot of people who use this tool to simplify their daily workflow! In the past year, I've started getting requests for 64-bit support from folks working with extremely large files that don't fit in the 4GB virtual address space limits of a normal 32-bit process on Windows. Although .NET 1.1 didn't support 64-bit processes, .NET 2.0 does, and I've decided it's finally time to take the plunge.:)

    Animated GIF showing basic TextAnalysisTool.NET functionality

     

    With this release, TextAnalysisTool.NET has been compiled using the .NET 2.0 toolset and the AnyCPU option which automatically matches a process to the architecture of its host operating system. On 32-bit OSes, TextAnalysisTool.NET will continue to run as a 32-bit process, but on 64-bit OSes, it will run as a 64-bit process and have access to a significantly larger address space. This makes it possible to work with larger log files without the risk of crashing into the 4GB memory limit (which can end up being as low as 1.7 GB in practice)!

    Other than a few exceedingly minor string updates, I have made no changes to the way TextAnalysisTool.NET behaves - so the new version should feel just like the previous one. The framework update means .NET 1.1 is no longer a supported platform and .NET 2.0 is now natively supported. The included .config file allows the same executable to run under .NET 4.0 as-is (for example on Windows 8 without the optional ".NET Framework 3.5" feature installed).

    If you've ever run out of memory using TextAnalysisTool.NET, please give this new version a try! And if not, go ahead and continue using the previous version without worrying that you're missing out on anything.:)

     

    Click here to download the latest version of TextAnalysisTool.NET

    Click here to visit the TextAnalysisTool.NET web page for more information

     

    Many thanks to everyone for all the great feedback - I love getting messages from people around the world who are using TextAnalysisTool.NET to make their lives easier!

     

    Aside: As a matter of technical interest, details on the one bug I found with 64-bit TextAnalysisTool.NET: the following code had worked fine for the last decade (message.WParam is an IntPtr via Form.WndProc for WM_MOUSEWHEEL):
    int wheelDelta = ((int)message.WParam)>>16; // HIWORD(WPARAM)
    However, when that assignment ran under .NET 2.0 on a 64-bit OS, it quickly threw OverflowException! I was surprised, but it turns out this is documented behavior. Because that line interoperates with the Windows API, I couldn't change the types involved - but I could tweak the code to avoid the exception by avoiding the problematic explicit conversion:
    int wheelDelta = ((int)((long)message.WParam))>>16; // HIWORD(WPARAM)
    Yep, the proverbial one-line fix saves the day!
  • Delay's Blog

    Happy Birthday, FeedWorm! [A simple, efficient (free!) Google Reader client for keeping up with RSS feeds on Windows Phone]

    • 1 Comments
    FeedWorm feed view

    A little over a year ago, I got tired of searching for a Google Reader client on Windows Phone that worked exactly how I wanted. So I started writing one myself. My plan was to create a simple, efficient app that began by doing as little as possible - and gradually add more advanced features as time allowed. My goal was to understand the complete end-to-end experience of writing and publishing an application in the phone ecosystem.

    I released the first version of FeedWorm one year ago today. It was free then; it remains free now. And while FeedWorm has a few more features today than when it started, it's still very much an exercise in minimalism (which helps keep it fast!).

    To be clear, there are plenty of good Google Reader clients in the Marketplace and if one of them already meets your needs, there's little reason to switch. But if you're looking for something different - and aren't very demanding - FeedWorm might right for you. :)

     

    [Click here to learn more about FeedWorm]

     

  • Delay's Blog

    "If I have seen further, it is by standing on the shoulders of giants" [An alternate implementation of HTTP gzip decompression for Windows Phone]

    • 3 Comments

    The HTTP protocol supports data compression of network traffic via the Content-Encoding header. Compressing network traffic is beneficial because it reduces the amount of data that needs to be transmitted over the network - and sending fewer bytes obviously takes less time! The tradeoff is that it takes a bit of extra work to decompress the data, but because the bottleneck is nearly always network, HTTP compression should be a win pretty much every time. And when you're dealing with comparatively slow, unreliable networks like the ones used by cell phones, the advantages of compression are even more significant.

    Aside: Because most networks are lossy and transfer data in packets, sending just one fewer byte can be meaningful if it reduces the number of packets.

     

    You might reasonably expect that enabling compression for Windows Phone web requests is as simple as setting the HttpWebRequest.AutomaticDecompression property available since .NET 2.0. Unfortunately, this property is not supported by current versions of the Windows Phone platform, so it's up to application developers to add HTTP compression support themselves. Consequently, a number of home-grown solutions have cropped up.

    One popular example is Morten Nielsen's GZipWebClient. Naturally, Morten didn't want to implement his own compression library, so he's using SharpZipLib to do the heavy lifting. This is a great example of reuse, but it's important to note that SharpZipLib is licensed under the GNU General Public License (GPL) and some developers won't be comfortable with the implications of using GPL code in their own project. (For more on what those implications are, the Wikipedia article for GPL has a fairly detailed overview which includes mention of ambiguities around the definition of "derivative works".)

    Aside: Of course, there are other options. Another popular library is DotNetZip which claims to be under the MS-PL (Microsoft Public License), the same permissive "do pretty much whatever you want with the code" license I use for this blog. However, a brief look at the license files it comes with suggests maybe that's not the whole story because there are at least four other licenses called out there.

    As you can probably tell, I'm not the biggest fan of lawyers, ambiguity, or unnecessary risk [ :) ], so I'm always happy to have a simple, "no restrictions" solution - even if that means I have to create one myself. In this case, what I wanted was a way to decompress gzip data on Windows Phone without needing a separate library. That's when I remembered another of Morten's posts, this one about unzipping files under Silverlight. I figured that maybe if I put the chocolate in the peanut butter, I could come up with a simple, dependency-free solution to the gzip problem on Windows Phone!

     

    The basic idea is to create a bit of code that customizes the user's WebClient/HttpWebRequest to add the Accept-Encoding header. Once that's in place, servers that support gzip automatically compress their response bodies. To decompress the downloaded response on the phone, another bit of code is used to wrap the compressed response stream in a ZIP archive and hand it off to Application.GetResourceStream which does the heavy lifting and provides access to the decompressed response stream. What's nice about this technique is that the decompression implementation is part of the Silverlight framework - meaning applications don't need to pull in a bunch of external code or increase their size!

    I wanted this to be easy, so I've created a WebClient subclass and all you have to do is change this:

    client = new WebClient();

    Into this:

    client = new GzipWebClient();

    After which your application's HTTP requests will automatically benefit from gzip compression!

    Of course, some people like to work a little closer to the metal and I've done a similar thing for the HttpWebRequest/HttpWebResponse crowd. Change this:

    request = WebRequest.CreateHttp(_uri);
    request.BeginGetResponse(callback, request);
    // ... response = (HttpWebResponse)request.EndGetResponse(result);
    stream = response.GetResponseStream();
    // ...

    Into this:

    request = WebRequest.CreateHttp(_uri);
    request.BeginGetCompressedResponse(callback, request);
    // ... response = (HttpWebResponse)request.EndGetResponse(result);
    stream = response.GetCompressedResponseStream();
    // ...

    And you're set!

     

    Great, that's all well and good, but does any of this really matter? Less is clearly more, but is there actually a noticeable difference when using gzip? I wanted to answer that question for myself, so the sample application for this post not only exercises the code I've written, it also acts as a simple, real-time performance report! The sample makes continuous requests for http://microsoft.com/ using WebClient, standard HttpWebRequest/HttpWebResponse, my custom GzipWebClient, my Compressed helpers for HttpWebRequest/HttpWebResponse, and (optionally) Morten's GZipWebClient (for comparison purposes). As the data is collected, it's charted via the Silverlight Toolkit's Data Visualization library (which I've previously shown running on Windows Phone).

    Here's what it looks like running in the emulator using a wired connection:

    GzipDemo on Windows Phone

    In the chart above, you can clearly see which requests are using gzip and which aren't! Not only are the gzip-enabled requests noticeably faster on average, they're nearly always faster even in the worst case. What's more, while there's variability for both kinds of requests (that's part of how the internet works), the delta between best/worst times of gzip-compressed requests is smaller (i.e., they're more consistent).

    That's pretty compelling data, but the real benefit comes when the phone's data connection is used. Here's the output from the same app using the cell network (via Excel this time):

    HTTP Request Performance

    All our previous observations remain true - and are even more pronounced in this scenario. Gzip-compressed HTTP requests are significantly faster (taking less than half the time) and more predictable than traditional requests for the same data.

    Awesome!

    Aside: Based on the chart above, it seems reasonable to claim all three gzip solutions are equivalent. That said, if you squint just right, it looks like using HttpWebRequest is - on average - marginally quicker than using WebClient (as you'd expect; WebClient calls HttpWebRequest under the covers). Additionally, SharpGIS.GZipWebClient appears to be - on average - very slightly quicker than Delay.GzipWebClient (which is also not surprising when you consider the hoops my code jumps through to avoid the external dependency). Bear in mind, though, that these differences only really show up at the millisecond level, and seem unlikely to be significant for most real-world scenarios.

     

    [Click here to download the source code for the gzip helper classes and the sample application shown above]

    OR

    [Click here to visit the NuGet gallery page for Delay.GzipWebClient which contains the code for both gzip helper classes]

     

    Notes:

    • Using the code I've authored is quite easy:

      • If you want to use GzipWebClient, simply add the GzipWebClient.cs and GzipExtensions.cs files to your project, reference the Delay namespace, and replace any instances of WebClient.

      • If you're a HttpWebRequest/HttpWebResponse fan, you only need to add GzipExtensions.cs to your project, reference the Delay namespace, and invoke the extension methods HttpWebRequest.BeginGetCompressedResponse and HttpWebResponse.GetCompressedResponseStream (as shown above).

    • By default, the sample application does not include Morten's GZipWebClient because I don't want to get into the business of distributing someone else's code. However it's easy to include - the following comment in MainPage.xaml.cs tells you how:

      // For an additional scenario, un-comment the following line and install the "SharpGIS.GZipWebClient" NuGet package
      //#define SHARPGIS

      The code to use that assembly is already in the sample application; you just need to provide the bits if you want to try it out. :)

    • The implementation of the gzip-to-ZIP wrapper is fairly straightforward: it reads the gzip-compressed response from the server, wraps it in a stream that represents a valid ZIP archive with a single file compressed using the deflate algorithm which both specifications share, and hands that off to the Silverlight framework to return a decompressed stream for that file. To be clear, the compressed data isn't altered - it's simply re-packaged into a format that's more useful. :) If you're interested in the specifics, you'll probably want to familiarize yourself with the gzip specification and the ZIP file specification and then have a look at the code.

    • There are two minor inefficiencies in my code, one of which seems unavoidable and the other of which could be dealt with.

      • The unavoidable one - and the reason I think SharpZipLib might be a smidge quicker - is that the size/checksum data for the gzip data is provided at the end of the download stream, but is needed at the beginning of the wrapper stream. If the correct values aren't used, the ZIP wrapper will be rejected - but those values are not known until the entire response has been downloaded. Therefore, it's not possible for my implementation to proactively process the data while it is being downloaded; the download must be buffered instead. A decompression library won't suffer from this limitation and ought to be quicker as a result.

      • The avoidable inefficiency is that a single copy of the input data is made when creating the ZIP wrapper stream. To be clear, this is the only time data is copied (I've structured the code so there isn't any buffer resizing, etc.), but it's not technically necessary because the ZIP wrapper stream could operate directly from the download buffers. I may make this improvement in the future, but expect the performance difference to be negligible.

     

    Compressing HTTP traffic is a pretty clear win for desktop applications - and an even bigger benefit for mobile apps communicating over slower, less reliable cellular networks. The Windows Phone platform doesn't make using gzip easy, but it's possible if you're sufficiently motivated. For people who aren't intimidated by licenses, there are already some good options for gzip - but for those who aren't comfortable with what's out there, I'm offering a new option under one of the most permissive licenses around.

    I hope you find it useful!

  • Delay's Blog

    "I never did mind about the little things..." [Free HideShowDesktopButtonOnTaskbar utility hides the "Show desktop" button on the Windows taskbar]

    • 3 Comments

    Over the holidays, a relative emailed me the following rant:

    Most of my conversation with technical support was to find out how to turn off the "Show desktop" icon in the lower right corner of the screen. This icon can be engaged by clicking on it or hovering over it (if you select Peek). On a desktop or a laptop this is NO PROBLEM. But on a tablet, there is no difference between clicking and hovering so Peek is meaningless. VERY OFTEN when using and holding the slate/tablet the heel of my thumb ever so gently touches the "Show desktop" icon and I am taken to the desktop in the middle of what I am doing. As I work, I am in constant fear of touching this icon. I know that everything on my hard drive will not be erased if I do, but it is very annoying. I went on the internet and found a program that turns the icon off and it worked (so it can be done), but then my antivirus program said that it was a THREAT AND ADVISED ME TO PUT IT IN A VAULT WHERE IT COULD NOT HARM MY COMPUTER. I agreed and the program was deleted. I'm not the only one with this problem. The internet has many complaining about the "Show desktop" icon. MS allows me to turn the Clock and Volume icon off; why not the "Show Desktop" icon?????

    If you want a somewhat more positive spin, "Aero Peek" and the "Show desktop" button are described in this article about new Windows 7 taskbar features. I don't use either myself, but a bit of internet searching confirmed some people really don't like these features.

    "Show desktop" button

     

    The best way to disable a feature is to use an officially supported mechanism - and the good news is that disabling Aero Peek is easy to do by following the directions "To turn off desktop previews" near the bottom of this article. However, I was not able to find similar support for disabling the "Show desktop" button, so I resorted to looking for the next best thing: a group policy setting or documented registry key. Unfortunately, I struck out there, too. But I did find this snippet from a Channel 9 video where Gov Maharaj confirms there's no built-in way to disable the button.

    Aside: The relevant discussion is interesting, so maybe have a look even if you're not opposed to the feature itself!

     

    Well, if there's no official way to remove the "Show desktop" button and enough people want to do it, then it's time to start considering other solutions. According to the original rant (and the video discussion), there already are third-party utilities for this purpose - although they're not supported by Microsoft. But it sounds like at least one of these tools might be malware, so I'm not super enthusiastic about trying them out...

    Fortunately, I have a rudimentary understanding of both Windows and programming [ :) ], so I wondered if the simplest, most obvious trick would work here. I coded it up one night while waiting for something to compile and was pleased to find that it worked! So I've gone ahead and prettied the code up and am sharing a simple utility to get rid of the Windows 7 taskbar's "Show desktop" button:

    HideShowDesktopButtonOnTaskbar icon

    [Click here to download the HideShowDesktopButtonOnTaskbar utility and its complete source code.]

     

    Of course, HideShowDesktopButtonOnTaskbar is just as unsupported and unofficial as the other utilities out there - so why choose it?

    • I've included the complete source code for HideShowDesktopButtonOnTaskbar, so you can review everything and re-compile it yourself if you're paranoid. Also, you can be pretty confident I'm not a 1337 *** trying to root your box. :)

    • HideShowDesktopButtonOnTaskbar makes no persistent changes to the machine, so there are no lingering effects and a simple logoff is all it takes to restore everything to the way it was.

    • HideShowDesktopButtonOnTaskbar is simple, small, unobtrusive, and easy to use - just add it to your Startup group to have it run every time you log into Windows!

     

    Okay, enough with the goofy sales pitch... how about some developer notes?

    • As I mentioned, HideShowDesktopButtonOnTaskbar works the simplest way you can imagine: it finds the window corresponding to the "Show desktop" button and hides it. Obviously, hidden windows aren't visible - but they also don't receive input (clicks or hover status), so although the taskbar is still listening for input messages, they don't get sent. Running HideShowDesktopButtonOnTaskbar a second time finds and unhides the "Show desktop" button, restoring things back to how they started. Logging off disposes of the entire taskbar and logging back on creates a new one from scratch, so HideShowDesktopButtonOnTaskbar's changes don't persist.

    • Spy++ of "Show desktop" button

      I figured out the right window class to target by using the handy-dandy Spy++ Windows development tool. Specifically, I ran Spy++, clicked the "Find Window" tool, dragged the crosshairs over the "Show desktop" button, and hit OK. That showed the window hierarchy to the right beginning with the desktop window at the top and going down to the "Show desktop" button. Translating that into three nested calls to FindWindowEx was trivial, as was adding a call to ShowWindow to hide the window. Using IsWindowVisible to unhide the button when it was already hidden was just icing on the cake. :)

      Aside: If the window hierarchy changes or if any of the hard-coded class names is different in a newer version of Windows, this will stop working... Yep, that's how it is with simple hacks like this - if it matters to anyone, I can always tweak things to accommodate.
    • Because HideShowDesktopButtonOnTaskbar ended up being easy to write, I was looking for a bit more challenge and set out to make it small. Specifically, the executable is just 11,776 bytes - and 7,015 bytes of that is due to the icon! While I could have squeezed a few more bytes out if I felt like it, the big win was by not linking to the standard C Run-Time library (the use of which results in a 41,472 byte file assuming static linking (i.e., /MT) is used to remove the dependency on MSVCR100.dll). Eschewing the CRT is an advanced scenario, but it was easy to do for HideShowDesktopButtonOnTaskbar because it's so small and simple.

      Aside: This is why the code's entry point is named WinMainCRTStartup instead of the usual WinMain. And by the way: if you're interested in reading more about what it means to get rid of the default CRT, Matt Pietrek's classic "Reduce EXE and DLL Size with LIBCTINY.LIB" is a good place to start.
    • Of course, everything has a price, and I needed to make two other changes as a result of omitting the default CRT. Both are found in Visual Studio's project Properties, Configuration Properties, C/C++, Code Generation settings: changing Basic Runtime Checks (known as <BasicRuntimeChecks> in the .vcxproj file) to Default (i.e., no /RTC? option) and changing Buffer Security Check (<BufferSecurityCheck>) to false (i.e., /GS-). Disabling these checks isn't something you should do in general (especially the latter), but HideShowDesktopButtonOnTaskbar takes no input, has no buffers, and is so simple that I'm (tentatively!) okay sacrificing these two security/resiliency measures.

    • The call to HeapSetInformation / HeapEnableTerminationOnCorruption is almost definitely overkill - but it's good practice and so easy to add that I did so anyway (FYI that the default CRT call this automatically). Plus, doing this made me feel a little better about losing /GS. :)

     

    If you're bothered by Windows 7's "Show desktop" button and are looking for a solution, maybe HideShowDesktopButtonOnTaskbar is the answer. If you're curious how a hack like this works or are looking to dabble with replacing the CRT in your own programs, there might be something of interest here. Either way, HideShowDesktopButtonOnTaskbar was a fun side project - I hope you like it! :)

     

    The code:

    // Include system headers (at warning level 3 because they're not /Wall-friendly) #pragma warning(push, 3)
    #include <windows.h> #pragma warning(pop)
    
    // Disable harmless /Wall warning C4514 "unreferenced inline function has been removed" #pragma warning(disable: 4514)
    
    // Default entry point function int __stdcall WinMainCRTStartup()
    {
        // Enable "terminate-on-corruption"
        (void)HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
    
        // Find the "Show desktop" button/window (starting from the Desktop window)    const HWND hwndTaskbar = FindWindowEx(NULL, NULL, TEXT("Shell_TrayWnd"), NULL);
        if (NULL != hwndTaskbar)
        {
            const HWND hwndNotify = FindWindowEx(hwndTaskbar, NULL, TEXT("TrayNotifyWnd"), NULL);
            if (NULL != hwndNotify)
            {
                const HWND hwndShowDesktopButton = FindWindowEx(hwndNotify, NULL, TEXT("TrayShowDesktopButtonWClass"), NULL);
                if (NULL != hwndShowDesktopButton)
                {
                    // Toggle the visibility of the button                const int nCmdShow = IsWindowVisible(hwndShowDesktopButton) ? SW_HIDE : SW_SHOW;
                    (void)ShowWindow(hwndShowDesktopButton, nCmdShow);
                }
            }
        }
    
        // Return 0 because a message loop wasn't entered    return 0;
    }
  • Delay's Blog

    “Make things as simple as possible, but not simpler.” [ManagedMsiExec sample app shows how to use the Windows Installer API from managed code]

    • 9 Comments

    Windows Installer is the installation and configuration service used by Windows to add and remove applications. (For more information, Wikipedia has a great overview of Windows Installer, covering products, components, setup phases, permissions, etc..) In addition to exposing a rich, native API, Windows Installer comes with msiexec.exe, a command-line tool that offers fine-grained control over the install/uninstall process.

    I wanted to familiarize myself with the use of the Windows Installer API from .NET, so I wrote a wrapper class to expose it to managed applications and then a simple program to exercise it. Unlike msiexec.exe which can do all kinds of things, my ManagedMsiExec supports only UI-less install and uninstall of a .MSI Windows Installer package (i.e., /quiet mode). By default, ManagedMsiExec provides simple status reporting on the command line and renders a text-based progress bar that shows how each phase is going. In its "verbose" mode, ManagedMsiExec outputs the complete set of status/progress/diagnostic messages generated by Windows Installer (i.e., /l*) so any problems can be investigated.

    Aside: Although I haven't tested ManagedMsiExec exhaustively, it's fundamentally just a thin wrapper around Windows Installer, so I'd expect it to work for pretty much any MSI out there.

     

    Here's how it looks when run:

    C:\T>ManagedMsiExec
    SYNTAX: ManagedMsiExec <--Install|-i|--Uninstall|-u> Package.msi [--Verbose|-v]
    Windows Installer result: 87 (INVALID_PARAMETER)

    Doing a simple install:

    C:\T>ManagedMsiExec -i Package.msi
    ManagedMsiExec: Installing C:\T\Package.msi
    Windows Installer result: 0 (SUCCESS)

    Doing a simple uninstall:

    C:\T>ManagedMsiExec -u Package.msi
    ManagedMsiExec: Uninstalling C:\T\Package.msi
    Windows Installer result: 0 (SUCCESS)

    Using verbose mode to diagnose a failure:

    C:\T>ManagedMsiExec -u Package.msi -v
    ManagedMsiExec: Uninstalling C:\T\Package.msi
    ACTIONSTART: Action 18:31:21: INSTALL.
    INFO: Action start 18:31:21: INSTALL.
    COMMONDATA: 1: 0 2: 1033 3: 1252
    PROGRESS:
    PROGRESS: 1: 2 2: 189440
    COMMONDATA: 1: 0 2: 1033 3: 1252
    INFO: This action is only valid for products that are currently installed.
    C:\T\Package.msi
    COMMONDATA: 1: 2 2: 0
    COMMONDATA: 1: 2 2: 1
    INFO: DEBUG: Error 2755:  Server returned unexpected error 1605 attempting to
      install package C:\T\Package.msi.
    ERROR: The installer has encountered an unexpected error installing this package.
      This may indicate a problem with this package. The error code is 2755.
    INFO: Action ended 18:31:21: INSTALL. Return value 3.
    TERMINATE:
    Windows Installer result: 1603 (INSTALL_FAILURE)

     

    Of note:

    • Msi.cs, the class containing a set of .NET platform invoke definitions for interoperating with the native MSI.dll that exposes Windows Installer APIs. The collection of functions and constants in this file is not comprehensive, but it covers enough functionality to get simple scenarios working. Most of the definitions are straightforward, and all of them have XML documentation comments (via MSDN) explaining their purpose. For convenience, many of the relevant Windows error codes from winerror.h are exposed by the ERROR enumeration.

    • ManagedMsiExec.cs, the sample application itself which works by calling the relevant Msi APIs in the right order. Conveniently, a complete install can be done in as few as three calls: MsiOpenPackage, MsiDoAction, and MsiCloseHandle (with MsiSetProperty an optional fourth for uninstall or customization). To provide a better command-line experience, the default UI for status reporting is customized via MsiSetInternalUI, MsiSetExternalUI, and MsiSetExternalUIRecord. Implementing the handler for a MsiSetExternalUI callback is easy because it is passed pre-formatted strings; parsing record structures in the handler for the MsiSetExternalUIRecord callback requires a few more API calls (and a closer reading of the documentation!).

    Note: When providing delegates to the MsiSetExternalUI and MsiSetExternalUIRecord methods, it's important to ensure they won't be garbage collected while still in use (or else the application will crash!). Because the managed instances are handed off to native code and have a lifetime that extends beyond the function call itself, it's necessary to maintain a reference for the life of the application (or until the callback is unregistered). A common technique for maintaining such a reference is the GC.KeepAlive method (though ManagedMsiExec simply stores its delegate references in a static member for the same effect). Conveniently, the callbackOnCollectedDelegate managed debugging assistant can help identify lifetime problems during debugging

     

    [Click here to download a pre-compiled executable along with the complete source code for the ManagedMsiExec sample.]

     

    The introduction of Windows Installer helped unify the way applications install on Windows and encapsulates a great deal of complexity that installers previously needed to deal with. The openness and comprehensiveness of the Windows Installer native API makes it easy for applications to orchestrate installs directly, and the power of .NET's interoperability support makes it simple for managed applications to do the same.

    The Msi class I'm sharing here should allow developers to get started with the Windows Installer API a bit more simply - as the ManagedMsiExec sample demonstrates. The wrapper class and the sample are both pretty straightforward - I hope they're useful, informative, or at least somewhat interesting! :)

  • Delay's Blog

    "You are now up to date." [IfModifiedSinceWebClient makes it easy to keep a local file in sync with online content - and do so asynchronously]

    • 1 Comments

    Recently, I was working with a program that needs to cache the content of a network file on the local disk for faster, easier access. The network file is expected to change periodically according to an unpredictable schedule; the objective is to keep the local file in "close" sync with the online copy without a lot of cost or complex protocols.

    The network content is static (when it's not changing!), so hosting it on a web server and accessing it via HTTP seems like a reasonable start. Implementation-wise, it turns out the file is needed immediately when accessed, so downloading it on-demand is not an option due to the possibility of lengthy network delays or connectivity issues. Fortunately, it's okay to use an out-of-date version as long as there's a good chance the next access will use the latest one. So a reasonable approach seems to be to wait for the local file to be needed, use it immediately in its current state, then update it to the latest version by asynchronously downloading the network file and replacing the local copy in the background.

    IfModifiedSinceWebClientDemo sample

    That approach satisfies the requirements of the scenario, but it's a little wasteful because the local file is likely to be used much more frequently than the online version gets updated - which means most of the downloads will end up being the same version that's already cached locally! Fortunately, we can improve things easily: the HTTP specification defines the If-Modified-Since header for exactly this purpose! By including that header with the HTTP request, the server "knows" whether the local file is out of date. If so, it returns the data for the network file as usual - but if the network file has not changed more recently, the web server returns a 304 Not Modified result and no content. This "short circuiting" of the HTTP response eliminates the need to send redundant data and reduces the network traffic to a single, short HTTP request/response pair.

     

    When implementing a solution like this with the .NET Framework, two approaches spring to mind. The first is to use the low-level HttpWebRequest/HttpWebResponse classes and manage the entire operation directly. HttpWebRequest has an IfModifiedSince property that can be used to set the relevant HTTP header (and format it correctly), so this approach is straightforward and quite flexibile. However, it also requires the caller to manage the transfer of bits from the source to the destination (including reading, writing, buffering, etc.), and that's not really code we want to write. The second approach is to use something like the higher-level WebClient class's DownloadFileAsync method to do the entire download and call us back when everything has been taken care of. That seems preferable, so lets go ahead and set the WebClient.IfModifiedSince property and... umm... wait... WebClient doesn't have an IfModifiedSince property! And not only doesn't the property exist, you're not allowed to set it manually via the Headers property: "In addition, some other headers are also restricted when using a WebClient object. These restricted headers include, but are not limited to the following: ... If-Modified-Since ...".

    Darn, I really wanted to use WebClient and avoid having to encode the If-Modified-Since header myself. If only it were possible to tweak the way WebClient initializes its underlying HttpWebRequest, we'd be set... Hey, what about the WebClient.GetWebRequest method? Isn't this exactly what it's for? Yes, it is! :)

     

    To make this all work, I created IfModifiedSinceWebClient which is a WebClient subclass that adds an IfModifiedSince property and overrides GetWebRequest to set that DateTime value onto the underlying HttpWebRequest. Unfortunately, there are two issues: the destination file gets deleted before the download starts (so it ends up being 0 bytes when HTTP 304 is returned) and HTTP 304 is defined as a failure code, so WebClient thinks a successful (NOOP) download has failed. To address both issues and offer a seamless experience, IfModifiedSinceWebClient exposes a custom UpdateFileIfNewer method that's asynchronous (i.e., "fire and forget") and simple. Just pass it the path to a local file to create/update and a URI for the remote file. (You can optionally pass a "completed" method to be called with the result of the asynchronous update.) UpdateFileIfNewer sets the If-Modified-Since header and initiates a call to DownloadFileAsync, providing a temporary file path. If the remote file is not newer than the local one, no transfer occurs and the UpToDate result is passed to the completion method. If the remote file is newer (or the server doesn't support If-Modified-Since), the local file will be replaced with the just-downloaded copy and the Updated result will be returned. And if something goes wrong, the local file is left alone and the Error result is used.

     

    Here's what a typical call looks like:

    private void MyMethod()
    {
        // ...
    
        var localFile = "LocalFile.txt";
        var uri = new Uri("http://example.com/NetworkFile.txt");
        IfModifiedSinceWebClient.UpdateFileIfNewer(localFile, uri, UpdateFileIfNewerCompleted);
        // Note: Download occurs asynchronously
    
        // ...
    }
    
    private void UpdateFileIfNewerCompleted(IfModifiedSinceWebClient.UpdateFileIfNewerResult result)
    {
        switch (result)
        {
            case IfModifiedSinceWebClient.UpdateFileIfNewerResult.Updated:
                // ...
                break;
            case IfModifiedSinceWebClient.UpdateFileIfNewerResult.UpToDate:
                // ...
                break;
            case IfModifiedSinceWebClient.UpdateFileIfNewerResult.Error:
                // ...
                break;
        }
    }

     

    [Click here to download the source code for IfModifiedSinceWebClient and the sample application shown at the start of the post.]

    (Don't forget to update the sample's localhost test URI to a valid URI for your environment.)

     

    IfModifiedSinceWebClient is a simple subclass that adds If-Modified-Since functionality to the .NET Framework's WebClient. But that's only half the battle - the UpdateFileIfNewer method makes the "asynchronously update a file if necessary" scenario work by building on that with a temporary file, error detection, and result codes. The result is a seamless, unobtrusive way for an application to keep itself up to date with dynamically changing online content without incurring unnecessary network overhead. Although there are other, more sophisticated solutions to this problem, it's hard to beat the simplicity and compactness of IfModifiedSinceWebClient!

    Aside: For bonus points, IfModifiedSinceWebClient could also set the local file's "last modified" time to the Last-Modified value returned by the server. I haven't done so in the sample because it doesn't seem like the subtle time skew (between the client and server clocks) will matter in most cases. However, I reserve the right to change my mind if practical experience contradicts me. :)
  • Delay's Blog

    Don't make the audience sit through all your typo-ing [Overview of implementing an extension for WebMatrix - and the complete source code for the Snippets sample]

    When I wrote about WebMatrix's new extensibility features a couple of posts back, I said I'd share the complete source code to Snippets, one of the sample extensions in the gallery. In this post, I'll be doing that along with explaining the overall extension model in a bit more detail. To begin with, here's the code so those of you who want to follow along at home can do so:

    [Click here to download the complete source code for the Snippets extension]

     

    Background

    I created the Snippets extension by starting from the "WebMatrix Extension" Visual Studio Project template I wrote about previously. When expanded, the project template automatically set up the right project references (i.e., Microsoft.WebMatrix.Extensibility.dll) and build steps and created a functioning extension with a single Ribbon button. From there, creating Snippets was a simple matter of adding the functionality I wanted on top of that foundation. But more on that later - I want to go over the default code first.

     

    The default project template extension

    WebMatrix Extension project template

    One key thing to notice is that the template-generated WebMatrixExtension class derives from ExtensionBase, a base class which implements WebMatrix's IExtension interface and simplifies some of the work of dealing with it. In the interest of generality (and because it's just an interface), IExtension doesn't do much beyond identifying a few required properties. ExtensionBase builds on that to offer concrete collections for the IEnumerable(T) properties, automatically MEF Imports the IWebMatrixHost interface, creates an OnWebMatrixHostChanged override, and so on. Of course, none of this is rocket science and the decision to use ExtensionBase is completely up to you. But the whole reason it exists to make your life easier, so I'd suggest at least giving it a chance. :)

    In order for an extension to be loaded by WebMatrix, it needs to be located in the right directory (more on that in the previous post) and it needs to MEF Export the IExtension interface. You might think that ExtensionBase should do the latter for you, but it doesn't because that might restrict your own class hierarchy (i.e., intermediary classes would advertise themselves as extensions even though they're not). Therefore, subclasses like the template-generated WebMatrixExtension class need to explicitly export IExtension.

    With the groundwork out of the way, the basic functionality of the example extension is to add a Ribbon button and handle activation of that button to open the current web site in the browser. Providing content for the Ribbon is as easy as adding instances implementing the relevant interfaces (IRibbonButton, IRibbonMenuButton, IRibbonGroup, etc.) to the generated class's RibbonItemsCollection. It's important to do this exactly once (typically in the extension's constructor) because subsequent changes are not honored (FYI, that may change in the future, but please don't count on it). Of course, you can show and hide Ribbon content whenever you wish; you just can't add or remove items after initialization. Again, there are simple, concrete subclasses for each of the relevant interfaces (IRibbonButton->RibbonButton, etc.) so you don't need to spend time implementing these simple things yourself. And just like before, using the "helper implementations" is completely optional.

    Creating a RibbonButton requires a label, an ICommand implementation, and (optionally) a small/large image. The template's sample includes a couple of images already configured properly to provide a working example. Wiring up the images correctly is standard WPF, but the pack URI syntax can be a little tricky and it's common to forget to change the build type of the image files to "Resource" - so that's already been done for you as a helpful reminder. :) For its ICommand implementation, the template sample uses a simple DelegateCommand class (also included). The sample DelegateCommand is very much in line with other implementations of DelegateCommand or RelayCommand. (Feel free to use whatever version you'd like; the sample DelegateCommand exists simply to avoid introducing a dependency on a third-party library.) As you'd expect, the ICommand's CanExecute and CanExecuteChanged methods are used to dynamically enable/disable the button and its Execute method is called when the button is clicked.

    Yeah, it takes a while to describe what's going on, but there's hardly any code at all! :)

     

    The Snippets extension

    Snippets extension in use

    With the foundation behind us, it's time to consider the Snippets extension itself - and for that, it's helpful to know what it does. Snippets was created with the typical demo scenario in mind: a presenter is showing off WebMatrix and wants to add a block of code to a document, but doesn't want to type it out in front of the audience because that can be slow and boring. Instead, he or she clicks on the Snippets button in the Ribbon, selects from a list of available snippets, and the relevant text is automatically inserted in the editor. Of course, individual snippets should be easy for the user to add or modify and they should allow small and large amounts of text as well as blank lines, etc..

    There are probably a hundred ways you could build this extension; here's how I've done it:

    • Each snippet is stored as a text file (ex: "Empty DIV.txt") in the Snippets folder of the user's Documents folder (i.e., %USERPROFILE%\Documents\Snippets). The file's name identifies the snippet and its contents get added when the snippet is used. You can have as many or few snippet files as you'd like; they're read when the extension is loaded and cached. If there aren't any snippet files (or the Snippets folder doesn't exist), the extension provides a simple message with instructions for how to set things up.

      Aside: WebMatrix needs to be restarted in order for snippet changes to take effect. An obvious improvement would be for the Snippets extension to monitor the Snippets directory for changes and apply them on the fly.
    • The Snippets user interface is a RibbonMenuButton which contains a collection of RibbonButton instances corresponding to each of the available snippets. When the RibbonMenuButton is clicked, it automatically shows the IRibbonButton instances from its Itemscollection in a small, drop-down menu. When a selection is made, the menu is automatically closed.

      Aside: A RibbonSplitButton could have been used if there was a scenario where the user could click the top half of the button to perform a similar action (like inserting a default snippet).
    • The Snippets button only makes sense when the "Files" workspace is active (i.e., a document is being edited), so the extension listens to the IWebMatrixHost.WorkspaceChanged event and shows/hides its Ribbon button according to whether the new workspace is an instance of the IEditorWorkspace interface.

      Aside: One of the bits of feedback we've gotten so far is that this scenario (i.e., "only available for a single workspace") is common and should be simplified. Yep, message received. :)
    • To insert text into the document, Snippets invokes the Paste command via the IWebMatrixHost.HostCommands property. While it's possible to get and set editor text directly for more advanced scenarios, the Paste command works nicely because the editor automatically updates the caret position, replaces selected text, etc.. The downside to using the (application-wide) Paste command is that if the input focus isn't in the body of an open file, then the paste action will be directed elsewhere and won't work as it's meant to.

      Aside: The editor interfaces are almost rich enough to manage everything here and avoid using Paste. However, there were a couple of glitches when I tried which led me to use the simpler paste approach for the sample.
    • Input focus aside, one thing that's clear is that Snippets can never be added without an open document visible. To determine if that's the case, Snippets uses an unnamed host command to populate an instance of IEditorContainer. If that fails or the IEditorExt within is null, that means no document is open and Snippets knows to disable its Ribbon buttons.

      Unnamed host commands work just like named commands (i.e., Paste), though they're a little harder to get to. The HostCommands.GetCommand method exists for this purpose and allows the caller to pass a GUID/ID for the command to return. The relevant code looks like this:

      /// <summary>
      /// Gets an IEditorExt instance if the editor is in use.
      /// </summary>
      /// <returns>IEditorExt reference.</returns>
      private IEditorExt GetEditorExt()
      {
          var editorContainer = new EditorContainer();
          _editorContainerCommand = WebMatrixHost.HostCommands.GetCommand(new Guid("27a0f541-c86c-4f0b-b436-0b50bf9f7ef8"), 10);
          if (_editorContainerCommand != null)
          {
              _editorContainerCommand.Execute(editorContainer);
          }
      
          return editorContainer.Editor;
      }

      Helper properties for this and other unnamed commands be added in the future. For now, FYI about a useful one. :)

    • Although the RibbonButton class supports a parameter parameter for passing to the ICommand's CanExecute/Execute methods to provide additional context, this value is not actually passed through in some cases. :( This bug was found too late to fix for the Beta, but the good news is that it's easy to work around by creating a closure to capture the relevant parameter information instead. If you're not familiar with this technique, it involves defining an anonymous method that references the desired data; the compiler automatically captures the necessary values and passes them along when the delegate gets called.

      Here's what it looks like in the sample (using LINQ to create the collection):

      // Create buttons for each snippet
      var snippetsButtons = _snippets
          .Select(s =>
              new RibbonButton(
                  s.Key,
                  new DelegateCommand(
                      (parameter) => GetEditorExt() != null,
                      (parameter) =>
                      {
                          // parameter is (incorrectly) null, so add this indirection for now
                          HandleSnippetInvoke(s.Value);
                      }),
                  s.Value,
                  _snippetsImageSmall,
                  _snippetsImageLarge));
    • It turns out there's a subtle bug because of the following code in the sample:

      // Paste the snippet into the editor
      var paste = WebMatrixHost.HostCommands.Paste;
      if (paste.CanExecute(insertText))
      {
          paste.Execute(insertText);
      }

      Note that Snippets is invoking a special flavor of the Paste command by providing the text as the parameter property for the CanExecute/Execute methods (meaning "paste this text, please"). However, the underlying editor code is returning a CanExecute result based on whether or not it could paste from the clipboard (i.e., it's not honoring the meaning of the text parameter). Therefore, if the clipboard is empty or contains non-text data like a file, CanExecute returns false and Snippets isn't able to insert text.

      The easy workaround is to copy some text to the clipboard so the underlying implementation will return true for CanExecute and the specialized paste operation will be invoked.

     

    Summary

    Snippets is a small extension that builds on the default WebMatrix Extension project template to implement some useful (if limited) functionality. Its original purpose was to simplify demos, but if people find practical uses for it, that's great, too! :)

    But whether or not people use it, the Snippets extension touches on enough interesting areas of extensibility that people can probably learn from it. If you're getting started with WebMatrix extensions and are looking for a "real world" sample, I hope Snippets can be helpful. If you have feedback or questions - about Snippets or more generally - please let me know!

Page 1 of 28 (277 items) 12345»