The blog of dlaa.me

Video frame grabbing made easy [How to: Quickly capture multiple video frames with WPF]

When I transfer clips from my digital camera to my computer, I briefly check that the video transferred successfully. For images, this is usually a matter of opening a few of them - for video files I usually load them in Media Player and seek to a couple of different points. Mine is hardly a fail proof system, but it's fairly quick and it makes me feel a little safer about deleting the originals from the camera. :) One day I got to thinking that it would be pretty easy to automatically generate a set of video thumbnails instead of seeking around manually...

So I did some research into video frame capture with WPF and found that it boiled down to a few main ideas:

  1. All it takes to capture a frame of video is an instance of MediaPlayer and a call to RenderTargetBitmap
  2. Setting the ScrubbingEnabled property tells MediaPlayer to update its content dynamically when changes to the Position property are made
  3. The video updates are asynchronous, so it's necessary to delay the capture a short while after changing the Position property in order to capture the new frame
  4. Performing a capture in response to the MediaOpened event is a handy way to capture the first frame reliably
  5. However, nothing I found demonstrated a good way to capture additional frames reliably...

Because of the asynchronous nature of the video frame updates, attempting to capture multiple frames in rapid succession (ex: seek, capture, seek, capture, ...) resulted in duplicate frames every time the capture occurred too soon for the frame's content to update (which was most of the time!). This was clearly unsuitable for my scenario because I wanted my application to generate its thumbnails as quickly as possible. After a bit of experimentation and playing around, I arrived at a technique that seems to work quite reliably in practice.

I wrote a simple WPF application to show how it works - here's an image of VideoThumbnailer displaying a summary of a video of someone tubing in the snow:

VideoThumbnailer sample frame capture application

(The complete source code to VideoThumbnailer is available as an attachment to this post.)

The technique itself is fairly simple, if not entirely as straightforward as it would be if changes to the Position property were synchronous:

  1. Create a MediaPlayer instance and hook the MediaOpened and Changed events
  2. Set ScrubbingEnabled and open the media file
  3. When the MediaOpened handler fires, capture the initial frame and set Position to the first location of interest
  4. When the Changed handler fires, capture the current frame
  5. If that frame is new/different than before, seek to the next position of interest and repeat as necessary; otherwise wait for the Changed handler to fire again

There's nothing particularly clever going on here, but there are a few complicating circumstances worth mentioning:

  • The Changed event fires whenever the MediaPlayer instance gets a new frame from its underlying implementation. However, a single Position change results in multiple invocations of the Changed event (2-5 seems to be the usual range). Typically, the first Changed event doesn't actually correspond to new data, but rather the same frame as before - so simply listening for the first Changed event is not sufficient. What's most desirable is to listen for the last Changed event - but without knowing how many there will be in advance, it's difficult to know which one is the last frame...
  • Fortunately, in practice the first Changed event corresponding to different content (as determined by a pixel-by-pixel comparison of the captured frames) seems to also correspond to the final, desired content. So a very reasonable solution is to wait for the first changed content, call it "good enough", and move on by seeking to the next position in the file.
  • So now the application is quickly seeking to arbitrary positions and capturing the desired frame with a high degree of reliability. We're done, right? Well, not quite... What if a video file contains the same exact content at two different positions? (Note: This is uncommon in motion video, but VERY common at the beginning and ending of clips (ex: 100% black frame) or in generated content (ex: title frame).) The approach described so far will spend forever waiting for a new frame that will never arrive...
  • What's needed here is a separate watchdog timer that fires after a reasonable time has elapsed and aborts the infinite wait. The catch is that this timer should be long enough to give the new video content sufficient time to be made available, but also short enough that it's not onerous if it fires many times during the generation of thumbnails. I experimentally settled on 1 second for the watchdog timeout because it seems to strike a good balance between these competing goals. With the watchdog timer in place, the process of generating a video summary should never hang - and we're done!

