Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Path List Box Adventures - Part 1

0.00/5 (No votes)
18 Sep 2011 2  
This article describes a Prize Wheel designed with Silverlight Pathlistbox control.

Contents

Introduction

I wrote an article last year in CodeProject for a Silverlight PrizeWheel using a custom control Silverlight Prize Wheel Animation Using Custom Circular ListBox Control. This article describes a similar prize wheel designed with Silverlight Pathlistbox.

Background

Silverlight Pathlistbox is an exciting control which lets you use a ListBox, but position the items in a custom path. This article describes a Silverlight Template control for the Pathlistbox and custom controls for the user interface panel and help (as a HTML page). Resource Dictionaries have been used to modularize the XAML code.

Output

When you run the program, you get the page which looks like below. A live demo is available at this site: PathListBox Prize Wheel Live Demo.

Start_Screen

Start_Screen

The PrizeWheel can be loaded with text or image data. Start button would spin the Prizewheel and select a random item as the prize winner. The wheel size can be changed by the Width and Height NumericUpDown controls (minimum 300, maximum 500, increment 25, default values 400). The Wheel also can be rotated using the Angle NumericUpDown control (minimum 0, maximum 360, increment 1, default values 90). The Items orientation can be changed by the Orientation CheckListBox, the Angle by Angle NumericUpDown control (minimum 0, maximum 360, increment 30, default values 270) and the Size by the Scale NumericUpDownControl (minimum 1, maximum 100, increment 1, default values 10).

Code Description

Main Page

The MainPage has a 2 tabs Prize Tab and Help Tab. The Prize Tab has a Pathlistbox template control and a control panel user control. The Help Tab has the Help user control. The main page also has a Radio Button to select the control panel to be in the left or right. When this radio button selection is changed, the Grid column definitions are changed to place the control panel to the left or right of the Prize control. After the main page is loaded, the Initialize_Class1 is set up.

private void Initialize()
        {
            Initialize_Class1 initialize_class1 = new Initialize_Class1();
            initialize_class1.cp_control1 = this.cp_control1;
            initialize_class1.plb_t_c1 = this.plb_t_c1;
            initialize_class1.Initialize();
        }

See how the main page cp_control1 and plb_t_c1 are referenced for the Initialize_class1. This technique has been used to keep the classes modular so that they can be reused.

Initialize Class

The control panel combo box is populated with the text file names in this class. The click event handlers for the images and text button click are also set up here. When user changes the text file in the drop down combo box, changes the text or image button the PathListBox path and data are loaded again. The binding of the control panel items angle and scale are also initialized here. We will discuss more about the binding when the Prize Wheel control is described.

private void Set_Binding()
{
plb_t_c1.Item_Angle = 270.0;
plb_t_c1.Item_Scale = 10.0;
cp_control1.scale_numericupdown.SetBinding(NumericUpDown.ValueProperty,
new Binding("Item_Scale") { Source = plb_t_c1, Mode = BindingMode.TwoWay });
cp_control1.Angle_numericupdown.SetBinding(NumericUpDown.ValueProperty,
new Binding("Item_Angle") { Source = plb_t_c1, Mode = BindingMode.TwoWay });
}

Prize Wheel Control

This is a template control which generates the path ListBox along with a stack panel to show the prize winner. XAML for the template control is in the Generic.XAML file in the Themes folder. This template control has a Canvas named "PLB_Canvas", PathListBox control named "pathListbox1", a Stack Panel to display winners and couple of media elements to produce the audio effects when the PrizeWheel is spinning.

The PathListBox control is the most interesting and the code is given below:

<ec:PathListBox x:Name="pathListbox1" HorizontalAlignment="Left" 
VerticalAlignment="Top" WrapItems="True"
StartItemIndex="0" 
ItemContainerStyle="{StaticResource PLB_ItemStyle1}" 
>
<ec:PathListBox.LayoutPaths >
<ec:LayoutPath FillBehavior= "NoOverlap" Distribution="Even" 
Orientation="OrientToPath" Capacity="50" >
</ec:LayoutPath>
</ec:PathListBox.LayoutPaths>
<i:Interaction.Behaviors >
<Expression_Samples_PathListBoxUtils:PathListBoxScrollBehavior >
<i:Interaction.Triggers>
<i:EventTrigger >
<i:InvokeCommandAction CommandName="DecrementCommand"/>
</i:EventTrigger>
<i:EventTrigger >
<i:InvokeCommandAction CommandName="IncrementCommand" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Expression_Samples_PathListBoxUtils:PathListBoxScrollBehavior>
</i:Interaction.Behaviors>
</ec:PathListBox>

