Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Building the new PDC Trivia Application

Pete Brown - 03 November 2010

The PDC trivia application (more generically called the Event Trivia or Conference Trivia app) is a full-screen web-delivered Silverlight application that displays trivia questions on the big screen in the time between breakout sessions at a conference. So far, it has been used at PDC09, MIX10, and PDC10.

The previous versions had only trivia questions, and a very conference-themed display. The latest version, rewritten for PDC10, has a Halloween theme in recognition of the timing of the conference. The overall architecture for the runtime trivia portion hasn't changed, but the implementation details have. In addition, for the PDC2010 version, I added Twitter integration with tweets popping up as tombstones on the bottom half of the application.

Trivia application at PDC10

One other change is the removal of the images from the questions. While the database still supports images, I removed them in order to avoid any potential copyright issues (including images I purchased from various sites) which would prevent releasing this application on codeplex.

Overall Architecture

The Silverlight application follows a typical Silverlight application pattern: WCF service on an ASP.NET web site, database behind that. External services (search.twitter.com in this case) called directly from the Silverlight client.

The Silverlight client itself uses a basic MVVM pattern approach. I'm not using any specific toolkit, nor was I religious about it (there is a little bit of code in the code-behind). I unapologetically did what made sense in the few hours I had to develop this new version over the course of a few days. :) That said, the overall architecture is solid.

image

Why did I pick Silverlight? I wanted to have a lot of freedom for the experience, and I needed something that would install without any trouble. Since the PDC machines often include futures and side builds to show proofs-of-concept, I couldn't count on specific versions of .NET. I could, however, count on Silverlight 4 :)

Let's take a deeper look into the layers of this app, starting with the XAML user interface.

User Interface

The majority of the user interface was done with bitmap images (jpeg and pngs) created in Photoshop. The grass was created with a couple of CS5 brushes that look like, well, grass. I desaturated the color, then added a layer (render->clouds) which I used to modify the base layer to create some fog. I hand-edited the fog to get the look I was going for. The background is typical gradient with some manually-added stars.

Background

I then added some mist. My intent was to have this mist roll across the screen, but I didn't get a chance to implement that animation. To do that, I would create a much wider version of the mist and make sure it nicely trails off on the right.

Mist

Finally, I have the tombstone graphics, broken into two pieces. The first piece is the tombstone itself. This is what pops up from the grass. The right is just enough grass to cover the base of the tombstone. This is used to hide the base of the tombstone as well as provide something to cover the hard clipping edge used to allow the tombstone to pop up.

Tombstone1 Tombstone1_Grass

The two images are separate layers in the same Photoshop file.

Displaying the Tombstones

The tombstones are surfaced from the viewmodel as an ObservableCollection of Tweet objects. The Tweet object contains the tweet id, timestamp, sender, message and (although unused) the url of the sender's avatar:

// TwitterService

private ObservableCollection<Tweet> _tweets = new ObservableCollection<Tweet>();
public ObservableCollection<Tweet> Tweets
{
    get { return _tweets; }
}


// ViewModel

public ObservableCollection<Tweet> Tweets
{
    get { return _twitterService.Tweets; }
}

You'll notice that this is just a pass-through from the TwitterService class. The TwitterService class is responsible for polling twitter, and loading up the latest tweets. It's also responsible for semi-randomizing what shows up, but I know from feedback that I fell down a bit on the randomization in the PDC10 version. The version on codeplex will have that code updated.

The TwitterService class has a timer which makes a call to search.twitter.com every 60 seconds (30 would be better) and loads the latest tweets. Those are then put into the observable collection. Binding on the client takes care of the rest. In fact, the XAML UI for the tombstones in MainPage.xaml is pretty simple:

<Grid x:Name="TwitterFeed" Margin="50 300 50 0">

    <ItemsControl x:Name="TweetList"
                    ItemsSource="{Binding Tweets}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <localControls:GraveyardPanel ItemNearScale="1.50" BackgroundPositionJitter="35" />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
                    
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <localControls:Tombstone />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
                    
    </ItemsControl>
</Grid>

In that, you can see that I have an ItemTemplate which contains a reference to the Tombstone usercontrol. I also have a custom GraveyardPanel which is responsible for positioning the tombstones and showing one larger than the rest. The source for that will be included in the codeplex upload.

The use of the custom panel allows me to use a regular old ItemsControl. Yep, all seven tombstones (I could have done 9 on that 720p screen, I realized) are displayed using that single ItemsControl and the GraveyardPanel.

While the GraveyardPanel is responsible for positioning (which has some randomization built-in) the Tombstone control itself is responsible for canting the stone one way or the other, and displaying it after a random delay. The XAML for the tombstone usercontrol looks like this:

<UserControl x:Class="PeteBrown.ConferenceTrivia.Controls.Tombstone"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="874" d:DesignWidth="695">
    

    <UserControl.Resources>
        <Storyboard x:Key="ShakeAndAppear">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)"
                                           Storyboard.TargetName="grid">
                <EasingDoubleKeyFrame KeyTime="0"
                                      Value="1000">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <ElasticEase EasingMode="EaseOut"
                                     Oscillations="1"
                                     Springiness="10" />
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
                <EasingDoubleKeyFrame KeyTime="0:0:2"
                                      Value="0">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <ElasticEase EasingMode="EaseOut"
                                     Oscillations="1"
                                     Springiness="10" />
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)"
                                           Storyboard.TargetName="grid">
                <EasingDoubleKeyFrame KeyTime="0"
                                      Value="50"
                                      EasingFunction="{x:Null}" />
                <EasingDoubleKeyFrame KeyTime="0:0:2"
                                      Value="0">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <ElasticEase EasingMode="EaseOut"
                                     Oscillations="15"
                                     Springiness="7" />
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>
    
    <Grid x:Name="LayoutRoot">
        <Viewbox>
            <Grid Width="695"
                  Height="874">
                <Grid>
                    <Grid.Clip>
                        <RectangleGeometry Rect="0,-20,695,794" />
                    </Grid.Clip>

                    <Grid x:Name="grid"
                          RenderTransformOrigin="0.5,0.5">
                        <Grid.RenderTransform>
                            <CompositeTransform TranslateY="1000" />
                        </Grid.RenderTransform>

                        <Image Source="../Assets/Tombstone1.png" />

                    <Grid Margin="115 90 115 200">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>

                            <TextBlock Grid.Row="0"
                                       Text="{Binding Author}"
                                       Style="{StaticResource TweetAuthorStyle}">
                                                <TextBlock.Effect>
                                                    <DropShadowEffect BlurRadius="15"
                                                                      Color="Black"
                                                                      ShadowDepth="0" />
                                                </TextBlock.Effect>

                            </TextBlock>

                            <TextBlock Grid.Row="1"
                                       Text="{Binding Message}"
                                       Style="{StaticResource TweetMessageStyle}">
                                                <TextBlock.Effect>
                                                    <DropShadowEffect BlurRadius="10"
                                                                      Color="Black"
                                                                      ShadowDepth="0" />
                                                </TextBlock.Effect>

                            </TextBlock>

                        </Grid>
                </Grid>

                </Grid>
                
                <Image Source="../Assets/Tombstone1_Grass.png" />

            </Grid>
        </Viewbox>

    </Grid>
</UserControl>

 

There's a bunch there, but if you saw the tombstones in action, you'd have noticed:

  • They scale based on resolution
  • They randomly cant a number of degrees either left of right
  • After a short, random delay, they pop up from the grass (this involves clipping and some animation)

The ShakeAndAppear animation is a total cheat :) To get the shaking effect, rather than keyframe a true shake, I used a tight ElasticEase with a higher than usual number of iterations. The animation also moves the tombstone up so it appears within the clipping rectangle. Scaling is handled by the ViewBox.

The display of the tombstone is relatively straight forward. The initial state of the usercontrol is as shown in the left of the image below. The blue rectangle is the clipping rectangle for the tombstone (the grass sits in front of it, outside the clip). When the time comes to appear, the tombstone's Y offset is animated from 1000 to 0 with some easing to give it a little bounce. This brings it within the clipping bounds.

image

Forcing Full-Screen Mode

When you fist run the application, the displayed UI is just a background with a button asking you to enter full-screen mode. This is handled by grouping the page content into two containers, and by handling the FullScreenChanged event in the code-behind.

<Grid x:Name="RunningContent"
      Visibility="Collapsed">

    <Grid x:Name="Trivia"
    ...
    </Grid>


    <Grid x:Name="TwitterFeed" Margin="50 300 50 0">

        <ItemsControl x:Name="TweetList"
         ...
        </ItemsControl>
    </Grid>
    
 ...
</Grid>
    
<Grid x:Name="OpeningContent">
    <Rectangle Fill="Black" />
    <TextBlock Text="PDC 2010 Trivia by Pete Brown"
               Style="{StaticResource HashTagStyle}"
               HorizontalAlignment="Center"
               Margin="0 200 0 0"/>
    <Button Content="Click here for Full Screen" 
            Height="30" 
            Width="175"
            Click="Button_Click" />
</Grid>

The part of the code-behind that integrates with that looks like this:

public MainPage()
{
    ...
    App.Current.Host.Content.FullScreenChanged += new EventHandler(Content_FullScreenChanged);
}

void Content_FullScreenChanged(object sender, EventArgs e)
{
    if (App.Current.Host.Content.IsFullScreen)
    {
        RunningContent.Visibility = Visibility.Visible;
        OpeningContent.Visibility = Visibility.Collapsed;
    }
    else
    {
        RunningContent.Visibility = Visibility.Collapsed;
        OpeningContent.Visibility = Visibility.Visible;
    }
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    App.Current.Host.Content.IsFullScreen = true;
}

When the button is clicked, the application is placed into full-screen mode. That, in turn, fires the FullScreenChanged event. The same thing happens when the user hits escape to exit full-screen mode.

Deployment

At every conference where I use this, I request a small server. This is usually a desktop or workstation-class box with 8gb memory and Windows Server 2008 r2. On it I install .NET 4 and SQL Server Express 2008. The database is in the app_data folder of the site.

Once the site is set up and tested (I deploy everything via remote desktop), the server is ready to be packed and shipped to the facility. Once there, I usually do a smoke test to make sure everything is still working.

The room staff know to simply hit the URL for the machine and click the "full screen" button. It is super simple for them to use, which definitely adds to the success of the project.

Summary

There's more interesting stuff going on in the code. Rather than just paste it all in here, I figured I'll shortly release the source on codeplex at http://eventtrivia.codeplex.com so anyone can use it, modify it, or just take a look.

If you want to use this in your own events, please feel free to do so. In fact, I encourage you to use this, and add to the store of trivia questions. I'd love it if you mentioned somewhere obvious that you're using the app I wrote, but that's neither necessary nor a condition of use. Do tell me if you use it, though, and take pictures! :)

Update 2010-11-22: This is now published at http://eventtrivia.codeplex.com/

 

       
posted by Pete Brown on Wednesday, November 3, 2010
filed under:        

1 comment for “Building the new PDC Trivia Application”

Comment on this Post

Remember me