Having implemented the technique above, we should have a fast, reliable solution for video file thumbnail generation that works with a wide variety of file types by virtue of the underlying operating system's support. And, indeed, VideoThumbnailer works fine with all the WMV, DVR-MS, MPEG, and AVI files I've tried (and probably supports some other types I didn't try). There are plenty of changes one could make to VideoThumbnailer to make it a more useful application, but I hope it serves pretty well as a concrete example of the concepts discussed here.

Happy frame grabbing!

[VideoThumbnailer.zip]

Tags: WPF

Successfully passing the baton [AJAX Control Toolkit release!]

Bertrand Le Roy posted earlier today about the new 20820 release of the AJAX Control Toolkit! In addition to being compiled for the recently available .NET 3.5 SP1 platform and containing a new user-contributed control (MultiHandleSlider by Daniel Crenna), this release is also notable from my perspective because it represents the first release for which I was not directly involved. As much as I've enjoyed working on the Toolkit, there are only so many hours in the day; organizational changes and my present responsibilities developing for Silverlight are taking up all of my time. So it has been fantastic to have Bertrand continue moving the Toolkit project forward and taking care of all the many tasks that go into publishing a new release.

Congratulations to Bertrand - and all the individual contributors! Thank you for the hard work that made this release possible!

Read a Silverlight book - with Silverlight! [SimpleSilverlightXpsViewer adaptation by Laurence Moroney]

At the bottom of the post on migrating SimpleSilverlightXpsViewer to Silverlight Beta 2, I hinted at "a cool, practical, relevant use of SimpleSilverlightXpsViewer that I'll be able to share sometime soon". "Soon" is now, and I'm happy to point readers at the "Read about Silverlight in XPS" post by Laurence Moroney in which he uses an adaptation of my viewer for showcasing the first three chapters of his new book, Introducing Microsoft Silverlight 2.

Here's what it looks like - just click the image to try it out for yourself:

Screen shot of the XPS Viewer adaptation

In addition to modifying the sample for his purposes, Laurence went further and integrated mouse wheel support: zooming in and out is as easy as spinning the wheel. I've got to agree that it's a very nice usability improvement!

Since releasing the XPS Viewer Sample, a few people have expressed interest in repurposing the code for something similar. Kudos to Laurence for doing a great job with this - and I love that it's being used to help spread the word about Silverlight! :)

Maintaining access to Silverlight 2's generic.xaml resources [SilverlightDefaultStyleBrowser updated for build 2.0.30523.8 and beyond!]

A few days ago the Silverlight team released a minor update to Beta 2, changing the version number from 2.0.30523.6 to 2.0.30523.8 in the process. The version number is part of Silverlight's install path and SilverlightDefaultStyleBrowser (background reading available here, here, and here) didn't know to look in the new location when automatically importing Styles. As such, when run on machines with the updated Silverlight bits, the list was missing Styles for the core controls like Button, ListBox, etc.. (As I discovered for myself a short while ago when I tried to use SilverlightDefaultStyleBrowser!)

I've just updated SilverlightDefaultStyleBrowser to look in all subdirectories of %ProgramFiles%\Microsoft Silverlight when starting up. Not only does this fix the current problem, but it should save me from having to do anything next time there's an update, as well! :)

The version number of SilverlightDefaultStyleBrowser always appears in the window's title and the latest release number is 1.0.3123.31689. (Note: I haven't updated the screen shot below which shows the introductory version number.) If installed via ClickOnce, the application should automatically prompt you to upgrade once it detects the update (which typically happens after running the app once or twice). If you're using the standalone EXE, you'll need to update manually.

SilverlightDefaultStyleBrowser Application

Click here or on the image above to install SilverlightDefaultStyleBrowser via ClickOnce with automatic updating.

Click here to download the standalone SilverlightDefaultStyleBrowser executable and source code in a ZIP file.

Notes:

  • On most machines it seems that the upgrade to Silverlight version 2.0.30523.8 automatically uninstalled the bits for 2.0.30523.6. However, on some machines (like one of mine!), the uninstall of 2.0.30523.6 was skipped or incomplete. So it's possible that SilverlightDefaultStyleBrowser's new behavior of importing from all directories under %ProgramFiles%\Microsoft Silverlight will import Styles from more than one version of Silverlight. To help avoid confusion - and to support multiple side-by-side installations of Silverlight in the future - I've modified the ToolTip for the ListBox items to display the path of the assembly from which that Style came. So if you see multiple Button Styles in the list, just hover over each of them to see where they came from - then pick the one that matters to you!
  • I've tweaked the ListBox item ToolTip to pop up when the mouse is over any part of the item (instead of just the Style's name). This was a simple matter of adding HorizontalContentAlignment=Stretch to the ListBoxItems via the ListBox's ItemContainerStyle property.

