Introduction
This article describes a Silverlight custom control listbox with a RadialPanel
as a prize wheel. The wheel can be loaded with text, number, or image data. Using the spin button, we can start the wheel animation. The wheel would spin and select a random item. A second animation displays the selected winning item. This project shows how to design custom controls and animation in Silverlight using code-behind and XAML.
Background
Silverlight custom controls are very flexible. Using the templates, we can design custom controls which look different than customary rectangular ones. This project uses a custom circular list box and a RadialPanel
for the prize wheel. The design is inspired by excellent blogs on building custom controls by Jeff Prosise, Scott Guthrie, and Jit Ghosh. These are listed in the references at the end of this article.
Output
The page looks like the image shown below. A live demo is available at this site: Prize Wheel Live Demo.
When the page gets loaded, the prize wheel (with a default diameter of 300) is loaded with text data and displayed. The diameter of the wheel can be changed by using the numeric up down counter. The wheel will be modified with the new diameter when the Radius Load button is pressed. The data contents of the wheel can be changed by picking one of three options: number, text, or images, and pressing the Data Load button.
Picture wheel is shown below:
The number wheel is shown below:
When the spin button is pressed, a random number box is chosen and the wheel rotation starts. The counter on the top right corner gets loaded with the number of steps for the wheel animation. This will count down to zero when the wheel is rotating. The wheel rotates up to the randomly chosen item and stops. Then the second animation starts. The chosen item is magnified, translated, and flipped (using plane projection animation), and displayed at the center of the wheel. The chosen item number and the angle are displayed in a list box on the right top corner. This list box and counter are for debugging purposes, and can be removed (or collapsed) if needed.
The winner selected image is shown below:
Code Description
Prize Wheel Control Template
One of the most powerful features of Silverlight is the ability to completely customize the look and feel of the controls. I have used these capabilities in designing the prize wheel as a custom control, which is the main element of this project. It is a list box with a circular shape and a RadialPanel
. Please refer to excellent articles on custom controls by Scott Guthrie (reference 2) and Jit Ghose (reference 3) given below. The shape of the list box is determined by the template which is coded in the App.Xaml file resources. Here we are declaring an Ellipse
as the control shape, with a gradient fill. By this trick, we are replacing the control's visual tree with an ellipse, the dimensions of which can be defined at run time. By making the ellipse width and height equal, we can create a circle as the shape of the control.
<Style TargetType="ListBox" x:Key="RoundListBox_Style" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Grid>
<Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Ellipse.Fill>
<RadialGradientBrush GradientOrigin="0.5,0.5"
Center="0.5,0.5" >
<GradientStop Color="SteelBlue" Offset="0.1" />
<GradientStop Color="RoyalBlue" Offset="0.25" />
<GradientStop Color="LightSkyBlue" Offset="0.50" />
<GradientStop Color="LightBlue" Offset="0.75" />
<GradientStop Color="BurlyWood" Offset="1.0" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<ItemsPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ItemsPresenter
: The next interesting thing in the template is the ItemsPresenter
. This lets us preserve the multiple item content model, and we will be doing another trick to use the RadialPanel
as the items presenter.
Custom RadialPanel
The RadialPanel
uses the excellent work done by Jeff Prosise (reference 1), which is in RadialPanel.Cs file. In this class, MeasureOverride
and ArrangeOverride
do the heavy lifting to arrange the listbox items in a radial layout. The prize wheel code in XAML is given below:
<ListBox
Style="{StaticResource RoundListBox_Style}"
x:Name="Prize_ListWheel">
<ListBox.RenderTransform>
<RotateTransform x:Name="Prize_ListWheel_Rotate"
Angle="0" >
</RotateTransform>
</ListBox.RenderTransform>
<ListBox.ItemsPanel>
<ItemsPanelTemplate x:Name="RPIPT1">
<custom:RadialPanel x:Name="RadialPanel1"
Loaded="RadialPanel1_Loaded"
ItemAlignment="Center"
ItemOrientation="Rotated" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
The RadialPanel1_Loaded
event handler gets the RadialPanel
radius, and uses it to modify the wheel parameters by calling ModifyWheelParameters()
.
private void RadialPanel1_Loaded(object sender, RoutedEventArgs e)
{
radialpanelxaml =(RadialPanel) sender;
ModifyWheelParameters();
Load_Wheel_Data();
}
ModifyWheelParameters
calculates the radius value from the up down counter, and adjusts the Prize_ListWheel
ellipse width and height using a variable, wheelradiusfactor
, which is 2.4. This factor fits the RadialPanel
inside the listbox circular shape.
private void ModifyWheelParameters()
{
Prize_ListWheel.ItemsSource = "";
radialpanelradius = Convert.ToInt16(Counter_Radius.Value);
radialpanelxaml.Radius = radialpanelradius;
Prize_ListWheel.Height = radialpanelradius * wheelradiusfactor;
Prize_ListWheel.Width = radialpanelradius * wheelradiusfactor;
Prize_ListWheel_Rotate.CenterX = Prize_ListWheel.Width/2;
Prize_ListWheel_Rotate.CenterY = Prize_ListWheel.Height/2;
Canvas.SetLeft(Prize_ListWheel, ListWheelLeft);
Canvas.SetTop(Prize_ListWheel, ListWheelTop);
RectMaskCreate();
}
Animation Code
There are two animations in this project. The main animation is the prize wheel rotation. It is very simple and straightforward. This code in the Main.Xaml determines the rotation transform:
<ListBox.RenderTransform>
<RotateTransform x:Name="Prize_ListWheel_Rotate"
Angle="0" >
</RotateTransform>
</ListBox.RenderTransform>
When the Spin button is pushed, the SpinCode_Start_Click
event is raised, which picks a random number and calculates the angle to be rotated. The variable numberofrotations
is set to be two rotations (so that the wheel will rotate twice) + the random number listbox item angle. The variable anglefactor
is set to be 4 to increase the number of steps of rotation to produce a smooth rotating wheel motion. This event also starts the myDispatchTimer
, which is a 25 ms timer set in MainPage()
.
private void SpinCode_Start_Click(object sender, RoutedEventArgs e)
{
Normal_Shift_Box(oldrandomnumber);
Random random = new Random();
if (Pictures_Button.IsChecked==false)
{
randomnumber = random.Next(0, itextmaxcount);
animanglerot =360.0 / Convert.ToDouble(itextmaxcount);
anglesteps = Convert.ToInt64((itextmaxcount*numberofrotations
- randomnumber+oldrandomnumber) * anglefactor);
}
else
{
randomnumber = random.Next(0, iimagemaxcount);
animanglerot = 360.0 / Convert.ToDouble(iimagemaxcount);
anglesteps = Convert.ToInt64((iimagemaxcount *
numberofrotations - randomnumber + oldrandomnumber) * anglefactor);
}
anglesteps--;
Prize_ListWheel_Rotate.Angle += animanglerot / anglefactor;
myDispatcherTimer.Start();
}
Every 25 ms, the Each_Tick
event is raised. This event rotates the wheel one step till the anglesteps
counter goes to zero.
public void Each_Tick(object o, EventArgs sender)
{
if (anglesteps > 0)
{
Prize_ListWheel_Rotate.Angle += animanglerot / anglefactor;
anglesteps--;
}
else
{
ListBoxWinners.Items.Add(randomnumber.ToString()+", " +
Prize_ListWheel_Rotate.Angle.ToString());
myDispatcherTimer.Stop();
Winner_Animation();
oldrandomnumber = randomnumber;
}
TextBox_Counter.Text = anglesteps.ToString();
}
When anglesteps
goes to zero, the second animation and the myDispatcherTimer2
is started.
This animation uses Scale_Shift_Box
which uses PlaneProjection
, ScaleTransform
, and TranslateTransform
to create the second animation to bring the winning item to the top center of the prize wheel.
private void Winner_Animation()
{
double Scale=1;
double translatefactor=-0.5;
int selectedbox = randomnumber;
TextWinner.Visibility = Visibility.Visible;
transform_animation_method(Scale, translatefactor, selectedbox);
}
private void Scale_Shift_Box(int boxnumber)
{
TransformGroup tg = new TransformGroup();
ScaleTransform st = new ScaleTransform();
PlaneProjection pp = new PlaneProjection();
st.ScaleX = 1 + Scale10 / step10;
st.ScaleY = 1 + Scale10 / step10;
TranslateTransform tt = new TranslateTransform();
tt.X = xincrement * (stepmax - step10);
tt.Y = yincrement * (stepmax - step10);
tg.Children.Add(st);
tg.Children.Add(tt);
if (Pictures_Button.IsChecked == true)
{
imagelist[boxnumber].RenderTransform = tg;
imagelist[boxnumber].Projection = pp;
pp.RotationY = 15 * step10;
}
else
{
namelist[boxnumber].RenderTransform = tg;
namelist[boxnumber].Projection = pp;
pp.RotationY = 15 * step10;
}
}
Points of Interest
Custom controls are one of the very interesting parts of Sivlerlight. The articles by Jeff Prosise, Scott Guthrie, Jit Ghosh will give you a very good overview of custom controls. You can also use Xamlpadx-v2 from Lester's blog (reference 4) to understand the Visual Tree in Silverlight controls using the information provided in Jit Ghosh's blog.
References
- Radial Layout in Silverlight, blog by Jeff Prosise on the
RadialPanel
control.
- Silverlight Tutorial Part 7: Using Control Templates to Customize a Control's Look and Feel, blog by Scott Guthrie about custom controls.
- WPF Control Templates - An Overview, Jit Ghosh's blog on custom controls
- Xamlpadx-v2, link to Xamlpadx-v2 from Lester's XML blog, an excellent tool for understanding custom controls.
- Prize Wheel Live Demo: A live demo of this project.
Other Articles by the Author
History
This is the first version of this article.