Silverlight ChildWindows and VisualStates

Unfortunately, as of the latest version of Silverlight 4 and Blend 4, there’s a feature supportability mismatch. Blend 4’s designer may lead to you to believe and expect that VisualStates will work within a Silverlight 4 application. (It led me down that path).

In fact, VisualStates do not work directly inside of a ChildWindow. VisualStates are only applicable on the root element of a ControlTemplate or UserControl (as described here). I just spent the last 20 minutes learning about this limitation the hard way – by trying it, over and over. :)  It’s not a bug, it just isn’t supported.

If you want to use VisualStates within a ChildWindow, the simplest workaround for this problem is to wrap the content of the child window into a new UserControl and place that UserControl in the ChildWindow (as a child). You’ll need to do a little more work to expose the functionality of the UserControl to the containing ChildWindow (like OK/Cancel button handling for example), but it’s simple work. I added an event which indicates the child window should be closed:

public event EventHandler<CloseDialogEventArgs> CloseDialog;
public class CloseDialogEventArgs : EventArgs
{
    public bool? DialogResult { get; set; }

    private CloseDialogEventArgs()
    {

    }
    public CloseDialogEventArgs(bool? dialogResult)
    {
        DialogResult = dialogResult;
    }

    public static new CloseDialogEventArgs Empty = new CloseDialogEventArgs();
}

Then, it can be closed:

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
    if (CloseDialog != null)
    {
        CloseDialog(this, new CloseDialogEventArgs(false));
    }
}

Silverlight’s ItemsPanelTemplate, Horizontal Only?

Have you tried to create a horizontally oriented ListBox in Silverlight with a custom ItemsPaneltemplate, with a Panel of type other than StackPanel? If you have, you’ll likely have run into the same problem I have – you can’t really do it. Unfortunately, the ItemsControl (which the ListBox derives from), eventually checks the Panel type of the ItemsPanel (the ItemsHost), and it only reacts properly to a Vertical orientation if the Panel type is a StackPanel.

internal bool IsVerticalOrientation()
{
    StackPanel itemsHost = base.ItemsHost as StackPanel;
    if (itemsHost != null)
    {
        return (itemsHost.Orientation == Orientation.Vertical);
    }
    return true;
}

You might try deriving from StackPanel, but unfortunately, ArrangeOverride and MeasureOverride are both sealed methods on the StackPanel. So, you can’t. This behavior certainly limits what you can do with a ListBox layout.

What happens if you try something vertically oriented without a StackPanel host? The behavior is that the ListBox won’t properly scroll and activate the current ListBoxItem. Calling ScrollIntoView has no impact (as the math doesn’t work correctly when the IsVerticalOrientation isn’t properly handled).

Yet another feature of WPF that you’ll need to ignore for the time being. :)

Converting RESTful XML output to formatted XML in Silverlight

Ever debugged output from a RESTful web service and was driven absolutely mad by the lack of formatting of the XML? Seriously, how much fun is it to stare at XML that has no line breaks or indentation whatsoever?

I haven’t tried to solve the problem within the IDE as that’s a different problem. However, I have written the code to convert the output into something that you could stuff into a TextBox for example (which is what I’ve done in my Silverlight application – I’ve created a small debug window for RESTFul web service calls).

 

HttpWebRequest request = (HttpWebRequest)
    HttpWebRequest.Create(new Uri("http://localhost:12345/MyWebService.asmx"));

request.BeginGetResponse(delegate(IAsyncResult ar)
{
    HttpWebRequest req = (HttpWebRequest)ar.AsyncState;
    HttpWebResponse webResponse = req.EndGetResponse(ar) as HttpWebResponse;
    try
    {
        using (Stream stream = webResponse.GetResponseStream())
        {
            XmlReader xmlReader = XmlReader.Create(stream);
            try
            {
                XmlWriterSettings settings = new XmlWriterSettings();                            
                settings.Indent = true;
                settings.Encoding = System.Text.Encoding.Unicode;
                using (StringWriter strWriter = new StringWriter())
                {
                    XmlWriter xmlWriter = XmlWriter.Create(strWriter, settings);
                    try
                    {
                        // cool, this recursively writes all of the nodes!
                        xmlWriter.WriteNode(xmlReader, false);

                        // output it to the UI as a string 
                        Dispatcher.BeginInvoke(delegate()
                        {
                            txtRawResults.Text = strWriter.ToString();
                        });
                    }
                    finally
                    {
                        xmlWriter.Close();
                    }
                }
            }
            finally
            {
                xmlReader.Close();
            }

            stream.Close();
        }
    }
    finally
    {
        webResponse.Close();
    }
}, request);

Replace the Uri in the first line with a call to your web service that returns XML.

Steps:

  1. The Response is requested asynchronously (as is the only option with Silverlight)
  2. Within the anonymous method, the request is retrieved from the IAyncResult object instance (ar)
  3. The async request is properly ended (EndGetResponse) and retreieved
  4. The stream is fetched from the response and passed to a new XmlReader instance.
  5. Create a new instance of the XmlWriterSettings object with indentation activated (Indent = True)
  6. Create a new XmlWriter instance using a new instance of a string writer and the settings created in step #5
  7. Recursively (thanks to the WriteNode method), write the entire document to the writer, formatted with indentations as requested!
  8. Invoke back to the UI thread with the result of the formatted, recursive WriteNode call.
  9. Clean up.

A Silverlight 2 TilePanel