I'm sorry for any trouble the recent Silverlight upgrade may have caused! The good news is that this particular problem shouldn't happen again. :) And if it does, you can always use the "Add Assembly" button to manually import Styles from any file(s) you want until I get around to fixing things.

Smaller is better! [A simple step to shrink the download size of Silverlight 2 applications]

When putting content on the web, it's important to get that content to the user as quickly as possible. In general, reducing the amount of data users need to download is a big step in the right direction. As such, the XAP packaging format used by Silverlight 2 wraps everything in a single, compressed archive file that's browser-friendly. At the moment, the compression for XAP files in Beta 2 is not as efficient as it could be, and XAP files tend to be a bit larger than necessary. Until this is addressed in a future release of the Silverlight tools, here is something that's almost as good. :)

XAP files are really just standard ZIP files with a different extension, so we can use ZIP-file tools to improve the XAP situation quite easily. I wrote a simple batch file named XapReZip.cmd (contents below) that can be added to a Visual Studio 2008 Silverlight project to automatically re-compress the project's XAP file more efficiently. All you need to do is add a single command to your project's post-build events (detailed below) and from then on all your XAP files will be even smaller and quicker to download!

To demonstrate its effectiveness, I've run XapReZip against three test applications, two simple demonstration applications of mine, and the current version of two professional-grade applications you're probably already familiar with. The table below shows the starting XAP file size, the size after running XapReZip, the difference between the two, and the percent difference. (All sizes are in bytes.)

Scenario Starting Size After XapReZip Difference Reduction
New Silverlight Project 3,980 3,202 778 20%
... with DatePicker 73,228 56,513 16,715 23%
... and DataGrid 194,452 151,646 42,806 22%
HtmlTextBlock 12,360 10,312 2,048 17%
SimpleSilverlightXpsViewer 13,879 11,408 2,471 18%
NBC Olympics Video Player 789,523 615,239 174,284 22%
Line Rider 789,496 538,784 250,712 32%

Basically, you can expect to see about a 22% reduction in size for XAP files in general - slightly less for code-intensive XAPs and potentially much more for larger, richer XAPs. For example, it looks like Line Rider could shave a quarter of a megabyte off its download size without breaking a sweat - which is even more compelling given how easy it would be! :)

The complete code for XapReZip.cmd is below. As written, XapReZip assumes that Zip.exe and UnZip.exe will be located in the same directory as XapReZip.cmd (this is easy to change) and that all of the files that are part of the XAP are available in the same directory as the XAP file itself (which just happens to be true after compiling with Visual Studio or MSBuild). For the purposes of this demonstration, I've used Zip 2.32 and UnZip 5.52 from the Info-ZIP group because they're free and have a nice license. Of course, you can use whatever tools you're most comfortable with.

@REM XapReZip - Recompresses XAP files smaller

@REM Invoke as a VS post-build event like so:
@REM C:\XapReZip\XapReZip.cmd $(TargetName).xap

@echo off
setlocal

REM Define paths to zip.exe and unzip.exe
set ZIPEXE="%~p0zip.exe"
set UNZIPEXE="%~p0unzip.exe"

REM Define paths for intermediate files
set XAP=%1
set XAPDIR=%~p1
set XAPBAK=%1.bak
set XAPZIP=%1.zip

REM Output a banner message
echo XapReZip: %XAP%

REM Change to XAP file directory
pushd %XAPDIR%

REM Create new XAP file with maximum compression
%UNZIPEXE% -Z -1 %XAP% | %ZIPEXE% -@ -9 -X %XAPZIP%

REM Abort if something went wrong
if ERRORLEVEL 1 goto :DONE

REM Replace original XAP file with smaller one
copy /y %XAP% %XAPBAK% > NUL
copy /y %XAPZIP% %XAP% > NUL
del %XAPZIP%

REM Output a success message
echo XapReZip: Success

:DONE

REM Restore previous directory
popd

endlocal

Adding XapReZip to a Silverlight Project in Visual Studio is easy. Just go to the Project menu, choose Properties (it's the last item), switch to the Build Events tab, and set a post-build event command line like this: C:\XapReZip\XapReZip.cmd $(TargetName).xap. Here's what it looks like in Visual Studio:

Configuring XapReZip as a post-build event in Visual Studio

After you've set that one command, you can forget about it - XapReZip will go to work every time you recompile the application and you'll be able to run and debug it just like always. Except a little smaller. :)

