序言:記得去年8月就開始接觸Silverlight 1.0了,那時候剛出來,接觸的人還不是很多,一位MVP講師朋友說國內精通的也就幾千人吧,因為自己對RIA一直也比較有興趣,所以學起來也很快,想不到MS終于放出了2.0,立馬給VS2008打上SP1的補丁,裝上sl2.0 RTW(SDK),裝上EB SP1...
正文:關于Sl2.0的一些新特性啥的我就不說了,直接切入正題,我之前在個人網站上的首頁(見http://www.ajaxplaza.net/)一直是用Ajax來實現的,效率低不說,CPU那個使用率...
所以今天的主要內容就是使用SL2.0來實現旋轉木馬的效果,最終效果圖如下(點我體驗):

導航分類會圍繞中心點做橢圓軌跡的旋轉,并且產生不同視覺效果(近點較大,遠點較小),那么首先對橢圓軌跡要有一個清晰的認識(x2/a2+y2/b2=1),我們沒有必要去求每個分類在橢圓上每個點的精確坐標值(x,y),使用x=sinα*a,y=cosα*b即可.這樣根據我們定義的橢圓長軸和短軸坐標可以很方便的計算分類對象當前運動軌跡的x軸坐標和y軸坐標.至于分類對象的大小則可以使用一個比例來進行縮放,基本原理就這么多,下面來看具體的實現步驟:
a) 數據提供
使用Xml就可以輕松的實現配置.Xml的結構如下:

WebDatas
1
xml version="1.0" encoding="utf-8" ?>
2
<WebDatas>
3
<WebData>
4
<UniqueId>UniqueId>
5
<ImageSrc>ImageSrc>
6
<Title>Title>
7
<link>link>
8
<linkContent>linkContent>
9
<Description>Description>
10
WebData>
11
WebDatas
WebDatas為根節點,WebData為每個分類對象,包括的屬性為:
UniqueId - 唯一標識
ImageSrc - 導航分類的展示圖片Url
Title - 每個分類的標題
Link - 每個分類鏈接Url
linkContent - 分類的實際顯示文字(比如錨的InnerText)
Description - 分類描述
b) Xaml生成
由于數據是由Xml讀取的,所以需要動態生成Xaml.
基礎知識:
1) Silverlight 2.0 讀取Xml
SL2提供了很多數據通信的方法,包括WCF,WebRequest,WebClient,Json等,那么這里就直接使用WebClient類來讀取一個Xml文件的內容:

Use WebClient
WebClient wc = new WebClient();
wc.DownloadStringAsync(new Uri(HtmlPage.Document.DocumentUri,"Data/Data.xml"));
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
首先創建一個WebClient的對象,并為其注冊DownloadStirngCompleted事件, 我會將數據文件放在宿主網站的Data目錄下,命名為Data.xml.使用HtmlPage.Document.DocumentUri來取得其Uri,并進行下載.

Code
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Cancelled == false && e.Error == null && e.Result != string.Empty)
{
XmlReader xr = XmlReader.Create(new StringReader(e.Result));
XDocument xd = XDocument.Load(xr);
var wdm = from f in xd.Descendants("WebData")
select new WebDataModel
{
Title = f.Element("Title").Value,
UniqueId = f.Element("UniqueId").Value,
Link = f.Element("link").Value,
ImgSrc = f.Element("ImageSrc").Value,
Description = f.Element("Description").Value,
LinkContent = f.Element("linkContent").Value
};
wdms = new List<WebDataModel>();
wdms.AddRange(wdm);
}
}
使用XDocument加載下載到的Xml文檔,其中WebDataModel是我定義的一個實體類,由LinqXml可以看出其基本結構(參照Xml定義),將結果以泛型保存(wdms對象).這樣就可以取得Xml中的數據了。
2)生成Xaml
首先簡述下導航分類對象包含的Xaml元素:
2.1 ControlTemplate - 用來承載所有的Xaml元素
2.2 Image 用來展示導航分類圖片
我們需要將二者組合成新的用戶控件來靈活使用,因此新建類,命名為MyImage,詳細代碼為:

MyImage UserControl
public class MyImage : Control


