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!

Hi [NAME], (Are you listening?)

Have you ever had a conversation similar to below? It happens far too frequently. If you give out your business card or contact information, expect that people might actually use it and expect a response. Don’t give it out if you don’t care about your customers. You’re just wasting my time if you don’t bother to respond.

———————–

We recently met at the [LOCATION:___________]. We were discussing your [product/solution/technology/____________]. We really had a [great/good/interesting/________] conversation. It really helped me understand some of the [issues/challenges/__________] I was facing recently.

You provided me your contact information and suggested that I e-mail you if [I needed more help/had suggestions/________]. So, I thought I’d take you up on that offer. I really appreciate your willingness to discuss this with me more.

[DETAILS]

———————–

A few weeks later ….

Hi [NAME],

I’m sure you’re really busy, but I was wondering if you got my last e-mail? I had really hoped that you’d continue the conversation we’d started at ____________. I’ve attached my original e-mail below for reference in case you misplaced it or it was unfortunately marked as spam.

———————–

A few weeks later ….

[NAME],

Any chance you’ve given any thought to my e-mail? I had expected to hear back from you by now. I’d really appreciate a response. I really do need [some help/comments on my feedback/______________].

———————–

A few weeks later ….

[NAME],

Hello?

What have I done to deserve this treatment? I’m a paying customer. If you hadn’t committed to me that you’d be willing to communicate, I wouldn’t be so frustrated now. Is it too much to ask for you to at least send back a response to my e-mails? It’s been several months.

———————–

Not soon enough ….

[NAME],

It is with no sadness that I’m writing to say that we’ve switched to your competitor. A little correspondence with us would have gone a long way. Event a polite, “I’m really busy, but I’ll look into the details of your e-mail soon” would have been better than the silence. Thanks for nothing.