So if you're eager to keep your Silverlight applications as lean as possible, consider adding XapReZip to your project. It'll probably be the easiest 22% savings you get all day!

[XapReZip.cmd]

The layout system lies have become a bit more elaborate [LayoutTransform functionality updated and enhanced for Silverlight 2 Beta 2!]

In the introductory post for LayoutTransformControl, I showed a trivial use case to demonstrate the need for LayoutTransform in some scenarios. I also noted that Silverlight 2 supports only RenderTransform, but went on to demonstrate how it was possible to use RenderTransform to get LayoutTransform behavior. I described how my LayoutTransformControl sample did just that, but implemented only RotateTransform functionality for its initial release. My impression then - as now - is that rotation is easily the most common use of LayoutTransform. However, there are plenty of scenarios where the other Transform subclasses (scale, skew, matrix, etc.) are required, and I wanted to have a solution for them as well.

So I've added full support for arbitrary transformations to LayoutTransformControl along with updating it for Beta 2! (The complete implementation - along with demo and test applications - can be found in LayoutTransformControl.cs in the attached ZIP.)

Where you might previously have written:

<local:LayoutTransformControl Angle="15">
    <TextBlock Text="I am rotated 15 degrees!"/>
</local:LayoutTransformControl>

You now write:

<local:LayoutTransformControl>
    <local:LayoutTransformControl.Transform>
        <RotateTransform Angle="15"/>
    </local:LayoutTransformControl.Transform>
    <TextBlock Text="I am rotated 15 degrees!"/>
</local:LayoutTransformControl>

Wait a second! That's more typing than before - why is this an improvement? Well, because you can also do this (just as you would on WPF):

<local:LayoutTransformControl>
    <local:LayoutTransformControl.Transform>
        <ScaleTransform ScaleX="1.5" ScaleY="2.5"/>
    </local:LayoutTransformControl.Transform>
    <TextBlock Text="I am a little wider and a lot taller!"/>
</local:LayoutTransformControl>

Or this:

<local:LayoutTransformControl>
    <local:LayoutTransformControl.Transform>
        <MatrixTransform Matrix="-1,0,0,1,0,0"/>
    </local:LayoutTransformControl.Transform>
    <TextBlock Text="I am flipped horizontally!"/>
</local:LayoutTransformControl>

Or even this:

<local:LayoutTransformControl>
    <local:LayoutTransformControl.Transform>
        <TransformGroup>
            <ScaleTransform ScaleX="1.5" ScaleY="2.5"/>
            <SkewTransform AngleX="10"/>
            <MatrixTransform Matrix="-1,0,0,1,0,0"/>
            <RotateTransform Angle="15"/>
        </TransformGroup>
    </local:LayoutTransformControl.Transform>
    <TextBlock Text="I am all of the above - and tilted, too!!"/>
</local:LayoutTransformControl>

So maybe it's a bit of an improvement, after all... :)

Naturally, I updated my sample application to show off the new capabilities:

LayoutTransformControl Sample Application