You may notice that the Silverlight TileBrush is missing some key properties which would enable it to actually tile a brush. The WPF TileBrush has properties such as TileMode, Viewbox, and ViewportUnits that can be used to tile an image as a fill or as a background for a UIElement. For some reason, they’re not implemented in Silverlight.

The Silverlight object framework is far more closed than the equivalent WPF framework. There are many more sealed class types and methods in Silverlight than in WPF. For example, Canvas in Silverlight is sealed. If you want to make a more interesting Canvas of some sort, you need to implement it from a base Panel. It’s not clear why classes such as Canvas are sealed in Silverlight.

Back to the TileBrush.

It’s not practical to try to extend the existing TileBrush in Silverlight to provide the tiling support offered by WPF so I took a very different approach.

As my tiling needs were straightforward, I created a new control, derived from Panel. The new control is called TilePanel. The TilePanel’s one and only task is, given an ImageBrush, and a few tile size values, tiles the brush to fill it’s container completely.

The use of the TilePanel is straightforward:

<local:TilePanel x:Name="pnlTile" TileWidth="32" TileHeight="16">
    <local:TilePanel.Image>
        <ImageBrush ImageSource="LED64x32-rect.png" />
    </local:TilePanel.Image>                   
</local:TilePanel>

Above, you’ll see the panel declaration, the width and height of the tile, and the brush that is used as the tiled image.

The TilePanel handles all of the details.

I created a small demo program to show off the tile support.

image

We have an exercise bicycle with an LED style display that inspired the simple demo program. It displays statistics and messages through the use of the bicycle. The demo allows you to change the text and the shape of the LED. Additionally, it shows how the tiles can be semi-transparent (the images used are 24-bit PNGs), allowing the content underneath to show through. In this case, it’s a large TextBlock which is animated within a clipped Canvas.

DispatcherTimer tmr = new DispatcherTimer();
tmr.Interval = TimeSpan.FromMilliseconds(250);
tmr.Tick += new EventHandler(UpdateCharacterPosition);
tmr.Start();

Any time the overall canvas changes size, the code adjusts the Clip property to a new rectangle (otherwise the text appears outside of the boundaries of the LED display).

private void TextHolderSizeChanged(object sender, 
SizeChangedEventArgs e) { if (sender is Canvas) { Canvas c = sender as Canvas; RectangleGeometry rc = new RectangleGeometry(); rc.Rect = new Rect(new Point(0, 0), e.NewSize); c.Clip = rc; } }

To more efficiently update the TilePanel when multiple properties are being set at one time, I took a queued approach to the update:

protected void TileAdjustmentNeededAsync()
{
    // by doing this sync, we can queue up several request
    // but then really only handle the last one.
    _needsUpdate = true;
    // async call the adjust tiles
    this.Dispatcher.BeginInvoke(delegate
    {
        AdjustTiles();
    });
}

This way, a few properties can be set together, without the TilePanel needlessly recalculating and rebuilding the tiles.

The demo and the source code are located here (zip).

A few notes for those interested …

For maximum efficiency, if you use the TilePanel, try to make the Tiles sized so that fewer tiles are actually necessary to tile (create the tiles so that there are tiles within the brush you use). Instead of a tile that has only one image in it for example, consider building a tile which is really a 2 x 2 set of tiles.

One detail of the implementation that is important: you cannot reliably add new controls/UIElements to the Children collection of a Panel within the MeasureOverride or ArrangeOverride methods. Although the child UIElements are added without error, they typically will not render or display correctly. Building something like this TilePanel required a trick where the actual updates to the tiles always happen outside of the scope of an measure-arrange pass.

Where do you "event"?

From JohnPapa.net, “Siliverlight 2-Tip – Declaring Events.”

He thinks that the place to wire up an event is always in code rather than in markup (like XAML).

“What’s my opinion? I firmly believe the handlers belong in the code and not in the XAML.”

I’d suggest that’s not always right either (and maybe not the right question to ask). This technique doesn’t allow the designer to have freedom to decide how to build the interface freely. If you wire up to a “button” click, then the designer has to implement a certain UI feature as a button. Maybe that’s not what they wanted. The UI/code are wound together extremely tightly when using this pattern.

A command/message driven architecture that splits the actual implementation as much as possible is the best pattern for growth and true separation of roles. That being said, it’s not always practical to build a system like that.

I like the CONCEPT of WPF’s RoutedCommand system which is a way in WPF to handle this pattern, but I don’t care for the way that things are enabled and disabled via the CanExecute event handlers. The event handlers can be called hundreds of times a minute depending on your user interface.

The bad news is that Silverlight doesn’t lend itself easily to a message/command driven architecture (as the RoutedCommand system isn’t available in Silverlight 2.0). So what’s a developer to do? :)

I like the ‘toolability’ of doing inline (in XAML) wire-up of events. If the developer just provides a common set of end-points (events) to the UI designer, then they may have an opportunity to more freely control and adjust the user interface as needed without requiring code changes.

So in John’s example code, instead of

btnAddToCard.Click += new RoutedEventHandler(btnAddToCard_Click);

It might be better to provide a method called:

AddToCard(object sender, RoutedEventArgs e) {

}

That way, the designer can wire-up any way they want.

Or, I suppose one could build a rudimentary message/command system by using attached properties and a common event handler which handles all command driven user interface elements. (And now that I think about that more — I think that could end up being quite slick if done carefully).