The Pathlistbox is using the style PLB_ItemStyle1 from the Resource Dictionary PathListBoxItemStyle_Dictionary1.xaml. This style is a generic PathListBox except for the following grid transform portion.

<Grid.RenderTransform >
    <CompositeTransform 
    ScaleY="{Binding Value, ElementName=scale_numericupdown, 
	Converter={StaticResource DivisionConverter}, ConverterParameter=10}" 
    ScaleX="{Binding Value, ElementName=scale_numericupdown, 
	Converter={StaticResource DivisionConverter}, ConverterParameter=10}" 
    Rotation= "{Binding Value, ElementName=Angle_numericupdown}"
    />
</Grid.RenderTransform>

This is designed based on the ideas given in the article electric beach Blend 4: About Path Layout, Part II by Christian Schormann about how to transform items in the PathListBox. These transforms enable the code to rotate or scale the items. The transforms also use the Division converter to divide the Scale NumericUpDown control value by 10.

Another challenge is to bind these transforms to the NumericUpDown controls. Since these dependency properties are generated through a template control, 2 dependency properties are created as given in the code below:

#region Dependency Properties

public static readonly DependencyProperty Item_AngleProperty =
DependencyProperty.Register("Item_Angle",
typeof(double),
typeof(PLB_T_C1),
new PropertyMetadata(null));
public static readonly DependencyProperty Item_ScaleProperty =
DependencyProperty.Register("Item_Scale",
typeof(double),
typeof(PLB_T_C1),
new PropertyMetadata(null));

#endregion

#region Public Properties

public double Item_Angle
{
get { return (double)GetValue(Item_AngleProperty); }
set { SetValue(Item_AngleProperty, value); }
}
public double Item_Scale
{
get { return (double)GetValue(Item_ScaleProperty); }
set { SetValue(Item_ScaleProperty, value); }
}

#endregion

The following 2 properties are declared and initialized by OnApplyTemplate(). This helps to get reference to the Pathlistbox control and the Canvas for further manipulations in the code.

public PathListBox plistbox;
public Canvas PLB_Canvas;
----
----
public override void OnApplyTemplate()
{
base.OnApplyTemplate();

PLB_Canvas = this.GetTemplateChild("PLB_Canvas") as Canvas;
foreach (var child in PLB_Canvas.Children)
     {
    if (child is PathListBox)
    {
    plistbox = child as PathListBox;
    }
     }
}

The DecrementCommand and IncrementCommand in the triggers are used to rotate the PrizeWheel.

The following resources are used for the audio files to be played during the Prize wheel rotation.

<Canvas.Resources>
<MediaElement x:Name="dingdingFile" Source="/audio/dingding.mp3" AutoPlay="False">
</MediaElement>
<MediaElement x:Name="lonloffFile" Source="/audio/lonloff.mp3" AutoPlay="False">
</MediaElement>
</Canvas.Resources> 

Path Load Class

The Path for the PathListBox is set to an ellipse. The height, width and angle of rotation of the ellipse can be adjusted. A block arrow is used to point to the winner.

The following code removes ellipse and block arrow if they exist.

public void Load_Path()
{
//Remove ellipse and block arrow if it exists
Remove_shapes_if_exist();
//Add ellipse
Add_Shapes();
el1.RenderTransform = rt;
}
private void Remove_shapes_if_exist()
{
Ellipse ellipse_toremove = null;
BlockArrow blockarrow_toremove = null;
foreach (var item in plb_t_c1.PLB_Canvas.Children)
{
var type_name = item.GetType();
if (type_name == typeof(Ellipse))
{
ellipse_toremove = item as Ellipse;
}

if (type_name == typeof(BlockArrow))
{
blockarrow_toremove = item as BlockArrow;
}
}

if (ellipse_toremove != null)
{
plb_t_c1.PLB_Canvas.Children.Remove(ellipse_toremove);
}

if (blockarrow_toremove != null)
{
plb_t_c1.PLB_Canvas.Children.Remove(blockarrow_toremove);
}
}