And I updated my cross-platform test suite as well (here's the WPF version):

LayoutTransformControl Test Matrix on WPF

LayoutTransformControl should now be just as powerful as WPF's LayoutTransform is. Your application can take advantage of complete LayoutTransform functionality today - even though the Silverlight platform still doesn't officially support LayoutTransform!

Notes:

  • Improvements to Silverlight in Beta 2 (vs. Beta 1) meant that I was able to remove the lesser of the two Silverlight-specific workarounds present in the Beta 1 code.
  • I removed Button from the test matrix and added two new test cases to cover scenarios where LayoutTransformControl was previously behaving incorrectly. (Unfortunately, the default Button template in Beta 2 still causes the Button to measure/arrange itself incorrectly in some cases. The existing ContentControl test cases already cover correct Button behavior, so there's no loss of coverage.)
  • In WPF, changes to the members of a Transform property (be it an individual Transform or a TransformGroup) automatically cause the Transform property's property changed handler to be invoked (even though OldValue==NewValue). This is a wonderfully elegant way to handle the situation - and unfortunately this behavior isn't present on Silverlight. So LayoutTransformControl needs a way to know when one of its Transform members has been updated and it can't rely on Silverlight to tell it. One solution is to try to create a Binding to the relevant properties of each of the members. Unfortunately, Bindings don't work on Transform-derived classes in Silverlight (my theory: because Transform derives from DependencyObject instead of FrameworkElement where the SetBinding method is implemented). So what I've done is create a set of "Ltc"-prefixed Transform-derived classes that do provide automatic notifications (but still not Binding support - due to the same Silverlight limitation, I suspect). So instead of RotateTransform, you use LtcRotateTransform and get change notifications for free. The "Ltc" Transforms have the same transformation properties (ex: Angle, ScaleX) as their counterparts, so they're simple, plug-compatible replacements - with two caveats: LtcTranslateTransform doesn't exist (because TranslateTransform has no effect during LayoutTransform), and the CenterX/CenterY properties aren't implemented (for the same reason).
  • The "Ltc" Transforms are a pretty good workaround, but you don't have to use them. If the members of the LayoutTransformControl's Transform property never change, then change notification is unnecessary and the standard Transforms can be used without issue. The code in the "Motivation" project (part of the attached solution) demonstrates this approach. If the members of the Transform property do change and you're able to use the "Ltc" transforms, then the right thing should happen automatically. The code in the "Sample" project demonstrates this approach. But if your scenario is such that the members of the Transform property do change and you're unable to use the "Ltc" Transforms, all is not lost! LayoutTransformControl exposes a public TransformUpdated method that you can call to notify it of changes to the members of its Transform property. The code for the test matrix projects uses this last approach.
  • In any kind of project where the goal is to duplicate an existing behavior, it's common to do side-by-side comparisons to find bugs in the new implementation. This is exactly what my WPF test matrix does and it helped me find and fix a number of problems during the development of LayoutTransformControl. However, there typically comes a point at which this comparison process starts revealing differences that turn out to be bugs in the reference implementation. That's usually a pretty good sign that the new implementation is approaching the same level of quality as the old one - and it's a milestone I've hit with LayoutTransformControl. In addition to the "maximal area rectangle" improvement mentioned in the original blog post, LayoutTransformControl exhibits two new, deliberate behavior differences (improvements!) from WPF's LayoutTransform implementation. They're subtle, but they can both be demonstrated in the test matrix. Let me know if you think you've found one! :)
  • The examples above assume the "local" namespace prefix has been mapped to an assembly containing the LayoutTransformControl implementation:

    xmlns:local="clr-namespace:LayoutTransformControlSample;assembly=YourAssemblyName"

My goal with LayoutTransformControl has always been to address a perceived need. It began life as a bit of an experiment, but by now I've become rather fond of the solution and plan to incorporate it into projects of my own. I hope that if you have a need for LayoutTransform behavior in Silverlight, you'll consider LayoutTransformControl, too!

[LayoutTransformControl.zip]

Text; from a slightly different perspective [VerticalTextBlock Control sample for Silverlight 2]

During an informal discussion yesterday, the question of displaying text vertically (i.e., letters stacked on top of each other instead of side-by-side) came up and I offhandedly said it would be pretty easy to do. Later on, I got curious about just how accurate my claim was, so I spent a bit of time on the bus coding up a quick demonstration of what I had in mind. What I ended up with is a simple example, but it's enough that I feel like my flippant estimate wasn't so far off after all. :)

VerticalTextBlock is a simple Control subclass that runs on both Silverlight and WPF. The following XAML (in which the optional parts are italicized):

<local:VerticalTextBlock
    Text="Hello world"
    FontFamily="Arial"
    FontSize="12"
    FontWeight="Bold"
    Background="Orange"/>

Renders as follows (Silverlight on the left, WPF on the right):

VerticalTextBlock Demonstration

The implementation is quite simple: VerticalTextBlock exposes a Text property of type string, the VerticalTextBlock Template contains a TextBlock, and that TextBlock is populated with a series of LineBreak-separated Runs containing one character each.

Here's the complete code that compiles for both platforms:

using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;

namespace VerticalTextBlockSample
{
    public class VerticalTextBlock : Control
    {
        public VerticalTextBlock()
        {
            IsTabStop = false;
            var templateXaml =
                @"<ControlTemplate " +
#if SILVERLIGHT
                    "xmlns='http://schemas.microsoft.com/client/2007' " +
#else
                    "xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " +
#endif
                    "xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>" +
                    "<Grid Background=\"{TemplateBinding Background}\">" +
                        "<TextBlock x:Name=\"TextBlock\" TextAlignment=\"Center\"/>" +
                    "</Grid>" +
                "</ControlTemplate>";
#if SILVERLIGHT
            Template = (ControlTemplate)XamlReader.Load(templateXaml);
#else
            using(var stream = new MemoryStream(Encoding.UTF8.GetBytes(templateXaml)))
            {
                Template = (ControlTemplate)XamlReader.Load(stream);
            }
#endif
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _textBlock = GetTemplateChild("TextBlock") as TextBlock;
            CreateVerticalText(_text);
        }

        private string _text { get; set; }
        private TextBlock _textBlock { get; set; }

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text", typeof(string), typeof(VerticalTextBlock), new PropertyMetadata(OnTextChanged));
        private static void OnTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((VerticalTextBlock)o).OnTextChanged((string)(e.NewValue));
        }
        private void OnTextChanged(string newValue)
        {
            CreateVerticalText(newValue);
        }

        private void CreateVerticalText(string text)
        {
            _text = text;
            if (null != _textBlock)
            {
                bool first = true;
                foreach (var c in _text)
                {
                    if (!first)
                    {
                        _textBlock.Inlines.Add(new LineBreak());
                    }
                    _textBlock.Inlines.Add(new Run { Text = c.ToString() });
                    first = false;
                }
            }
        }
    }
}

