Path List Box 冒险 - 第 1 部分






4.83/5 (4投票s)
本文介绍了一个使用Silverlight Pathlistbox控件设计的抽奖轮盘。
目录
引言
去年我曾在CodeProject上发表过一篇关于使用自定义控件的Silverlight PrizeWheel的文章 使用自定义圆形ListBox控件实现的Silverlight奖品轮盘动画。本文介绍了一个使用Silverlight Pathlistbox
设计的类似奖品轮盘。
背景
Silverlight Pathlistbox
是一个令人兴奋的控件,它允许您使用ListBox
,但将项目定位在自定义路径上。本文介绍了一个用于Pathlistbox
的Silverlight模板控件,以及用于用户界面面板和帮助(作为HTML页面)的自定义控件。资源字典已用于模块化XAML代码。
输出
运行程序时,您会看到如下所示的页面。实时演示可在以下网站找到: PathListBox 奖品轮盘实时演示。
奖品轮盘可以用文本或图像数据加载。开始按钮会旋转奖品轮盘并随机选择一个项目作为获奖者。轮盘大小可以通过Width和Height的NumericUpDown控件更改(最小300,最大500,增量25,默认值400)。轮盘也可以通过Angle NumericUpDown
控件旋转(最小0,最大360,增量1,默认值90)。项目方向可以通过Orientation CheckListBox
更改,角度通过Angle NumericUpDown
控件(最小0,最大360,增量30,默认值270)更改,大小通过Scale NumericUpDownControl
(最小1,最大100,增量1,默认值10)更改。
代码描述
主页
MainPage
有2个选项卡:奖品选项卡和帮助选项卡。奖品选项卡包含一个Pathlistbox
模板控件和一个控件面板用户控件。帮助选项卡包含帮助用户控件。主页面还有一个单选按钮,用于选择控件面板位于左侧还是右侧。当更改此单选按钮的选择时,Grid的列定义会被更改,以便将控件面板放置在奖品控件的左侧或右侧。主页面加载后,会设置Initialize_Class1
。
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();
}
查看如何引用主页面的cp_control1
和plb_t_c1
来设置Initialize_class1
。此技术已被用于保持类的模块化,以便它们可以被重用。
初始化类
控件面板组合框使用此类中的文本文件名进行填充。图像和文本按钮的点击事件处理程序也在此处设置。当用户更改下拉组合框中的文本文件、文本或图像按钮时,PathListBox
的路径和数据会重新加载。控件面板项的角度和缩放的绑定也在此处初始化。在描述奖品轮盘控件时,我们将进一步讨论绑定。
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 });
}
奖品轮盘控件
这是一个模板控件,用于生成路径ListBox
以及一个用于显示获奖者的堆栈面板。模板控件的XAML文件位于Themes文件夹中的Generic.XAML文件中。此模板控件包含一个名为“PLB_Canvas
”的Canvas
,一个名为“pathListbox1
”的PathListBox
控件,一个用于显示获奖者的Stack Panel,以及几个媒体元素,用于在奖品轮盘旋转时产生音频效果。
PathListBox
控件是最有趣的部分,代码如下:
<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>
Pathlistbox
使用来自资源字典PathListBoxItemStyle_Dictionary1.xaml的样式PLB_ItemStyle1
。此样式是一个通用的PathListBox
,但以下网格变换部分有所不同。
<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>
这是基于Christian Schormann在文章 electric beach Blend 4: About Path Layout, Part II 中关于如何在PathListBox
中变换项目的想法设计的。这些变换使代码能够旋转或缩放项目。变换还使用Division转换器将Scale NumericUpDown
控件的值除以10
。
另一个挑战是将这些变换绑定到NumericUpDown
控件。由于这些依赖属性是通过模板控件生成的,因此创建了2个依赖属性,如下面的代码所示:
#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
以下2个属性在OnApplyTemplate()
中声明和初始化。这有助于获取对Pathlistbox
控件和Canvas
的引用,以便在代码中进行进一步的操作。
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;
}
}
}
触发器中的DecrementCommand
和IncrementCommand
用于旋转奖品轮盘。
以下资源用于在奖品轮盘旋转期间播放的音频文件:
<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>
路径加载类
PathListBox
的路径被设置为一个椭圆。椭圆的高度、宽度和旋转角度可以调整。使用一个块状箭头指向获奖者。
以下代码会在它们存在时删除椭圆和块状箭头。
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);
}
}
以下代码添加了椭圆和块状箭头。高度、宽度和角度数字增量框;淡入淡出和方向复选框的事件也在此处设置。
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);
}
方向点击事件代码如下:
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;
}
}
高度和宽度numericupdown
控件的事件代码如下:首先更改椭圆的宽度和高度,然后进行调整以使椭圆和块状箭头位于正确的位置。
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);
}
淡入淡出是一个有趣的主题,将在下面的单独部分中介绍。
数据加载类
此类包含2个可观察集合,一个用于文本,一个用于图像。
ObservableCollection<TextBox> name_textbox_list = new ObservableCollection<TextBox>();
ObservableCollection<Image> image_list = new ObservableCollection<Image>();
如果用户选择“文本”按钮,则从组合框选择的文本文件中加载文本。
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;
}
如果选择Imagebutton
,则从images文件夹加载images img0.jpg --- img15.jpg。
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
类的主要方面。Spin Timer用于控制奖品轮盘的旋转,Tone Timer用于控制声音。以下代码将Spin Timer初始化为200毫秒的滴答,启动它,然后选择一个随机数。步数设置为随机数+2次旋转的计数,并启动轮盘旋转音调。PathListBox
行为集合的持续时间设置为200毫秒,步长设置为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];
}
在每次Spin Timer滴答时,以下代码会将PrizeWheel
前进1步,并将prize_next_number
减去1
。当prize_next_number
达到10
时,计时器和Pathlistbox
的持续时间会更改为400毫秒,这会使PrizeWheel
减速。当prize_next_number
为0
时,PrizeWheel
停止,并启动第二个音调和Tone Timer。奖品获奖者堆栈面板会根据情况填充获胜者姓名或图像。
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;
}
}
}
淡入淡出代码
当选择淡入淡出时,块状箭头指向的项目将被高亮显示,其余项目将被淡出,如下所示:
此设计基于James在Coffeefueled.org 上发表的一篇优秀博客 Silverlight PathListBox Fading Unselected Items CoffeeFueled。正如James所指出的,您需要使用以下代码来查找PathListBoxItem
网格:
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;
}
以下代码中有2个动画。第一个动画淡出除选定项外的所有项,第二个动画则移除所有项的淡出效果。
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);
}
}
}
帮助控件
这是基于David Anson的文章 将HTML引入Silverlight [HtmlTextBlock使富文本显示变得容易!] - Delay's Blog - Site Home - MSDN Blogs。HTMLTextBlock
用于将一些HTML引入Silverlight。此技术有助于在此自定义TextBlock
中加载常规HTML页面。
在XAML中创建一个HTMLTextBlock
。
<local:HtmlTextBlock x:Name="htmlTextBlock"
Canvas.Left="2"
Canvas.Top="2"
Width="700" Height="700"
TextWrapping="Wrap"
UseDomAsParser="true" />
使用代码隐藏中的以下代码加载HTMLPage1.htm。
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
UI使用了参考11到14中描述的几项技术。
关注点
这是一个有趣的演示。我认为我已经实现了代码结构,这将有助于我添加更多功能。这是我计划撰写的关于我的PathListBox
冒险之旅系列的第一部分。在接下来的文章中,我将介绍如何为PathListBox
使用不同的路径,创建自定义路径以及更多路径动画。敬请关注!
参考文献
- 使用自定义圆形ListBox控件实现的Silverlight奖品轮盘动画 - CodeProject
- PathListBox 奖品轮盘实时演示
- Electric beach » Blend 4: 关于路径布局,第二部分
- 将HTML引入Silverlight [HtmlTextBlock使富文本显示变得容易!] - Delay's Blog - Site Home - MSDN Blogs
- Silverlight PathListBox 淡出未选中的项目 « CoffeeFueled.
- 如何在Silverlight自定义控件中实现模板绑定?
- 热门婴儿名字
- Silverlight 4 PathListBox控件入门指南(第一部分) - CodeProject
- PathListBox简介 | .tutorials.pathlistbox | Microsoft Design .toolbox
- Silverlight模板控件 « Johan's Blog
- Silverlight工具包中的NumericUpDown控件 | Ning Zhang's Blog
- Silverlight中的玻璃球按钮 - CodeProject
- Silverlight中的颜色:我需要更大的蜡笔盒!: Microsoft 官方Silverlight.NET论坛.
- 分组切换按钮.
- Silverlight 4 PathListBox 控件入门指南 (第一部分).
- HTML 目录生成器.
历史
这是本文的第一个版本。