{
public MyImage(string xamlTemplate,string elementId)

{
this.elementId = elementId;
Template = (ControlTemplate)XamlReader.Load(string.Format(tx, xamlTemplate));
ApplyTemplate();
}
public override void OnApplyTemplate()

{
curImage = (Image)GetTemplateChild(elementId);
}
string tx = ""http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">{0}";
string elementId;

public string ElementId

{

get
{ return elementId; }

set
{ elementId = value; }
}
Image curImage = null;

public Image CurrentImage

{

get
{ return curImage; }

set
{ curImage = value; }
}
}
此舉主要是為了更方便的創建一個元素對象,并將其應用.
下面就可以開始創建用于展示的Image元素了:

Code
void CreateImages(List<WebDataModel> wdms,List<WebDataProperty> wdps)
{
string xaml = ""Stretch\" HorizontalAlignment=\"Stretch\" Tag=\"{2}|{3}|{4}|{5}|{6}|{7}\" Cursor=\"Hand\" Canvas.ZIndex=\"10\" x:Name=\"{0}\" Opacity=\"0.6\" Source=\"{1}\" >";
MyImage img = null;
Image curImg = null;
for (int i = 0; i < wdms.Count; i++)
{
img = new MyImage(string.Format(xaml, wdms[i].UniqueId, wdms[i].ImgSrc,wdms[i].Tag,wdms[i].Title,wdms[i].Link,wdms[i].ImgSrc,wdms[i].Description,wdms[i].LinkContent), wdms[i].UniqueId);
canvasRoot.Children.Add(img);
curImg = img.CurrentImage;
LocaleImages(curImg, wdps[i]);
}
}
WebDataProperty這個實體類主要是存儲Image元素的一些基本屬性,包括ZIndex,Width,Height,Left,Top等(詳細可見源代碼),將每個導航分類的附加信息(Title,ImageSrc等)使用"|"符號進行分割存儲在Tag屬性里,為了便于計算Image元素相對與橢圓圓心的位置,Tag第一位用于存儲當前弧度.使用LocaleImages方法來綁定當前導航分類位置及大小:

LocaleImages
void LocaleImages(Image imgTarget,WebDataProperty wdProp)
{
imgTarget.Margin = new Thickness(wdProp.Left, wdProp.Top, wdProp.Right, wdProp.Bottom);
imgTarget.Width = wdProp.CurrentWidth;
imgTarget.Height = wdProp.CurrentHeight;
Canvas.SetZIndex(imgTarget, wdProp.ZIndex);
}
那么其實關鍵的問題就是關于WebDataProperty的實例對象wdProp的屬性值的計算問題了,其實也不是什么難事,見代碼.

Ellipse
double width = Application.Current.Host.Content.ActualWidth;
double height = Application.Current.Host.Content.ActualHeight;
wdps = new List<WebDataProperty>();
WebDataProperty wdp = null;
int totalCount = wdms.Count;
double degree = 360 / totalCount;
double dg = 270;
double xMove =0;
double yMove = 0;
double centerX = (width-imgWidth)/2;
double centerY = (height - imgWidth) /3 ;
double xyRadius = 0;
double initX = centerX;
double initY = centerY;
double radiu = 0;
for (int i = 0; i < totalCount; i++)
{
radiu = dg * Math.PI / 180;
xMove = xRadius * Math.Cos(radiu);
yMove = yRadius * Math.Sin(radiu);
xyRadius = 1 - yMove / yRadius;
wdms[i].Tag = dg.ToString();
wdp = new WebDataProperty(totalCount - i, imgHeight * xyRadius, imgWidth * xyRadius, initX + xMove, initY - yMove, 0, 0);
wdps.Add(wdp);
dg=(dg+degree)%360;
}
那么這樣就可以比較方便的在UI上繪制出我們需要的元素并顯示出其位置和視覺效果.
還需要做的一件事就是使其旋轉,其實就是使用一個Timer來動態更新弧度值(取得Tag值中的第一個值,即我們存儲的弧度值,然后進行更新(++或--)再存儲).這樣所有的分類導航就會圍繞一個橢圓軌跡來運動了,當然可以加上其他的效果來實現更酷的UI(我使用了一個面板來顯示詳細的導航信息,以及一個綁定數據的ComboBox來快速導航).
總結:本文只是簡單的介紹了其原理,關于很多細節沒有仔細說明,請見諒.
下載源碼