At the end of the day, VerticalTextBlock is a simple control I wrote to keep myself honest. :) I figured it could be useful to others, so I'm posting about it and attaching a ZIP file with the complete solution+projects so anyone who's interested can try it out!

[VerticalTextBlock.zip]

Proof-of-concept Silverlight XPS reader comes to Beta 2 [SimpleSilverlightXpsViewer sample updated for Silverlight 2 Beta 2!]

Earlier this week I was asked about an update to my SimpleSilverlightXpsViewer sample for the newly released Silverlight 2 Beta 2. (Background reading: Introductory Post, Beta 1 Update.) I spent a bit of time on this just now and migrating SimpleSilverlightXpsViewer to Beta 2 was very straightforward.

SimpleSilverlightXpsViewer Application

I've updated the SimpleSilverlightXpsViewer demonstration page and also the source code download, so you can try things out in your browser and/or download the code to see how it works!

Notes:

  • A project reference to System.Net needed to be added in order for WebClient to resolve properly. (This is covered in the "Breaking Changes" document.)
  • DependencyObject.SetValue no longer seems to automatically convert the type of its value parameter, so there were three places I changed an int to a double for consistency with the underlying DependencyProperty type (ex: "0"->"0.0").
  • The "disappearing page border" issue that showed up in Beta 1 still seems to be present in Beta 2, so I worked around it by changing the "pageGraphic" from a Rectangle[Fill/Stroke] to a Border[BorderBrush]+Grid[Background]. A web search turned up a couple of customer reports of this behavior for Shapes (Path, Rectangle, etc.), so it should be fixed for the final release of Silverlight.
  • I discovered and fixed a bug with the math behind the zoom slider that could cause problems when the browser window was made very small. (This bug was unrelated to Beta 2.)

SimpleSilverlightXpsViewer is a fun project that really seems to resonate with people; folks have told me all kinds of neat ideas they had while playing around with it. In fact, if all goes well, there just might be a cool, practical, relevant use of SimpleSilverlightXpsViewer that I'll be able to share sometime soon... :)

Again with the support for simple HTML display in Silverlight [HtmlTextBlock sample updated for Silverlight 2 Beta 2!]

A customer recently asked about an update to my HtmlTextBlock sample for the newly released Silverlight 2 Beta 2. (Background reading: HtmlTextBlock Announcement, Improvements, Beta 1 Update, Data Binding Support) I'd meant to eventually migrate my samples anyway, so HtmlTextBlock seemed like a great place to begin!

HtmlTextBlock Demonstration

I've updated the HtmlTextBlock demonstration page and also the source code download, so you can try things out in your browser and/or download the code to see how it works!

