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.

Netflix lays off people because Silverlight works…

Probably this is not quite the press Microsoft would have liked from Netflix.

For those of you who watch movies instantly on your PC or Mac, you may have noticed our player is much easier to install and use now with Silverlight. The good news is fewer problems for you. The bad news is that we are now overstaffed with technical specialists in our Customer Service (CS) group.

So last week we announced internally some changes in CS. 50 of our technical specialists will work through December, then be let go in early January after the holidays. 15 of our technical specialists will take new roles in the main CS group.

Too bad those affected couldn’t be moved into other positions. Admittedly, I don’t know how those affected are employed by Netflix – maybe contract work, etc. Maybe they can interview for other Netflix jobs. (Many companies operate that way – my team at Microsoft was cut, and I was forced to re-interview with no guarantee of future employment, even though at the time they were hiring hundreds of people every week).

I read a few of the comments (I know, I shouldn’t have) …

Too many are the typical, “It’s a Microsoft product, if it hasn’t failed you yet, it will. So, keep those employees.” 

One genius went so far as to call Silverlight, “pernicious malware.”

Drumming on Silverlight

A few weeks ago someone posted on their blog a simple drum kit written in Silverlight (Update: I found the original drum inspiration post here). I thought I’d give it a go as well tonight. Here’s the result:

image

(click to launch)

 

I used a few of the same tricks I’ve used before.

 

private void PlayAudio(string fileName, double volume)
{
    MediaElement me = new MediaElement();
    me.Source = new Uri(fileName, UriKind.Relative);
    me.Volume = volume;
    me.AutoPlay = true;
    me.CurrentStateChanged += new RoutedEventHandler(Audio_CurrentStateChanged);

    LayoutRoot.Children.Add(me);
}

public void Audio_CurrentStateChanged(object sender, RoutedEventArgs e)
{
    if (sender is MediaElement)
    {
        MediaElement me = sender as MediaElement;
        if (me.CurrentState == MediaElementState.Paused)
        {
            me.CurrentStateChanged -= new RoutedEventHandler(Audio_CurrentStateChanged);
            LayoutRoot.Children.Remove(me);
        }
    }
}

The kit plays multiple audio files at one time by adding (and removing) the audio streams dynamically as needed.

The ellipses are actually UserControls which use Dependency Properties to adjust to the needs of the specific drum.

private static void StandardBackgroundChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e) { Pad p = d as Pad; Ellipse playEllipse = p.FindName("playEllipse") as Ellipse; if (e.NewValue == null) { playEllipse.Fill = p.Resources["StandardBackgroundBrush"]
as Brush; } else { playEllipse.Fill = e.NewValue as Brush; } }

Even the audio file is a DependencyProperty:

public static readonly DependencyProperty AudioFileNameProperty =
    DependencyProperty.Register("AudioFileName", typeof(string),
    typeof(Pad), new PropertyMetadata(""));

The master UserControl uses eight instances of the “Pad” control like this:

<DrumPad:Pad 
    Grid.Column="1" 
    Grid.Row="1" 
    Margin="47,0,-47,0" 
    AudioFileName="/SoundEffects/Cymbal1.mp3" 
    ActivateKey="W" 
    StandardBackground="{StaticResource CymbalBrush}" 
    IsCymbal="True"/>

 

The source is here if you’re interested. If you do something cool with this … send me a link!