A Silverlight Magic Eight Ball

This application is a virtual Magic Eight Ball that can help you predict the future. Be careful what you ask it, you may not want to know the answer!


 

img3.jpg

Figure 1 - Eight Ball Demo in Silverlight

 

Introduction

When I was a kid, there used to be a little toy in the store called the Magic Eight Ball.  You could ask it a Yes/No question, shake it, and it would give you the answer.  The amazing thing is that sometimes it would predict things correctly!  Years later someone told me that if you want to become a successful stock broker, get a group of 64 people, and tell them to ask the magic eight ball whether to buy or sell a particular stock.  Chances are that 32 people will get the correct answer.  Now take those 32 people, tell them to ask the eight ball whether or not to buy a particular stock and have them shake the magic eight-ball.   Chances are you now have 16 people who wisely took the advice of the magic 8 ball twice.  Although the magic eight ball is a gimmick that has a good a chance of giving you the correct answer as flipping a coin, it still a lot of fun and here we have duplicated its magic in Silverlight.

The Application

Although we don't plan on shaking the window containing the eight ball, we'll simulate it's effects by dragging the mouse over the triangular window area.  So first ask the eight ball your question.  Remember it needs to be a yes/no question.  "Who is going to win the SuperBowl this year?" is not a legitimate question unless your team is named "Yes" or "No".  After the question is posed, the answer from the ghostly netherworlds will slowly fade into view.

The Design


img5.jpg

Figure 2 - Designing the Eight-Ball in Blend

 

There is not much code behind the eight-ball.  Almost the entire project can be designed in blend.  All of the actual animation for the eight ball and kicking off  the animation can be handled with Triggers and Behaviors inside of blend.  The only code that needs to be written is code needed to produce random messages bound to the eight ball.

The XAML

The Eight Ball Consists of 2 Ellipses, 2 TextBlocks, 2 Graphics Paths, 2 Behaviors, and 1 Storyboard.  That's our eight ball.  After that it's just a matter of getting everything to line up correctly and coloring and styling.  Getting everything to line up and size correctly was actually the hard part for me, but eventually it all worked out.  The XAML is listed in Listing 1.  Also included in our XAML is the constructor for our ViewModel, EightBallMessageManager.  Keeping to MVVM, we bind the manager to our grid and we bind the Message property of the ViewModel  to the TextBox displaying our message inside the Triangle Path.

 

Listing 1 - XAML for the Eight Ball

<UserControl 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" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:ViewModel="clr-namespace:MagicEightBall.ViewModel" x:Class="MagicEightBall.MainPage"

    d:DesignHeight="300" d:DesignWidth="400" Width="400" Height="300" mc:Ignorable="d">

       <UserControl.Resources>

        <ViewModel:EightBallMessageManager x:Key="messageManager" />

              <Storyboard x:Key="FadeAnswer">

                     <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="textBlock">

                           <EasingDoubleKeyFrame KeyTime="0" Value="0"/>

                           <EasingDoubleKeyFrame KeyTime="0:0:3" Value="1"/>

                     </DoubleAnimationUsingKeyFrames>

              </Storyboard>

       </UserControl.Resources>

 

    <Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource messageManager}">

        <TextBlock Canvas.ZIndex="10" Height="48" Margin="197,28,169,224" Foreground="Black" TextWrapping="Wrap"  FontSize="24" Text="8" />

        <Ellipse Margin="55,0,45,0" Width="300" Height="300" Stroke="Black" RenderTransformOrigin="0.5,0.5">

            <Ellipse.Fill>

                           <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

                                  <GradientStop Color="Black" Offset="0"/>

                                  <GradientStop Color="White" Offset="1"/>

                           </LinearGradientBrush>

                     </Ellipse.Fill>

              </Ellipse>

      

              <Path Data="M216.96667,226.4474 C164.96667,224.4474 209.20387,233.08141 209.20387,233.08141" Margin="133.501,140.171,184.826,84.047" Stretch="Fill" Stroke="White" UseLayoutRounding="False" RenderTransformOrigin="0.5,0.5" Cursor="Hand">

                     <Path.RenderTransform>

                           <CompositeTransform ScaleY="2" ScaleX="2"  Rotation="431" TranslateX="11.486352529854202" TranslateY="8.1514689166415621"/>

                     </Path.RenderTransform>

                     <Path.Fill>

                           <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

                                  <GradientStop Color="Black" Offset="0"/>

                                  <GradientStop Color="White" Offset="1"/>

                           </LinearGradientBrush>

                     </Path.Fill>

                     <i:Interaction.Triggers>

                           <i:EventTrigger EventName="MouseEnter">

                                  <ei:ControlStoryboardAction Storyboard="{StaticResource FadeAnswer}"/>

                                  <i:InvokeCommandAction Command="{Binding  Mode=OneWay}"/>

                           </i:EventTrigger>

                     </i:Interaction.Triggers>

              </Path>

              <TextBlock x:Name="textBlock" TextAlignment="Center" Margin="174,111.904,160,63.096" TextWrapping="Wrap" Text="{Binding Message}" FontWeight="Bold" FontSize="13.333" d:LayoutOverrides="VerticalAlignment" RenderTransformOrigin="0.5,0.5" UseLayoutRounding="False" d:LayoutRounding="Auto" VerticalAlignment="Center" HorizontalAlignment="Center">

                     <TextBlock.RenderTransform>

                           <CompositeTransform SkewY="-1.005" TranslateY="-0.482"/>

                     </TextBlock.RenderTransform>

                     <TextBlock.Foreground>

                           <RadialGradientBrush>

                                  <GradientStop Color="#FFAEB437" Offset="0"/>

                                  <GradientStop Color="White" Offset="1"/>

                           </RadialGradientBrush>

                     </TextBlock.Foreground>

              </TextBlock>

              <Path Data="M136.75972,247.40907 L169.72667,247.4695 L291.31763,247.46623 L320.49945,247.40099" Height="21.983" Margin="124.387,0,123.5,38.501" Stretch="Fill" Stroke="White" UseLayoutRounding="False" VerticalAlignment="Bottom" RenderTransformOrigin="0.495,-0.84">

                     <Path.Fill>

                           <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"

