從Silverlight從1.0版本開始,就提供了InkPresenter控件。很多人在第一次了解到這個控件時非常興奮,紛紛打算做“手寫識别”“網絡共享白闆”等應用程式。可惜Silverlight的InkPresenter隻是WPF中InkPresenter的閹割版,想要擴充它不是說不可以,而是一件相當傷腦筋的麻煩事——至少,到目前為止,我都沒有見到過真正的Silverlight網絡共享白闆。
webabcd的在4個月前的一篇文章的評論中,也說到了打算做Silverlight 網絡白闆的問題。
而WXWinter(冬)在3個月前用Silverlight+WCF完成了一個“近似”的“網絡共享白闆”。
為什麼我說他是“近似”的呢?
這得先從InkPresenter的原理說起:使用者用滑鼠畫在InkPresenter上的筆迹,都被儲存為
StrokeCollection類型的inkPresenter.Strokes裡。顧名思義,StrokeCollection是Stroke
的集合。Stroke可以通俗地了解成“一筆”。而這“一筆”是“一條線”,它是包由很多“點”構成的,Stroke把關鍵的點(有轉折的點)儲存在Stroke.StylusPoints裡,StylusPoint則是具體點每個點。
可惜的是,StylusPoint裡除了X和Y坐标外,幾乎沒有提供任何可供程式設計的接口和方法,連Visible這種屬性都沒有提供。Stroke稍好一些,但也提供得不多。
WXWinter(冬)的“網絡共享白闆”是以Stroke為機關的。當使用者畫完“一整筆”後,Silverlight程式将描述這”一整筆”的Stroke通過WCF發送到伺服器,同時通過Timer定時取得伺服器上最新版本的所有Stroke。
用過基于socket的“網絡白闆”的人都知道,WXWinter(冬)的方法隻是一個近似的方法。第一,它沒有真正的“實時”,這個問題不大,就算是不直接使用socket,Shareach也示範了使用WCF的解決方式;第二,它的資料是以“線”為機關的,在實際使用時,對方看不到你畫線的過程,隻是會突然發現自己的螢幕上出現一條别人畫的線,這是一件比較囧的事。
說了這麼多,終于進入正題了:我最近一直在思考以上提到的第二個問題,如何直播或回放使用者畫線的過程,而不是讓那些筆迹一整條一整條地跳出來呢?我認為,首先要把“點”從“線”中分離出來,對“點”程式設計而不是操作“線”;其次記錄使用者畫每個點的準确時間;第三,使用動畫。本文展示一個回放使用者在InkPresenter上塗鴉過程的Demo。
效果圖:
Silverlight InkPresenter 實作路徑回放的探索 現場Demo (需要安裝Silverlight 3.0控件,在這裡安裝:http://download.microsoft.com/download/0/D/7/0D76C405-E0E5-43CC-89D3-18243A4FCA86/Silverlight.3.0_Developer.exe )
【使用說明】
1.等待資料加載完,
2.點選“開始錄制”,
3.音樂響起,你可以随便塗寫.
4.畫完後點選“停止錄制”.
5.點選”回放預覽”檢視你的傑作。
(如果你看不到下面的Silverlight對象,可以到這裡檢視:http://azuredrive.blob.core.windows.net/netdrive1/file_98a1cf05-94e2-4918-a6ef-29e791c8e327.html)
Silverlight InkPresenter 實作路徑回放的探索 實作步驟草圖:
1.InkPresenter的XAML代碼及基本操作
Silverlight InkPresenter 實作路徑回放的探索 <InkPresenter Name='inkPresenter' Canvas.Left='10' Canvas.Top='10'
Silverlight InkPresenter 實作路徑回放的探索 MouseLeftButtonDown='onInkPresenterDown' MouseMove='onInkPresenterMove' MouseLeftButtonUp='onInkPresenterUp'>
Silverlight InkPresenter 實作路徑回放的探索 <InkPresenter.Resources>
Silverlight InkPresenter 實作路徑回放的探索 <Storyboard Duration="0:0:0" Completed="onStrokePlaybackTimerTick" x:Name="strokePlaybackTimer" />
Silverlight InkPresenter 實作路徑回放的探索 </InkPresenter.Resources>
Silverlight InkPresenter 實作路徑回放的探索 <MediaElement Name='mediaElement' Source="http://azuredrive.blob.core.windows.net/netdrive1/file_c6184705-b9f7-49e9-a2e9-1e76a01a4565.wmv" Width='720' Height='480'
Silverlight InkPresenter 實作路徑回放的探索 AutoPlay='False' MediaEnded="onMediaEnded"/>
Silverlight InkPresenter 實作路徑回放的探索 </InkPresenter>
InkPresenter的基本操作請參考webabcd的這篇文章。
2.用視訊(或音頻)的時間軸來作為塗鴉事件的時間軸,記錄每一筆的開始時間
仔細看看上文的InkPresenter的XAML代碼。它的Resources裡是動畫資訊,它的内容僅僅是一個WMV媒體檔案。我們之前提到了要儲存每一個筆畫的時間,就可以直接使用這個媒體檔案的時間軸。
具體操作是這樣的:
Silverlight InkPresenter 實作路徑回放的探索 if (isRecording)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 {
Silverlight InkPresenter 實作路徑回放的探索 //捕獲滑鼠
Silverlight InkPresenter 實作路徑回放的探索 inkPresenter.CaptureMouse();
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 newStroke = new Stroke();
Silverlight InkPresenter 實作路徑回放的探索 //設定該筆畫的屬性。本Demo中全部使用預設屬性。
Silverlight InkPresenter 實作路徑回放的探索 newStroke.DrawingAttributes = defaultDrawingAttributes;
Silverlight InkPresenter 實作路徑回放的探索 //記錄該筆的第一個點的資訊
Silverlight InkPresenter 實作路徑回放的探索 newStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkPresenter));
Silverlight InkPresenter 實作路徑回放的探索 inkPresenter.Strokes.Add(newStroke);
Silverlight InkPresenter 實作路徑回放的探索 //記錄該筆第一個點畫下的時間
Silverlight InkPresenter 實作路徑回放的探索 strokeStartTimes.Add(mediaElement.Position.Seconds);
Silverlight InkPresenter 實作路徑回放的探索 }
Silverlight InkPresenter 實作路徑回放的探索 //用于儲存每筆每個點的資訊,分别用';'和','隔開
Silverlight InkPresenter 實作路徑回放的探索 string inkStringForPlayback = null;
Silverlight InkPresenter 實作路徑回放的探索 //用于儲存每筆的開始時間
Silverlight InkPresenter 實作路徑回放的探索 List<int> strokeStartTimes = new List<int>();
Silverlight InkPresenter 實作路徑回放的探索 private string ConvertInkToString(StrokeCollection strokes)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 string serializedStylusPoints = "";
Silverlight InkPresenter 實作路徑回放的探索 if (strokes != null)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 int strokeCount = strokes.Count;
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 for (int i = 0; i < strokeCount; i++)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 Stroke stroke = strokes[i];
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 int packetCount = stroke.StylusPoints.Count;
Silverlight InkPresenter 實作路徑回放的探索 for (int j = 0; j < packetCount; j++)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 StylusPoint stylusPoint = stroke.StylusPoints[j];
Silverlight InkPresenter 實作路徑回放的探索 serializedStylusPoints += stylusPoint.X.ToString();
Silverlight InkPresenter 實作路徑回放的探索 serializedStylusPoints += ",";
Silverlight InkPresenter 實作路徑回放的探索 serializedStylusPoints += stylusPoint.Y.ToString();
Silverlight InkPresenter 實作路徑回放的探索 if (j != packetCount - 1)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 serializedStylusPoints += ",";
Silverlight InkPresenter 實作路徑回放的探索 }
Silverlight InkPresenter 實作路徑回放的探索 else
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 serializedStylusPoints += ";";
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 }
Silverlight InkPresenter 實作路徑回放的探索 }
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 return serializedStylusPoints;
Silverlight InkPresenter 實作路徑回放的探索 }
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 private StrokeCollection ConvertStringToInk(string serializedStylusPoints)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 StrokeCollection strokes = new StrokeCollection();
Silverlight InkPresenter 實作路徑回放的探索 string[] strokeStrings = serializedStylusPoints.Split(';');
Silverlight InkPresenter 實作路徑回放的探索 for (var i = 0; i < strokeStrings.Length - 1; i++)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 Stroke stroke = new Stroke();
Silverlight InkPresenter 實作路徑回放的探索 stroke.DrawingAttributes = defaultDrawingAttributes;
Silverlight InkPresenter 實作路徑回放的探索 string[] stylusPoints = strokeStrings[i].Split(',');
Silverlight InkPresenter 實作路徑回放的探索 for (var j = 0; j < stylusPoints.Length / 2; j++)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 StylusPoint stylusPoint = new StylusPoint();
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 stylusPoint.X = double.Parse(stylusPoints[2 * j]);
Silverlight InkPresenter 實作路徑回放的探索 stylusPoint.Y = double.Parse(stylusPoints[2 * j + 1]);
Silverlight InkPresenter 實作路徑回放的探索 stroke.StylusPoints.Add(stylusPoint);
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 strokes.Add(stroke);
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 return strokes;
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 private void onStrokePlaybackTimerTick(object sender, EventArgs e)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 if (strokesForPlayback.Count == 0) return;
Silverlight InkPresenter 實作路徑回放的探索 Stroke currentStroke = strokesForPlayback[playbackStrokeIndex];
Silverlight InkPresenter 實作路徑回放的探索 if (playbackPointIndex == 0)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 if (mediaElement.Position.Seconds < strokeStartTimes[playbackStrokeIndex])
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 strokePlaybackTimer.Begin();
Silverlight InkPresenter 實作路徑回放的探索 return;
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 strokeToPlayback = new Stroke();
Silverlight InkPresenter 實作路徑回放的探索 inkPresenter.Strokes.Add(strokeToPlayback);
Silverlight InkPresenter 實作路徑回放的探索 strokeToPlayback.DrawingAttributes = currentStroke.DrawingAttributes;
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 strokeToPlayback.StylusPoints.Add(currentStroke.StylusPoints[playbackPointIndex]);
Silverlight InkPresenter 實作路徑回放的探索 playbackPointIndex++;
Silverlight InkPresenter 實作路徑回放的探索 if (playbackPointIndex < currentStroke.StylusPoints.Count)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 strokeToPlayback.StylusPoints.Add(currentStroke.StylusPoints[playbackPointIndex]);
Silverlight InkPresenter 實作路徑回放的探索 playbackPointIndex++;
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 if (playbackPointIndex == currentStroke.StylusPoints.Count)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 playbackPointIndex = 0;
Silverlight InkPresenter 實作路徑回放的探索 playbackStrokeIndex++;
Silverlight InkPresenter 實作路徑回放的探索 if (playbackStrokeIndex == strokesForPlayback.Count)
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索
Silverlight InkPresenter 實作路徑回放的探索 strokePlaybackTimer.Begin();
Silverlight InkPresenter 實作路徑回放的探索