Notes:

  • InitializeFromXaml was removed from Beta 2, so HtmlTextBlock now uses XamlReader.Load to set the equivalent Template in its constructor (and uses OnApplyTemplate to get a reference to the contained TextBlock).
  • Some of the properties HtmlTextBlock used to explicitly expose for TextBlock compatibility have moved to the Control class (from which HtmlTextBlock derives) in Beta 2, so HtmlTextBlock no longer implements them itself (ex: FontFamily, FontSize, Foreground). A handful of TemplateBindings are used to bind these properties of the Template's TextBlock to the corresponding properties on HtmlTextBlock.
  • The parameter list of DependencyProperty.Register changed slightly in Beta 2 and PropertyMetadata is now used to wrap the PropertyChangedCallback parameter.
  • Special-case code for setting certain values of TextBlock.LineHeight is no longer necessary in Beta 2 and has been removed.
  • The unusual "text disappears for certain font/size combinations" behavior that was present in Beta 1 has been fixed by the Silverlight team for Beta 2.

As you might expect, migrating HtmlTextBlock to Beta 2 was fairly straightforward. The majority of the changes arose because it tries to be a TextBlock and some of the text properties shifted around. (Incidentally, I wouldn't expect most applications to bump into this at all.) I hope people find the new sample useful as they come up to speed on Silverlight 2 Beta 2!

Continuing access to Silverlight 2's generic.xaml resources [SilverlightDefaultStyleBrowser updated for Silverlight 2 Beta 2!]

Silverlight 2 Beta was released earlier today and one of the big changes is the switch to using Visual State Manager for control design/development. Another is that many of the core controls (Button, ListBox, etc.) have moved from the SDK into the Silverlight runtime itself (which nicely avoids the need to include them with every application download). I wanted to be sure that SilverlightDefaultStyleBrowser (background reading available here and here) worked seamlessly with Beta 2, so I made a few quick changes to help users explore the new stuff.

The version number of SilverlightDefaultStyleBrowser always appears in the window's title and the latest release number is 1.0.3079.23243. (Note: I haven't updated the original screen shot which shows the introductory version number.) If installed with ClickOnce, the application should automatically prompt you to upgrade once it detects the update (which typically happens after running the app once or twice). If you're using the standalone EXE then you'll need to update manually. For people who might not be able to upgrade to Beta 2 immediately, there's no need to worry because the new version works with the Beta 1 bits as well.

SilverlightDefaultStyleBrowser Sample page

Click here or on the image above to install SilverlightDefaultStyleBrowser via ClickOnce with automatic updating.

Click here to download the standalone SilverlightDefaultStyleBrowser executable and source code in a ZIP file.

Notes:

  • Now that Blend 2.5 has rich support for Silverlight control design, some of the uniqueness of SilverlightDefaultStyleBrowser has been lost. However, it's still pretty handy for situations where Blend isn't available, so there may still be some residual value here. :)
  • The change I knew I wanted to make was the default search path for control assemblies. In Beta 1, nearly all the controls were part of the SDK, so that was the only place SilverlightDefaultStyleBrowser looked. But with Beta 2, the core controls are part of the Silverlight runtime and are located in the runtime install directory. SilverlightDefaultStyleBrowser now automatically imports assemblies from both locations when it loads.
  • The thing I didn't expect to change was special-casing the XML namespace for the Visual State Manager parts of the new control templates. SilverlightDefaultStyleBrowser uses LINQ to XML for parsing XAML and, by default, LINQ to XML emits namespace attributes at the time of first use (instead of on the top node). As far as I know, this is perfectly valid, but the resulting XAML causes a compile error when pasting such a Style/Template into Page.xaml: "Unexpected PROPERTYELEMENT in parse rule ...". This isn't ideal, so I added a bit of code to introduce the "xmlns:vsm" namespace on the Template node (i.e., before its first use) to avoid this error. (Note: The extra namespace is unnecessary - but harmless - for Beta 1 Templates.)
  • The header text changed from (for example) "System.Windows.Controls.ListBoxItem" to "System.Windows.Controls:ListBoxItem" to better communicate that ListBoxItem came from the System.Windows.Controls assembly (vs. being part of that namespace (which is not always be the case)).
  • If you compile the source code yourself, you may get a compiler warning from the ClickOnce settings in the project files: "Unable to find manifest signing certificate in the certificate store.". Despite turning off ClickOnce manifest signing, it seems to have gotten turned back on somehow. To make the warning go away, it should be enough to go to the Project's Properties page and uncheck the "Sign the ClickOnce manifests" box on the Signing tab. However, I found that unchecking that box didn't always do the trick - what worked for me was to check that box, use the "Create Test Certificate" button to create a dummy certificate, and then uncheck it.

Well, that's all the news about SilverlightDefaultStyleBrowser. Now go download Silverlight 2 Beta 2 and have fun with the new features! :)