The ellipse and the Block Arrow are added with the following code. The events for height, width and angle numeric up down; fade and orientation checkboxes are also set up here.

private void Add_Shapes()
{
//ellipse
el1.Style = Application.Current.Resources["ellipse_style_0"] as Style;
plb_t_c1.PLB_Canvas.Children.Add(el1);
plb_t_c1.plistbox.LayoutPaths[0].SourceElement = el1;
//blockarrow
blockarrow1.Style = Application.Current.Resources["Left_Block_Arrow_Style"] as Style;
plb_t_c1.PLB_Canvas.Children.Add(blockarrow1); 
Locate_Ellipse();
//ellipse width, height changed events
cp_control1.Height_numericupdown.ValueChanged += 
new RoutedPropertyChangedEventHandler<double>(Height_numericupdown_ValueChanged);
cp_control1.Width_numericupdown.ValueChanged += 
new RoutedPropertyChangedEventHandler<double>(Width_numericupdown_ValueChanged); 
// ellipse angle binding
cp_control1.Ellipse_Angle_numericupdown.SetBinding(NumericUpDown.ValueProperty,
new Binding("Angle") { Source = rt, Mode = BindingMode.TwoWay });
rt.Angle = 90;
//Items Orientation
cp_control1.Orientation_CheckBox.Click += 
	new RoutedEventHandler(Orientation_CheckBox_Click);
//Items Fade
if (cp_control1.Fade_CheckBox.IsChecked == true)
{
plb_fade_class.Fade_Selected_Item(plb_t_c1.plistbox);
}
cp_control1.Fade_CheckBox.Click += new RoutedEventHandler(Fade_CheckBox_Click);
}

Orientation click event code is given below:

void Orientation_CheckBox_Click(object sender, RoutedEventArgs e)
{
if (cp_control1.Orientation_CheckBox.IsChecked == true)
{
plb_t_c1.plistbox.LayoutPaths[0].Orientation = 
Microsoft.Expression.Controls.Orientation.OrientToPath;
}
else
{
plb_t_c1.plistbox.LayoutPaths[0].Orientation = 
Microsoft.Expression.Controls.Orientation.None;
}
}

Height and width numericupdown control event code is given below. First the ellipse width and height are changed, and adjustments are made for the changes and applied to locate the ellipse and the block arrow at the correct locations.

private void Locate_Ellipse()
{
el1.Width = cp_control1.Width_numericupdown.Value;
el1.Height = cp_control1.Height_numericupdown.Value;
double leftoffset = 0;
double topoffset = 0;
double width = cp_control1.Width_numericupdown.Value;
double height = cp_control1.Height_numericupdown.Value;
double difference = Math.Abs(width - height);
if (width > height)
{
leftoffset = difference / 2;
topoffset = -difference / 2;
}
else
{
topoffset = difference / 2;
leftoffset = -difference / 2;
}
Canvas.SetTop(el1, leftoffset);
Canvas.SetLeft(el1, topoffset);
Locate_BlockArrow(leftoffset, topoffset);
}

private void Locate_BlockArrow(double leftoffset, double topoffset)
{
double blockarrow1_y = el1.Margin.Top + 
cp_control1.Height_numericupdown.Value / 2 + leftoffset - blockarrow1.Height / 2;
double blockarrow1_x = cp_control1.Width_numericupdown.Value + 
el1.Margin.Left + ((App)Application.Current).plb_item_width + topoffset * 2;
Canvas.SetTop(blockarrow1, blockarrow1_y );
Canvas.SetLeft(blockarrow1, blockarrow1_x);
}

Fade is an interesting topic and covered in a separate section below.

Data Load Class

This class has 2 observable collections, one for text and other for images.

ObservableCollection<TextBox> name_textbox_list = new ObservableCollection<TextBox>();
ObservableCollection<Image> image_list = new ObservableCollection<Image>();