> 

                                  <GradientStop Color="Black" Offset="0"/>

                                  <GradientStop Color="White" Offset="1"/></LinearGradientBrush>

                     </Path.Fill>

              </Path>

        <Ellipse Height="55" HorizontalAlignment="Left" Margin="174,18,0,0" Name="ellipse1" Stroke="White" Fill="White" StrokeThickness="1" VerticalAlignment="Top" Width="62" />

    </Grid>

</UserControl>

 Behaviors

Behaviors are an awesome feature of Silverlight allowing us to have our UI Elements perform behaviors based on a particular event with just a few lines of XAML.  The eight ball uses two behaviors built into the blend sdk:  ControlStoryboardAction and InvokeCommandAction. The  ControlStoryboardAction tells Silverlight to play a storyboard off a particular event.  In the case of our eight ball, we are playing the FadeAnswer story when the mouse enters the triangular Path.

            <ei:ControlStoryboardAction Storyboard="{StaticResource FadeAnswer}"/>

 

The other behavior, InvokeCommandAction,  allows us to invoke a command in our ViewModel based on an event.  In the eight ball, we trigger off the same event to call a command inside of the EightBallMessageManager ViewModel to tell it to refresh the answer after hovering over the triangle path.   In order to make it easy for ourselves, we just implement the ICommand interface on our ViewModel so we can just bind directly to it.

<i:InvokeCommandAction Command="{Binding  Mode=OneWay}"/>

The ViewModel

The ViewModel implements two interfaces:  ICommand and INotifyPropertyChanged.  ICommand is used along with our InvokeCommandAction and INotifyPropertyChanged is used to tell our Silverlight UI that the message has changed.  Listing 2 is the ViewModel for the Eight Ball:

using System;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.Windows.Input;

namespace MagicEightBall.ViewModel
{
      
public class EightBallMessageManager : INotifyPropertyChanged, ICommand
      
{
             
readonly Random _random = new Random((int) DateTime.Now.Ticks);
             
private readonly List<string> _predictions = new List<string> {"Yes", "No", "Maybe",
                    "It's  Possible"
, "Not for sure", "Of Course"}; 

              public string Message { get; set; } 

              public void Refresh()
             
{

                  // generate random message and notify the UI
                    
Message = _predictions[_random.Next(_predictions.Count)];
                    
PropertyChanged(this, new PropertyChangedEventArgs("Message"));
             
} 

              public event PropertyChangedEventHandler PropertyChanged = delegate {};

              public bool CanExecute(object parameter)
             
{
                    
return true;
             
}

              public void Execute(object parameter)
             
{

                 // called by the InvokeCommandAction to refresh the Message in the eight ball

                    
Refresh();
             
}

              public event EventHandler CanExecuteChanged;
      
}

}

 

As we mentioned, there is not much code here.  Almost all the cool functionality is inside of the XAML.  When we mouse over the Path inside the Eight Ball, the MouseEnter event is triggered and implements the fade in storyboard.  The event also calls the InvokeCommand behavior on the EightBallMessageManager ViewModel.  Upon executing the behavior, Execute is called inside our ViewModel.  Execute calls Refresh which simply updates the Message with a random prediction and triggers a notify property changed event on the Message property.  The results of all this triggering and eventing is that you see the new message fade into view inside the eight ball window.

Conclusion

Silverlilght makes it real easy to create fun graphic toys in the browser.  Feel free to play around with other animations to get some cool effects on your eight ball.  Maybe you can even figure out a way to write your own behavior that let's you shake the window to produce a new prediction. (I suspect the answers will be more accurate if you come closer to reproducing the official eight ball).  In any case, don't get behind the eight ball.  Start experimenting with Silverlight so you are up to speed with a truly powerful web development toolset.