沈陽的老師問Silverlight能否設(shè)計(jì)動(dòng)態(tài)網(wǎng)頁?有的老師也問過Silverlight如何和數(shù)據(jù)庫連接的問題。嚴(yán)格來說Silverlight是富客戶端的設(shè)計(jì)軟件,對(duì)服務(wù)器端的設(shè)計(jì)能力較差,比如數(shù)據(jù)庫的訪問,但是Silverlight4
的開發(fā)環(huán)境有 Blend 4和Visual Studio2010,兩個(gè)環(huán)境中設(shè)計(jì)的項(xiàng)目是共享的,這就為Silverlight的設(shè)計(jì)如虎添翼了,下面舉兩個(gè)例子(清華大學(xué)今年7月份準(zhǔn)備出版的書中有,去年出版的書中示例簡單一些)。
一、播放服務(wù)器端的視頻
圖2-4 自定義的Silverlight視頻播放器
這個(gè)實(shí)例設(shè)計(jì)了1個(gè)自定義的Silverlight視頻播放器(視頻源文件放在服務(wù)器端),如圖2-4所示,這個(gè)示例完全在Blend4中設(shè)計(jì),視頻放在服務(wù)器端的文件夾中,網(wǎng)上調(diào)試成功。示例設(shè)計(jì)的復(fù)雜一點(diǎn),當(dāng)然功能也強(qiáng),如果減少功能程序也會(huì)簡單。圖2-4上方是視頻播放控件MediaElement(me);下方是需要播放的視頻圖片,點(diǎn)擊可以選擇視頻源;中間有1個(gè)視頻播放控制面板,Silverlight視頻控件MediaElement有邊下載邊播放的功能,圖2-5中間有個(gè)播放進(jìn)度條,同時(shí)顯示下載進(jìn)度和播放進(jìn)度;進(jìn)度條上方的2個(gè)文本分別顯示下載緩沖比例和播放器的當(dāng)前狀態(tài),進(jìn)度條下方的文本框顯示視頻播放的時(shí)間進(jìn)度值;進(jìn)度條上方右側(cè)有1個(gè)三角形音量控制圖標(biāo),點(diǎn)擊就可以選擇音量,喇叭圖標(biāo)是“靜音”控制圖標(biāo),點(diǎn)擊1次變化1次,靜音時(shí)喇叭圖標(biāo)上出現(xiàn)1個(gè)紅色“×”;
圖2-5 自定義的Silverlight視頻播放器控制板
當(dāng)鼠標(biāo)懸浮在進(jìn)度條上時(shí),先出現(xiàn)文本(Callout控件)顯示,顯示進(jìn)度條當(dāng)前值對(duì)應(yīng)的視頻時(shí)間,只要鼠標(biāo)懸浮不動(dòng)就會(huì)出現(xiàn)1個(gè)視頻預(yù)覽窗口(也是MediaElement控件,名為preview),可以看到此時(shí)對(duì)應(yīng)的視頻畫面(反復(fù)播放當(dāng)前畫面2秒,僅幾幀);進(jìn)度條下方有“暫?!?、“播放”、“停止”、“重播”和“全屏”等控制圖標(biāo)。下面說明設(shè)計(jì)過程。
1、界面設(shè)計(jì)
新建1個(gè)“Silverlight應(yīng)用程序+網(wǎng)站”項(xiàng)目,名稱為SilverlightMediaPlayer,設(shè)置界面對(duì)中:改變“LayoutRoot”的布局類型為“ScrollViewer”,放入Canvas(canvas1),設(shè)置其背景和大小,設(shè)置“UserControl”的Width和Height屬性為自動(dòng)。
2、me控件屬性設(shè)置
“布局”中Width:448 Height:252,相當(dāng)于16:9。
“媒體”中Stretch:UniformToFill。me控件放置在Canvas布局控件canvas1中,這對(duì)me的位置和尺寸變化很重要。
3、音量控制設(shè)置
圖2-5右側(cè)的音量控制圖標(biāo)表面看是三角形的,實(shí)際上它是1個(gè)矩形ProgressBar控件(命名為volume),但其左上角部分用1個(gè)和背景顏色(黑色)一致的Path(用鋼筆工具手繪)覆蓋,看到的是三角形。volume和Path組合在grid3中。
volume控件屬性設(shè)置(“公共屬性”欄目中):
Maximum:1 Minimum:0
volume控件的Value屬性(默認(rèn)值改設(shè)為0.6,原來是0.5)采用數(shù)據(jù)綁定設(shè)置:在【屬性】面板的“公共屬性”欄中選Value屬性—鼠標(biāo)左鍵點(diǎn)擊右側(cè)的白色方塊(高級(jí)選項(xiàng)),在彈出的菜單中選擇“數(shù)據(jù)綁定”,這時(shí)出現(xiàn)“創(chuàng)建數(shù)據(jù)綁定”窗口,選擇“元素屬性”頁面,左側(cè)場景元素中選“me”控件,右側(cè)屬性中選Value,點(diǎn)“確定”鍵完成設(shè)置。me控件的音量將跟隨volume控件的Value變化。
喇叭圖標(biāo)由圖形組合而成,最后組合在Grid控件(mute)中,構(gòu)成靜音/播音反復(fù)控制,如果處于靜音喇叭圖標(biāo)上又多出紅色“×”(組合在grid5中),程序如下。
//音量控制
private void volume_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
//獲取ProgressBar控件對(duì)應(yīng)的音量值
this.me.Volume=e.GetPosition(this.volume).X/this.volume.ActualWidth;
}
//靜音控制
private void mute_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
this.me.IsMuted=!this.me.IsMuted;
if (this.me.IsMuted)
//如果是靜音,顯示紅色×
this.grid5.Visibility=Visibility.Visible;
else
this.grid5.Visibility=Visibility.Collapsed;
}
4、進(jìn)度條設(shè)置和預(yù)覽窗口
MediaElement控件本身有邊下載邊播放的功能,這里使用圖圖2-5中間的進(jìn)度條控件ProgressBar(名為progressbar)的進(jìn)度值顯示視頻下載進(jìn)度,同時(shí)又在其中附加了1個(gè)矩形控件(名為vernier,紅色)作為游標(biāo)用于顯示播放進(jìn)度,這2個(gè)控件組合放置在Canvas中(名為canvas2),canvas2的大小和progressbar一致,這樣方便游標(biāo)vernier的位置設(shè)置。當(dāng)點(diǎn)擊進(jìn)度條時(shí)視頻就會(huì)選擇從點(diǎn)擊處對(duì)應(yīng)的視頻位置開始播放。另外有3個(gè)文本框分別顯示下載的緩沖進(jìn)度(textblock1)、播放器當(dāng)前狀態(tài)(textblock2)和視頻播放時(shí)的時(shí)間進(jìn)度(textblock3)。
關(guān)于緩沖進(jìn)度:當(dāng)me控件的屬性Source被賦值后,自動(dòng)產(chǎn)生下載緩沖過程,下載緩沖時(shí)間屬性BufferingTime默認(rèn)值是00:00:05(5秒,在“媒體”屬性欄目中),此值的大小會(huì)影響視頻播放的平滑程度,當(dāng)然和網(wǎng)絡(luò)速度也有關(guān),本實(shí)例中設(shè)置500ms:
this.me.BufferingTime=TimeSpan.FromMilliseconds(500);
但實(shí)際的緩沖值在0-1之間變化,乘以100后相當(dāng)于百分比,1個(gè)100%應(yīng)當(dāng)對(duì)應(yīng)1個(gè)下載緩沖時(shí)間,1個(gè)視頻的下載可能要經(jīng)過若干個(gè)這樣的過程。而且當(dāng)1個(gè)緩沖完成時(shí)自動(dòng)產(chǎn)生BufferProgressChanged事件,緩沖的顯示正是在此事件的程序中設(shè)計(jì)的。
緩沖變化會(huì)使視頻文件下載進(jìn)度變化,而此變化也會(huì)產(chǎn)生DownloadProgressChanged事件,可以在其中編寫程序顯示下載進(jìn)度屬性DownloadProgress的變化值(0-1之間)。
關(guān)于播放器狀態(tài),MedaiElement控件在視頻下載和播放時(shí)有如下狀態(tài):
Buffering、Closed、Opening、Paused、Playing 或 Stopped。默認(rèn)值為Closed,這些狀態(tài)根據(jù)英文就能理解它的含義,不再解釋。狀態(tài)的顯示也是在此BufferProgressChanged事件程序中設(shè)計(jì)的。
圖2-4下方有5個(gè)圖形圖標(biāo),代表5個(gè)視頻,當(dāng)點(diǎn)擊時(shí)就會(huì)產(chǎn)生下載播放過程,下面以點(diǎn)擊左邊的圖標(biāo)“機(jī)場”為例,看如何編寫程序的。
//播放"機(jī)場"視頻
private void image1_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
//定時(shí)器停止,定時(shí)器的作用后面介紹
timer.Stop();
//下載進(jìn)度值回0
this.progressbar.Value=0;
//獲取當(dāng)前瀏覽位置定位(含網(wǎng)頁文件名稱),如:http://localhost:2277/Default.html
uri=System.Windows.Browser.HtmlPage.Document.DocumentUri.ToString();
//LastIndexOf("/"):搜尋定位中右邊最后1個(gè)"/"的位置,其左側(cè)是不含網(wǎng)頁文件的定位 //形成服務(wù)器視頻文件的位置信息
videoname=uri.Substring(0,uri.LastIndexOf("/")+1)+"video/video1.wmv";
//設(shè)置視頻播放控件的視頻源
this.me.Source=new Uri(videoname,UriKind.RelativeOrAbsolute);
//設(shè)置預(yù)覽視頻源,后面會(huì)介紹視頻預(yù)覽
this.preview.Source=this.me.Source;
this.preview.Stop();
//設(shè)置視頻文件打開后的事件
this.me.MediaOpened+=new RoutedEventHandler(meOpened);
//設(shè)置下載進(jìn)度變化事件
this.me.DownloadProgressChanged+=newRoutedEventHandler(meDownload);
//設(shè)置下載緩沖時(shí)間
this.me.BufferingTime=TimeSpan.FromMilliseconds(500);
//設(shè)置下載緩沖變化的事件
this.me.BufferingProgressChanged+=newRoutedEventHandler(me_BufferingProgressChanged);
}
//下載進(jìn)度顯示
private void meDownload(object sender,System.Windows.RoutedEventArgs e)
{
//DownloadProgress是下載進(jìn)度,變化范圍0-1
this.progressbar.Value=this.me.DownloadProgress*this.progressbar.Maximum;
}
//下載緩沖變化顯示
double bp=0;
private void me_BufferingProgressChanged(objectsender,RoutedEventArgs e){
//BufferingProgress:緩沖變化值0-1
bp=this.me.BufferingProgress*100;
this.textblock1.Text="下載緩沖進(jìn)度:"+bp.ToString()+"%";
this.textblock2.Text="播放器當(dāng)前狀態(tài):"+me.CurrentState.ToString();
}
//文件打開后定時(shí)器啟動(dòng)
private void meOpened(object sender, System.Windows.RoutedEventArgse)
{
timer.Start();
}
//當(dāng)點(diǎn)擊進(jìn)度條時(shí),選擇視頻播放位置
private void progressbar_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
this.me.Pause();
this.me.Position=TimeSpan.FromSeconds(currentlength);
this.me.Play();
}
在本機(jī)實(shí)際調(diào)試時(shí),可能看不到緩沖變化和播放狀態(tài),但是如果放到服務(wù)器上調(diào)試就會(huì)很明顯,本實(shí)例已經(jīng)在網(wǎng)絡(luò)服務(wù)器上調(diào)試驗(yàn)證。
以上的程序大多數(shù)代碼在前面已經(jīng)有了解釋,還有視頻預(yù)覽preview和定時(shí)器timer沒有涉及到。
Preview也是1個(gè)MediaElement控件(和邊框矩形設(shè)計(jì)一起組合在canvas2的grid4中),當(dāng)視頻已經(jīng)下載并播放時(shí),如果鼠標(biāo)懸停在進(jìn)度條上立刻顯示視頻對(duì)應(yīng)的時(shí)間,如果鼠標(biāo)停止不動(dòng)一段時(shí)間(比如2秒),會(huì)自動(dòng)彈出視頻預(yù)覽窗口,看到的是鼠標(biāo)懸停處對(duì)應(yīng)的視頻,此預(yù)覽只反復(fù)播放懸停處的幾幀視頻圖像。鼠標(biāo)離開或變化位置自動(dòng)停止,這點(diǎn)和目前網(wǎng)上看到的有的視頻播放器的效果是一樣的。
這里用到時(shí)間控制,由定時(shí)器來完成,程序如下:
private DispatcherTimer timer=new DispatcherTimer();
//定義變量,用于視頻源地址和文件位置信息變量
string uri,videoname;
public MainPage()
{
InitializeComponent();
//Esc鍵提示關(guān)閉
this.Esctext.Visibility=Visibility.Collapsed;
//進(jìn)度條當(dāng)前位置顯示文本框關(guān)閉
this.cp.Visibility=Visibility.Collapsed;
//預(yù)覽窗口關(guān)閉
this.grid4.Visibility=Visibility.Collapsed;
timer.Interval=TimeSpan.FromMilliseconds(500);
timer.Tick+=new EventHandler(timerarrive);
//設(shè)置視頻播放進(jìn)度游標(biāo)初始位置
Canvas.SetLeft(this.vernier,0);
Canvas.SetTop(this.vernier,0);
}
//定時(shí)器定時(shí)訪問程序
//視頻時(shí)間總長度和視頻進(jìn)度條當(dāng)前值變量
double melength=0,currentvalue;
//記憶鼠標(biāo)懸停位置
double movevalue=0;
//懸停不動(dòng)時(shí)間計(jì)數(shù)和預(yù)覽時(shí)間計(jì)數(shù)
double times=0,previewtimes=0;
//鼠標(biāo)移動(dòng)
bool move=false;
private void timerarrive(object sender,EventArgs e){
//獲取視頻時(shí)間總長度
melength=me.NaturalDuration.TimeSpan.TotalSeconds;
//視頻進(jìn)度條當(dāng)前值=視頻進(jìn)度條最大值*視頻當(dāng)前位置時(shí)間/視頻時(shí)間總長度
currentvalue=this.progressbar.ActualWidth*me.Position.TotalSeconds/melength;
//設(shè)置視頻進(jìn)度游標(biāo)位置
Canvas.SetLeft(this.vernier,currentvalue);
//獲取視頻當(dāng)前位置時(shí)間小時(shí)值、分值、秒值
int h=me.Position.Hours;
int m=me.Position.Minutes;
int s=me.Position.Seconds;
//下面使用了C#的條件運(yùn)算符<條件>?<滿足條件時(shí)>:<不滿足條件時(shí)>
this.textblock3.Text="播放時(shí)間進(jìn)度:"+(h<10?"0"+h.ToString():h.ToString())+":"
+(m<10? "0"+m.ToString():m.ToString())+":"
+(s<10? "0"+s.ToString():s.ToString());
//預(yù)覽視頻處理
if (times<4)
//計(jì)數(shù)控制+1(位置不變的次數(shù))
times++;
if(times==1){
//取當(dāng)前鼠標(biāo)X位置
movevalue=progressbarX;
}
//如果鼠標(biāo)滑動(dòng)在進(jìn)度條且X坐標(biāo)沒有變化,且計(jì)數(shù)達(dá)4次
if (move &&movevalue==progressbarX){
if (times==4){
//開啟預(yù)覽窗口
this.grid4.Visibility=Visibility.Visible;
//如果預(yù)覽窗口當(dāng)前處于播放狀態(tài)
if (this.preview.CurrentState==MediaElementState.Playing){
//預(yù)覽時(shí)間控制計(jì)數(shù)
previewtimes++;
//達(dá)到預(yù)覽時(shí)間
if (previewtimes==4)
//暫停預(yù)覽
this.preview.Pause();
}else{
//設(shè)置預(yù)覽視頻位置
this.preview.Position=current;
//啟動(dòng)視頻預(yù)覽,且設(shè)置預(yù)覽時(shí)間控制計(jì)數(shù)回0
this.preview.Play();
previewtimes=0;
}
}
}else{
//關(guān)閉預(yù)覽窗口,同時(shí)位置不變的次數(shù)計(jì)數(shù)回0
this.grid4.Visibility=Visibility.Collapsed;
times=0;
}
}
定時(shí)訪問程序中用到的一些變量,如progressbarX、move等,和下列程序有關(guān):
//定義變量,用于當(dāng)前位置時(shí)間值和鼠標(biāo)X坐標(biāo)
double currentlength,progressbarX;
//進(jìn)度條鼠標(biāo)懸停處的時(shí)間
TimeSpan current;
//鼠標(biāo)懸停在進(jìn)度條上方,進(jìn)度條當(dāng)前位置時(shí)間顯示
private void progressbar_MouseMove(object sender,System.Windows.Input.MouseEventArgs e)
{
//事件引發(fā)者是ProgressBar控件
ProgressBar pb=sender as ProgressBar;
//視頻時(shí)間總長度
melength=me.NaturalDuration.TimeSpan.TotalSeconds;
//進(jìn)度條當(dāng)前位置時(shí)間值 currentlength=melength*e.GetPosition(this.progressbar).X/this.progressbar.ActualWidth;
int h=(int)currentlength/3600;
int m=(int)((currentlength600)/60);
int s=(int)((currentlength600)`);
//設(shè)置視頻進(jìn)度游標(biāo)位置
Canvas.SetLeft(this.cp,e.GetPosition(this.progressbar).X-30);
Canvas.SetLeft(this.grid4,e.GetPosition(this.progressbar).X-54);
//當(dāng)前位置顯示使用Callout(cp)控件
this.cp.Visibility=Visibility.Visible;
this.cp.Content=(h<10?"0"+h.ToString():h.ToString())+":"
+(m<10? "0"+m.ToString():m.ToString())+":"
+(s<10? "0"+s.ToString():s.ToString());
current=TimeSpan.Parse(this.cp.Content.ToString());
move=true;
progressbarX=e.GetPosition(this.progressbar).X;
}
//鼠標(biāo)離開進(jìn)度條
private void progressbar_MouseLeave(object sender,System.Windows.Input.MouseEventArgs e)
{
//關(guān)閉預(yù)覽,關(guān)閉進(jìn)度條當(dāng)前位置時(shí)間顯示
this.grid4.Visibility=Visibility.Collapsed;
this.cp.Visibility=Visibility.Collapsed;
//停止鼠標(biāo)移動(dòng)
move=false;
//鼠標(biāo)懸停不動(dòng)時(shí)間計(jì)數(shù)置0,鼠標(biāo)懸停位置回0
times=0;
movevalue=0;
//預(yù)覽播放停止,預(yù)覽計(jì)數(shù)回0
this.preview.Stop();
previewtimes=0;
}
5、播放控制
對(duì)視頻的“暫?!薄ⅰ安シ拧?、“停止”、“重播”等控制圖標(biāo)采用了故事板動(dòng)畫,當(dāng)鼠標(biāo)懸停在這些圖標(biāo)上時(shí)顏色發(fā)生變化(本例變?yōu)榫G色),程序比較簡單:
//暫停按鈕控制
private void pause_MouseEnter(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard1.Begin();
}
private void pause_MouseLeave(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard1.Stop();
}
private void pause_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
this.me.Pause();
}
//播放按鈕控制
private void play_MouseEnter(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard2.Begin();
}
private void play_MouseLeave(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard2.Stop();
}
private void play_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
this.me.Play();
}
//停止按鈕控制
private void stop_MouseEnter(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard3.Begin();
}
private void stop_MouseLeave(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard3.Stop();
}
private void stop_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
this.me.Stop();
}
//重播按鈕控制
private void replay_MouseEnter(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard4.Begin();
}
private void replay_MouseLeave(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard4.Stop();
}
private void replay_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
this.me.Stop();
this.me.Play();
}
6、全屏播放控制
Silverlight本身有全屏播放的控制語句:
Application.Current.Host.Content.IsFullScreen=!Application.Current.Host.Content.IsFullScreen;
但是,此語句控制的是當(dāng)前顯示界面的全屏顯示,而我們只希望視頻部分全屏播放,本實(shí)例中編程先將視頻放到瀏覽器的“全屏”(瀏覽器菜單和工具條保留)大?。ǔ绦蛟O(shè)計(jì)指定按Esc鍵恢復(fù)原始大?。?,然后再使用上述語句放大到全屏,此語句默認(rèn)并提示按Esc取消全屏,這樣當(dāng)程序運(yùn)行時(shí)第一次按Esc鍵實(shí)際恢復(fù)到瀏覽器“全屏”,第二次再按Esc鍵恢復(fù)到原始大小,程序如下。
//全屏控制
private void fullscreen_MouseEnter(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard5.Begin();
}
private void fullscreen_MouseLeave(object sender,System.Windows.Input.MouseEventArgs e)
{
this.Storyboard5.Stop();
}
//定義變量,記憶初始值
double canvas1W,canvas1H,meW,meH,meLeft,meTop;
private void fullscreen_MouseLeftButtonDown(object sender,System.Windows.Input.MouseButtonEventArgs e)
{
//視頻播放時(shí)才能使用全屏操作
if (this.me.CurrentState==MediaElementState.Playing){
//記憶Canvas(me控件的容器)的原始大小
canvas1W=this.canvas1.ActualWidth;
canvas1H=this.canvas1.ActualHeight;
//記憶me控件的原始大小,以及me控件的原始位置
meW=this.me.ActualWidth;
meH=this.me.ActualHeight;
meLeft=Canvas.GetLeft(me);
meTop=Canvas.GetTop(me);
//布局控件放大到當(dāng)前應(yīng)用程序(在瀏覽器中)界面大小
this.canvas1.Width=Application.Current.Host.Content.ActualWidth;
this.canvas1.Height=Application.Current.Host.Content.ActualHeight;
//設(shè)置me控件層次到上層(20是界面元素個(gè)數(shù)最大值,實(shí)際沒有這么多)
Canvas.SetZIndex(me,20);
//視頻控件也放大到當(dāng)前應(yīng)用程序界面大小
this.me.Width=this.canvas1.Width;
this.me.Height=this.canvas1.Height;
//視頻控件位置設(shè)置
Canvas.SetLeft(me,0);
Canvas.SetTop(me,0);
//使用按Esc鍵提示,以及按Esc鍵提示框位置,并將提示框放置最上層
this.Esctext.Visibility=Visibility.Visible;
Canvas.SetLeft(Esctext,0);
Canvas.SetTop(Esctext,0);
Canvas.SetZIndex(Esctext,21);
//設(shè)置按Esc鍵事件(自定義)
Application.Current.RootVisual.KeyDown += newKeyEventHandler(ESC_Down);
//使用Silverlight的全屏控制Application.Current.Host.Content.IsFullScreen=!Application.Current.Host.Content.IsFullScreen;
}
}
private void ESC_Down(object sender, KeyEventArgs e)
{
//如果按下Esc鍵
if (e.Key.ToString()=="Escape"){
//Esc鍵提示框關(guān)閉,恢復(fù)布局原始大小
this.Esctext.Visibility=Visibility.Collapsed;
this.canvas1.Width=canvas1W;
this.canvas1.Height=canvas1H;
//恢復(fù)me控件原始大小,置于底層,并恢復(fù)原始位置
this.me.Width=meW;
this.me.Height=meH;
Canvas.SetZIndex(me,0);
Canvas.SetLeft(me,meLeft);
Canvas.SetTop(me,meTop);
}
二、Silverlight中利用SQL數(shù)據(jù)庫注冊(cè)登錄
本例采用的數(shù)據(jù)源是SQL數(shù)據(jù)庫,利用WCF通訊模式,具有更廣泛的意義。然而,在 Blend4中并不直接支持SQL和WCF通訊方式的設(shè)計(jì),要借助于Visual Studio 2010。
圖2-22 顯示服務(wù)器端SQL數(shù)據(jù)庫數(shù)據(jù)
圖2-23 數(shù)據(jù)表zhuce的結(jié)構(gòu)
本例首先在Blend4中建立帶網(wǎng)站的Silverlight應(yīng)用項(xiàng)目,設(shè)計(jì)用戶界面,注冊(cè)登錄界面與上例雷同,僅僅增加了1個(gè)DataGrid控件顯示來自SQL數(shù)據(jù)庫表的數(shù)據(jù),如圖2-22。
然后在VisualStudio 2010中設(shè)計(jì)SQL數(shù)據(jù)庫,創(chuàng)建服務(wù)器端和客戶端的WCF通訊,可以在此環(huán)境調(diào)試,也可以在Blend4環(huán)境中調(diào)試,也可以交叉調(diào)試,都是微軟的產(chǎn)品,相互融合的優(yōu)越性顯示出來了。
使用的SQL數(shù)據(jù)庫“yuangong.mdf”中有1個(gè)表zhuce,其結(jié)構(gòu)很簡單,如圖2-23所示。
下面介紹設(shè)計(jì)過程。
1、建立項(xiàng)目,設(shè)置DataGrid
(1)在 Blend4中新建1個(gè)項(xiàng)目“Silverlight應(yīng)用程序+網(wǎng)站”,項(xiàng)目名稱為“SilverlightLoginSQL”,這樣會(huì)自動(dòng)建立客戶端的文件夾“SilverlightLoginSQL”和服務(wù)器端的文件夾“SilverlightLoginSQLSite”。
(2)完成圖2-22左側(cè)的界面設(shè)計(jì)這和圖2-19、圖2-21雷同。
(2)設(shè)置DataGrid,以顯示SQL數(shù)據(jù)。
DataGrid不能直接顯示數(shù)據(jù)庫表數(shù)據(jù),用下面的辦法能夠達(dá)到目的。
首先在 Blend 4的【數(shù)據(jù)】面板中“新建示例數(shù)據(jù)”,修改其結(jié)構(gòu)和屬性如圖2-24所示。
圖2-24 新建示例數(shù)據(jù)
從【工具】面板的“資產(chǎn)”欄中將DataGrid控件(名為datagrid)拖入MainPage.xaml的【設(shè)計(jì)面板】,將圖2-24中的“Collection”拖到datagrid,這時(shí)DataGrid的“公共屬性”ItemsSource屬性被綁定到內(nèi)置的示例數(shù)據(jù)源,顯示其數(shù)據(jù),MainPage.xaml中的xaml腳本如下:
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumnBinding="{Binding id}" Header="id"/>
<sdk:DataGridTextColumnBinding="{Binding name}" Header="name"/>
<sdk:DataGridTextColumnBinding="{Binding password}" Header="password"/>
</sdk:DataGrid.Columns>
將此腳本修改為:
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumnBinding="{Binding id}" Header="序號(hào)"/>
<sdk:DataGridTextColumnBinding="{Binding name}" Header="用戶名"/>
<sdk:DataGridTextColumnBinding="{Binding password}" Header="密碼"/>
</sdk:DataGrid.Columns>
同時(shí)將DataGrid的ItemsSource屬性“重置”,取消和內(nèi)置示例數(shù)據(jù)源的綁定,DataGrid將只有表頭的漢字顯示,數(shù)據(jù)消失。程序中將ItemsSource屬性和SQL的數(shù)據(jù)綁定就會(huì)顯示SQL數(shù)據(jù)庫表的數(shù)據(jù)了。
2、建立數(shù)據(jù)庫
(1)在Blend 4中關(guān)閉設(shè)計(jì)的項(xiàng)目。在Visual Studio2010中打開此項(xiàng)目,先在“服務(wù)器資源管理器”窗口設(shè)計(jì)SQL數(shù)據(jù)庫“yuangong.mdf”和其中的表zhuce(過程略)。設(shè)計(jì)完成后“服務(wù)器資源管理器”窗口如圖2-25所示。
圖2-25 數(shù)據(jù)庫yuangong
選中此數(shù)據(jù)庫點(diǎn)鼠標(biāo)右鍵,在彈出的窗口選“修改連接”,出現(xiàn)圖2-26。
圖2-26 修改連接
選擇其中的“更改”按鈕,出現(xiàn)圖2-27。
圖2-27 更改數(shù)據(jù)源
更改數(shù)據(jù)源為“Microsoft SQLServer數(shù)據(jù)庫文件”,點(diǎn)“確定”鍵,這時(shí)彈出如圖2-26一樣的窗口,但原來的“刷新”按鈕變?yōu)椤盀g覽”,選擇數(shù)據(jù)庫“yuangong.mdf”(注:利用VisualStudio2010中SqlExpress建立的表結(jié)構(gòu)同圖2-23的數(shù)據(jù)庫“yuangong.mdf”在本例中位于:C:\Progress FileMicrosoft SQL Server\ MSSQL.1\ MSSQL\ Data中)。
3、建立數(shù)據(jù)庫連接
(1)建立數(shù)據(jù)庫連接也只能在Visual Studio中,利用“LINQ toSQL類”完成。選擇服務(wù)器端文件夾點(diǎn)鼠標(biāo)右鍵在彈出的快捷菜單中選擇“添加新項(xiàng)”,出現(xiàn)圖圖2-28。
圖2-28 選擇LINQ to SQL類
選擇其中的“LINQ toSQL類”,文件名可以重新設(shè)置(本例使用默認(rèn)名稱),點(diǎn)擊“添加”按鈕,在隨后出現(xiàn)的窗口選擇“是”,這時(shí)在服務(wù)器端文件夾增加文件夾“App_Code”,文件夾中放置了DataClasses.dbml文件,如圖2-29所示。
圖2-29 添加DataClasses.dbml文件后
圖2-30 DataClasses.dbml窗口
DataClasses.dbml中的文件只要打開查看,就能知道其中的功能。同時(shí),服務(wù)器端文件夾中又多了項(xiàng)目運(yùn)行時(shí)的重要的Web.config配置文件。這時(shí),DataClasses.dbml文件窗口已經(jīng)打開,將數(shù)據(jù)庫“yuangong.mdf”中的表“zhuce”拖進(jìn)此窗口,在隨后出現(xiàn)的窗口選擇“是”,這時(shí)DataClasses.dbml文件窗口出現(xiàn)了zhuce表的屬性窗口,如圖圖2-30所示。
另外,在服務(wù)器端文件夾中又增加了文件夾“App_Data”,其中放置的是“yuangong.mdf”數(shù)據(jù)庫(從C:\ProgressFile\ Microsoft SQL Server\ MSSQL.1\ MSSQLData中選擇此數(shù)據(jù)庫,將其復(fù)制到本項(xiàng)目中的文件夾App_Data中)。項(xiàng)目完成了和SQL數(shù)據(jù)庫的連接。
4、在服務(wù)器端創(chuàng)建WCF服務(wù)
WCF為客戶端和服務(wù)器端的數(shù)據(jù)通訊建立服務(wù),創(chuàng)建方法是:在服務(wù)器端選中“ClientBin”,點(diǎn)鼠標(biāo)右鍵,在彈出的快捷菜單中選擇“添加新項(xiàng)”,出現(xiàn)圖2-31窗口。
圖2-31 添加“啟用了Silverlight的WCF服務(wù)”
圖2-32 客戶端添加服務(wù)引用
名稱可以自選,本例采用了默認(rèn)名稱Service.svc。選擇“添加”鍵后,在“ClientBin”文件夾中出現(xiàn)“Service.svc”文件,在“App_Code”文件夾中出現(xiàn)“Service.cs”文件,這是WCF服務(wù)的代碼文件,我們主要在此文件中編寫服務(wù)器端的應(yīng)答通訊代碼。代碼的編寫后面介紹。建立后選擇菜單“生成”中的“重新生成解決方案”,以保證創(chuàng)建成功。在“設(shè)計(jì)基礎(chǔ)”一書的“4.12節(jié)”中對(duì)測試服務(wù)創(chuàng)建是否成功做了介紹,可以參照,這里不再介紹。
5、在客戶端添加服務(wù)引用
在客戶端添加服務(wù)引用可以允許客戶端程序引用在服務(wù)器端“Service.cs”文件中編寫的服務(wù)程序。在客戶端添加服務(wù)引用的方法是:選擇客戶端文件夾,點(diǎn)鼠標(biāo)右鍵,在彈出的快捷菜單中選擇“添加服務(wù)引用”,出現(xiàn)圖2-32窗口。圖2-32窗口剛彈出時(shí),在地址欄和服務(wù)欄可能都是空白,可以點(diǎn)擊右邊的“發(fā)現(xiàn)”按鈕,會(huì)自動(dòng)找到相應(yīng)的服務(wù)。本例命名空間名稱采用默認(rèn)的“ServiceReference1”,點(diǎn)擊“確定”按鈕,在客戶端的文件夾中添加了文件夾“ServiceReferences/ServiceReference1”,文件夾中有自動(dòng)產(chǎn)生的一批文件,不要改動(dòng)。
要提示的是:在“Service.cs”文件中編寫服務(wù)程序后,必須選擇“ServiceReference1”點(diǎn)鼠標(biāo)右鍵,在彈出的快捷菜單中選擇“更新服務(wù)引用”,這時(shí)服務(wù)程序才會(huì)出現(xiàn)在服務(wù)引用中供客戶端調(diào)用??梢杂孟旅娴姆椒ú榭矗哼x擇“ServiceReference1”文件夾,點(diǎn)鼠標(biāo)右鍵,在彈出的快捷菜單中選擇“在對(duì)象瀏覽器中查看”就會(huì)出現(xiàn)圖2-33。
圖2-33 服務(wù)引用瀏覽
在圖2-33中應(yīng)該有“Service.cs”文件中編寫的服務(wù)程序方法項(xiàng)名稱,如果沒有就需要進(jìn)行“更新服務(wù)引用”的操作。
6、Web.config文件設(shè)置修改
Web.config文件是Web項(xiàng)目運(yùn)行的配置文件,配置不正確不會(huì)順利運(yùn)行。原來的文件配置中有下面一段關(guān)于服務(wù)的描述:
<services>
<servicename="Service">
<endpoint address="" binding="customBinding"bindingConfiguration="Service.customBinding0"
contract="Service" />
<endpoint address="mex" binding="mexHttpBinding"contract="IMetadataExchange" />
</service>
上面得配置要做如下更改:
<services>
<servicename="Service">
<endpointaddress="" binding="basicHttpBinding" bindingConfiguration=""
contract="Service"/>
<endpointaddress="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
其中帶陰影的是改動(dòng)部分:
“binding="basicHttpBinding"”指使用HTTP協(xié)議。
“bindingConfiguration=""”指無需其它配置。
本例設(shè)計(jì)中使用的是HTTP通訊協(xié)議,有了上面的修改,WCF通訊服務(wù)就會(huì)正常了。
所有設(shè)置完成,下面介紹相關(guān)程序設(shè)計(jì)。
7、Service.cs文件服務(wù)程序
在此文件中設(shè)計(jì)可供客戶端引用或調(diào)用的服務(wù)程序,如查看SQL數(shù)據(jù)庫數(shù)據(jù)、添加記錄等等。本例此文件中有如下一些服務(wù)程序:獲取SQL數(shù)據(jù)庫表數(shù)據(jù)、用戶名校驗(yàn)、添加新用戶、密碼校驗(yàn)等。
//獲取SQL數(shù)據(jù)
[OperationContract]
publicList<zhuce> GetData()
{
//創(chuàng)建LINQ to SQL查詢對(duì)象
DataClassesDataContext dcdc = new DataClassesDataContext();
//建立LINQ查詢,選擇zhuce表的所有記錄
var sqltable = from dc in dcdc.zhuce select dc;
//返回?cái)?shù)據(jù)集
return sqltable.ToList<zhuce>();
}
//用戶名校驗(yàn)
[OperationContract]
public string CheckName(string nm)
{
//創(chuàng)建LINQ to SQL查詢對(duì)象
DataClassesDataContext dcdc = new DataClassesDataContext();
//建立LINQ查詢,選擇zhuce表的所有記錄
var check = from dc in dcdc.zhuce where dc.name == nm selectdc;
if (check.Count() != 0)
//返回客戶端找到信息
return "Yes";
else
//返回客戶端沒有找到信息
return "No";
}
//添加新用戶
[OperationContract]
public string Tianjia(string nms,string pass)
{
//創(chuàng)建DataClassesDataContext對(duì)象,用于記錄添加
DataClassesDataContext dcdc = newDataClassesDataContext();
var records = from dc in dcdc.zhuce select dc;
zhuce zhc = new zhuce()
{
id=records.Count()+1,
name = nms,
password = pass
};
//添加記錄
dcdc.zhuce.InsertOnSubmit(zhc);
dcdc.SubmitChanges();
return "注冊(cè)成功";
}
//密碼校驗(yàn)
[OperationContract]
public stringCheckPassword(string pw)
{
//創(chuàng)建LINQ to SQL查詢對(duì)象
DataClassesDataContext dcdc = new DataClassesDataContext();
//建立LINQ查詢,選擇zhuce表的所有記錄
var check = from dc in dcdc.zhuce where dc.password == pw selectdc;
if (check.Count() != 0)
return "Yes";//返回找到信息
else
return "No";//返回沒有找到信息
}
上面的代碼已經(jīng)做了注解,不再解釋。MainPage.xaml.cs文件的開始添加了命名空間引用,在后面的程序中有用:
using System.Windows.Browser;//for HtmlPage
using System.ServiceModel;//forBasicHttpBinding
下面是MainPage.xaml.cs文件中客戶端的程序,可以對(duì)照檢查。
//獲取服務(wù)器端SQL數(shù)據(jù)
string nm,pw1;
private void b2_Click(objectsender, System.Windows.RoutedEventArgs e)
{
//服務(wù)器端WCF服務(wù)定位
Uri uri = new Uri(Application.Current.Host.Source,"Service.svc");
//服務(wù)協(xié)議指定
BasicHttpBinding binding = newBasicHttpBinding(BasicHttpSecurityMode.None);
//創(chuàng)建WCF通訊
ServiceReference1.ServiceClient client = new
ServiceReference1.ServiceClient(binding, newEndpointAddress(uri));
//通訊完成事件處理
client.GetDataCompleted += (ss, se)=>
{
//如果通訊沒有出錯(cuò)
if (se.Error == null)
{
//DataGrid控件和通訊返回?cái)?shù)據(jù)、即SQL數(shù)據(jù)綁定
this.datagrid.ItemsSource= se.Result;
this.textblock1.Text ="通訊成功!";
}
else
{
HtmlPage.Window.Alert("WCF通訊出錯(cuò)!");
this.textblock1.Text = se.Error.Message;
return;
}
};
//引用服務(wù)獲取SQL數(shù)據(jù)庫表數(shù)據(jù),啟動(dòng)通訊
client.GetDataAsync();
client.CloseAsync();//關(guān)閉通訊
}
//注冊(cè)新用戶名校驗(yàn)
private void text1_LostFocus(object sender,System.Windows.RoutedEventArgs e)
{
nm=this.text1.Text.Trim();
int nmlength=nm.Length;
//漢字長度處理
nmlength=getlength(nm,nmlength);
if (nmlength<6){
System.Windows.MessageBox.Show("注冊(cè)用戶名長度不能少于6個(gè)字符!");
return;
}
Uri uri = newUri(Application.Current.Host.Source, "Service.svc");
BasicHttpBinding binding = newBasicHttpBinding(BasicHttpSecurityMode.None);
ServiceReference1.ServiceClient client = new
ServiceReference1.ServiceClient(binding, newEndpointAddress(uri));
client.CheckNameCompleted += (ss, se) =>
{
if (se.Error == null)
{
this.textblock1.Text ="通訊成功!";
if (se.Result=="Yes"){
vark=System.Windows.MessageBox.Show("注冊(cè)用戶名已經(jīng)存在,
請(qǐng)重新選擇!","確認(rèn)",MessageBoxButton.OKCancel);
if (k.ToString()=="OK")
this.text1.Focus();
}
}
else
{
HtmlPage.Window.Alert("WCF通訊出錯(cuò)!");
this.textblock1.Text = se.Error.Message;
return;
}
};
//引用服務(wù)校驗(yàn)用戶名,啟動(dòng)通訊
client.CheckNameAsync(nm);
client.CloseAsync();
}
//注冊(cè)新用戶
private void b1_Click(object sender,System.Windows.RoutedEventArgs e)
{
nm=this.text1.Text.Trim();
int nmlength=nm.Length;
//漢字長度處理
nmlength=getlength(nm,nmlength);
if (nmlength<6){
System.Windows.MessageBox.Show("注冊(cè)用戶名長度不能少于6個(gè)字符!");
return;
}
pw1=this.passwordbox1.Password.Trim();
string pw2=this.passwordbox2.Password.Trim();
if (pw1.Length<6){
System.Windows.MessageBox.Show("密碼長度不能少于6個(gè)字符!");
return;
}
if (!pw1.Equals(pw2)){
System.Windows.MessageBox.Show("兩個(gè)密碼不等!");
return;
}
Uri uri = newUri(Application.Current.Host.Source, "Service.svc");
BasicHttpBinding binding = newBasicHttpBinding(BasicHttpSecurityMode.None);
ServiceReference1.ServiceClient client = new
ServiceReference1.ServiceClient(binding, newEndpointAddress(uri));
client.TianjiaCompleted += (ss, se) =>
{
if(se.Error == null)
{
this.textblock1.Text ="通訊成功!";
HtmlPage.Window.Alert(se.Result);
}
else
{
HtmlPage.Window.Alert("WCF通訊出錯(cuò)!");
this.textblock1.Text = se.Error.Message;
return;
}
};
//啟用服務(wù)添加新用戶,啟動(dòng)通訊
client.TianjiaAsync(nm,pw1);
client.CloseAsync();
}
//登錄用戶名校驗(yàn)
private void text2_LostFocus(object sender,RoutedEventArgs e)
{
nm=this.text2.Text.Trim();
int nmlength=nm.Length;
//漢字長度處理,C#中使用的unicode編碼格式,默認(rèn)一個(gè)漢字為一個(gè)字符。
nmlength=getlength(nm,nmlength);
if (nmlength<6){
System.Windows.MessageBox.Show("登錄用戶名長度不能少于6個(gè)字符!");
return;
}
Uri uri = newUri(Application.Current.Host.Source, "Service.svc");
BasicHttpBinding binding = newBasicHttpBinding(BasicHttpSecurityMode.None);
ServiceReference1.ServiceClient client = new
ServiceReference1.ServiceClient(binding, newEndpointAddress(uri));
client.CheckNameCompleted += (ss, se) =>
{
if (se.Error == null)
{
this.textblock1.Text ="通訊成功!";
if (se.Result=="No"){
var k=System.Windows.MessageBox.Show("登錄用戶名不存在,
重新輸入?","確認(rèn)",MessageBoxButton.OKCancel);
if (k.ToString()=="OK")
this.text2.Focus();
}
}
else
{
HtmlPage.Window.Alert("WCF通訊出錯(cuò)!");
this.textblock1.Text = se.Error.Message;
return;
}
};
client.CheckNameAsync(nm);
client.CloseAsync();
}
這段程序中提到的漢字處理和前一個(gè)應(yīng)用實(shí)例的一樣,程序不再列出。
//用戶登錄
private void b3_Click(object sender, System.Windows.RoutedEventArgse)
{
nm=this.text2.Text.Trim();
int nmlength=nm.Length;
//漢字長度處理
nmlength=getlength(nm,nmlength);
if (nmlength<6){
System.Windows.MessageBox.Show("登錄用戶名長度不能少于6個(gè)字符!");
return;
}
pw1=this.passwordbox3.Password.Trim();
if (pw1.Length<6){
System.Windows.MessageBox.Show("密碼長度不能少于6個(gè)字符!");
return;
}
Uri uri = newUri(Application.Current.Host.Source, "Service.svc");
BasicHttpBinding binding = newBasicHttpBinding(BasicHttpSecurityMode.None);
ServiceReference1.ServiceClient client = new
ServiceReference1.ServiceClient(binding, newEndpointAddress(uri));
client.CheckPasswordCompleted += (ss, se) =>
{
if (se.Error == null)
{
this.textblock1.Text ="通訊成功!";
if (se.Result=="No"){
var k=System.Windows.MessageBox.Show("密碼出錯(cuò),重新輸入嗎?",
"確認(rèn)",MessageBoxButton.OKCancel);
if (k.ToString()=="OK"){
this.passwordbox3.SelectAll();
this.passwordbox3.Focus();
}
}else{
//寫Cookie
DateTime ex = DateTime.UtcNow + TimeSpan.FromDays(30);
stringcookiestring = String.Format("{0}={1};expires={2}","Login",
this.text2.Text,ex.ToString("R"));
System.Windows.Browser.HtmlPage.Document.SetProperty("cookie",cookiestring);
System.Windows.MessageBox.Show("登錄成功!");
this.Content=new Page1();
}
}
else
{
HtmlPage.Window.Alert("WCF通訊出錯(cuò)!");
this.textblock1.Text= se.Error.Message;
return;
}
};
//引用服務(wù)密碼校驗(yàn),啟動(dòng)通訊
client.CheckPasswordAsync(pw1);
client.CloseAsync(); }
用戶登錄成功,同樣將用戶名寫入Cookie。