If the user selects the Text button, text is loaded from the text file selected by the combo box.

 public void Load_Data()
{
if (cp_control1.Text_Button.IsChecked == true) Populate_Text_ListPanel();
if (cp_control1.Images_Button.IsChecked == true) Populate_Pictures_ListPanel();
if (cp_control1.Orientation_CheckBox.IsChecked == true)
{
plb_t_c1.plistbox.LayoutPaths[0].Orientation = 
	Microsoft.Expression.Controls.Orientation.OrientToPath;
}
else
{
plb_t_c1.plistbox.LayoutPaths[0].Orientation = 
	Microsoft.Expression.Controls.Orientation.None;
}
}
private void Populate_Text_ListPanel()
{
pathlistbox1.ItemsSource = null;
name_textbox_list.Clear();
GetData_Combo();
int ipickcolor = 0;
int iwidth = 0;
int iwidth_max = 15; //restrict to 15 characters
for (int itextcount = itextmincount; itextcount < name_list.Count; itextcount++)
{
TextBox text1 = new TextBox();
text1.FontSize = 16;
text1.Height = text1.FontSize * 2;
text1.TextAlignment = TextAlignment.Center;
text1.Foreground = new SolidColorBrush(Colors.Black); 
text1.Text = name_list[itextcount].name1;
if (text1.Text.Length > iwidth)
{
iwidth = text1.Text.Length;

}
System.Windows.Media.Color[] myColorArray = 
	{ Colors.Blue, Colors.Red, Colors.Green, Colors.Purple };
text1.Background = new SolidColorBrush(colorpicker.ColorName_Array[ipickcolor]);
ipickcolor++;
if (ipickcolor == (colorpicker.ColorName_Array.Length)) ipickcolor = 0;
name_textbox_list.Add(text1);
}
if (iwidth > iwidth_max)
{
iwidth = iwidth_max;
}
foreach (var item in name_textbox_list)
{
item.Width = item.FontSize + iwidth * item.FontSize / 2;
}
((App)Application.Current).plb_item_width =Convert.ToInt16(name_textbox_list[0].Width)/2;

itemheight = name_textbox_list[0].Height + 10;
pathlistbox1.ItemsSource = name_textbox_list;
// int start_item_index = (name_list.Count - (name_textbox_list.Count - 1) / 4);
int start_item_index = 0;
pathlistbox1.StartItemIndex = start_item_index;
pathlistbox1.SelectedIndex = 0;
}
 private void GetData_Combo()
{
try
{
string selected_file = "text_files/" + 
			cp_control1.Text_DropDownList.SelectedItem.ToString()+".txt";
name_list = Load_Names(selected_file);
}
catch (Exception ex)
{
string errorex = ex.Message.ToString();
}
}
public ObservableCollection<name> Load_Names(string fileinfo)
{
ObservableCollection<name> temp_list = new ObservableCollection<name>();
StreamResourceInfo f1 = Application.GetResourceStream(
new Uri(fileinfo,
UriKind.Relative));

StreamReader r = new StreamReader(f1.Stream);

using (r)
{
string line;
while ((line = r.ReadLine()) != null)
{
name name1 = new name();
name1.name1 = line;
temp_list.Add(name1);
}
}
r.Close();
return temp_list;
}

If Imagebutton is selected, images img0.jpg --- img15.jpg is loaded from the images folder.

 private void Populate_Pictures_ListPanel()
{
pathlistbox1.ItemsSource = null;

image_list.Clear();
for (int iimagecount = iimagemincount; iimagecount < iimagemaxcount; iimagecount++)
{
Image image1 = new Image();
image1.Width = 70;
image1.Height = 70;
image1.Source = GetImage("/images/img" + iimagecount.ToString() + ".jpg");
image1.Stretch = Stretch.Fill;
image_list.Add(image1);
}

pathlistbox1.ItemsSource = image_list;
itemwidth = image_list[0].Width + 10;
itemheight = image_list[0].Height + 10;
int start_item_index = 0;
pathlistbox1.StartItemIndex = start_item_index;
pathlistbox1.SelectedIndex = 0;
((App)Application.Current).plb_item_width = Convert.ToInt16(image_list[0].Width) / 2;
}

private ImageSource GetImage(string path)
{
return new BitmapImage(new Uri(path, UriKind.RelativeOrAbsolute));
}

Prize Class

The following code snippets show the main aspects of Prize class. Spin Timer is used to control the spinning of the prize wheel and the Tone Timer to control the sound. The following code initializes the Spin Timer to 200 millisecond ticks, starts it and then picks a random number. The steps count is set to random number + counts for 2 rotations and the wheel spinning tone is started. The PathListBox behavior collection duration is set to 200 milliseconds and the step size is set to 1.

 private void prize_start()
{
Prize_Winner_TextBlock.Text = "";
Prize_Winner_Image.Source = null;
plb_fade_class.unfade_all_items(plb_t_c1.plistbox);
plb_t_c1.plistbox.SelectedIndex = current_index;
Initialize_SpinTimer();
Spin_Timer.Start();
Random random_prize = new Random();
int randomstartnumber = plb_t_c1.plistbox.Items.Count * 2;
int random_prize_number = random_prize.Next(0, plb_t_c1.plistbox.Items.Count);
prize_next_number = randomstartnumber - random_prize_number;
pbsb.Amount = 1;
pbsb.Duration = TimeSpan.FromMilliseconds(200);

tone1.Play();
}
private void Initialize_SpinTimer()
{
Spin_Timer.Stop();
Spin_Timer.Interval = TimeSpan.FromMilliseconds(200);
tone1.Stop();
tone2.Stop();
}
 private void Prize_pathListBox_behavior_initialize()
{
behavior_collection = System.Windows.Interactivity.Interaction.GetBehaviors
			(plb_t_c1.plistbox);
pbsb = (Expression.Samples.PathListBoxUtils.PathListBoxScrollBehavior)
			behavior_collection[0];
} 

At every Spin Timer tick, the following code advances the PrizeWheel by 1 step, decrements the prize_next_number by 1. When prize_next_number reaches a count of 10, the timer and the Pathlistbox duration are changed to 400 milliseconds which makes the PrizeWheel slow down. When the prize_next_number is 0, the PrizeWheel is stopped, the second tone and the Tone Timer are started. The prize winner stack panel is populated with the winning name or image as the case may be.

 void Spin_Timer_Tick(object sender, EventArgs e)
{
if (prize_next_number == 10)
{
Spin_Timer.Interval = TimeSpan.FromMilliseconds(400);
pbsb.Duration = TimeSpan.FromMilliseconds(400);
}

if (prize_next_number > 0)
{
pbsb.DecrementCommand.Execute(null);
prize_next_number--;
current_index--;
if (current_index < 0) current_index = plb_t_c1.plistbox.Items.Count - 1;
plb_t_c1.plistbox.SelectedIndex = current_index;
if (cp_control1.Fade_CheckBox.IsChecked == true)
{
plb_fade_class.Fade_Selected_Item(plb_t_c1.plistbox);
}
}
else
{
Spin_Timer.Stop();
tone1.Stop();
tone2.Play();
Tone_Timer.Interval = TimeSpan.FromMilliseconds(1000);
Tone_Timer.Start();
if (cp_control1.Fade_CheckBox.IsChecked == true)
{
plb_fade_class.Fade_Selected_Item(plb_t_c1.plistbox);
}

if (cp_control1.Text_Button.IsChecked == true)
{
Prize_Winner_TextBlock.Text = (plb_t_c1.plistbox.SelectedItem as TextBox).Text;
}
if (cp_control1.Images_Button.IsChecked == true)
{
Prize_Winner_Image.Source = (plb_t_c1.plistbox.SelectedItem as Image).Source;
}
}
}

Fade Code

When Fade is selected, the item pointed to by the block arrow will be highlighted and rest of the items will be faded as shown below:

Start_Screen

This design is based on an excellent blog written by James in Coffeefueled.org Silverlight PathListBox Fading Unselected Items CoffeeFueled. As James had indicated, you have to fish for the PathListBoxItem grid using the following code:

 public List<Grid> GetChildGrid(DependencyObject parent)
{
List<Grid> children = new List<Grid>();
int count1 = VisualTreeHelper.GetChildrenCount(
VisualTreeHelper.GetChild(
parent, 0));
int count = VisualTreeHelper.GetChildrenCount(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
parent, 0), 0), 0), 0));
var test1 = VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(

parent, 0), 0), 0), 0);

for (int i = 0; i < count; i++)
{
children.Add((Grid)VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild(
VisualTreeHelper.GetChild( //PathListBoxItem
VisualTreeHelper.GetChild(
parent, 0), 0), 0), 0), i), 0));
}

return children;
}

There are 2 animations in the following code. The first animation fades all items except the selected one and the second one removes the fading from all items.

 public void FadeAnimation(Grid target, double timespan, double opacity)
{
DoubleAnimationUsingKeyFrames itemFade = new DoubleAnimationUsingKeyFrames();
itemFade.Duration = TimeSpan.FromSeconds(timespan);

Storyboard.SetTargetProperty(itemFade, new PropertyPath("(ec:Grid.Opacity)"));
Storyboard.SetTarget(itemFade, target);

EasingDoubleKeyFrame fadeFrame = new EasingDoubleKeyFrame();
fadeFrame.Value = opacity;
fadeFrame.KeyTime = TimeSpan.FromSeconds(timespan);

itemFade.KeyFrames.Add(fadeFrame);

Storyboard fade = new Storyboard();
fade.Children.Add(itemFade);

fade.Begin();
}
public void unfade_all_items(Microsoft.Expression.Controls.PathListBox Plb1_fade)
{
List<Grid> children = GetChildGrid(Plb1_fade);
if (children != null)
{
for (int i = 0; i < children.Count; i++)
{

FadeAnimation(children[i], 0.5, 1);
}
}
}

Help Control

This is based on Bringing a bit of HTML to Silverlight [HtmlTextBlock makes rich text display easy!] - Delay's Blog - Site Home - MSDN Blogs by David Anson. The HTMLTextBlock is used to bring some HTML to Silverlight. This technique helps in loading a regular HTML page in this custom TextBlock.

Create a HTMLTextBlock in the XAML.

<local:HtmlTextBlock x:Name="htmlTextBlock" 
Canvas.Left="2" 
Canvas.Top="2" 
Width="700" Height="700" 
TextWrapping="Wrap" 
UseDomAsParser="true" />

The HTMLPage1.htm is loaded using the following in the code behind.

 void Help_Control1_Loaded(object sender, RoutedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(this))

return;
string selected_file = "Help_Control/HTMLPage1.htm";
htmlTextBlock.Text = Load_HTML_file(selected_file);
}
public string Load_HTML_file(string fileinfo)
{
string htmlstring = "";
StreamResourceInfo f1 = Application.GetResourceStream(
new Uri(fileinfo,
UriKind.Relative));

StreamReader r = new StreamReader(f1.Stream);

using (r)
{
string line;
while ((line = r.ReadLine()) != null)
{
htmlstring += line;
}
}
r.Close();
return htmlstring;
}

UI

The UI uses several of the techniques described in references 11 to 14.

Points of Interest

This was a fun project. I think I have achieved a structure for the code which will help me to add more features. This is the first part of a series I plan to write on my adventures with PathListBox. In subsequent articles, I will cover how to use different paths for the PathListBox, create custom paths and some more path animations. Stay tuned!

References

  1. Silverlight Prize Wheel Animation Using Custom Circular ListBox Control - CodeProject
  2. PathListBox Prize Wheel Live Demo
  3. Electric beach » Blend 4: About Path Layout, Part II
  4. Bringing a bit of HTML to Silverlight [HtmlTextBlock makes rich text display easy!] - Delay's Blog - Site Home - MSDN Blogs
  5. Silverlight PathListBox Fading Unselected Items « CoffeeFueled.
  6. How to implement Template Binding in Silverlight Custom Control?
  7. Popular Baby Names
  8. Beginners Guide to Silverlight 4 PathListBox Control (Part I) - CodeProject
  9. An introduction to the PathListBox | .tutorials.pathlistbox | Microsoft Design .toolbox
  10. Silverlight Template Control « Johan's Blog
  11. NumericUpDown Control in Silverlight Toolkit | Ning Zhang's Blog
  12. A Glass Orb Button in Silverlight - CodeProject
  13. Colors in Silverlight: I need a bigger box of crayons! : The Official Microsoft Silverlight.NET Forums.
  14. Grouped Toggle Buttons.
  15. Beginners Guide to Silverlight 4 PathListBox Control (Part–I).
  16. HTML Table Of Contents Generator.

History

This is the first version of this article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here