★★★本博客欢迎转载,但请注明版权、原文链接,谢谢。
Memento..
My stories in my way..

从Silverlight从1.0版本开始,就提供了InkPresenter控件。很多人在第一次了解到这个控件时非常兴奋,纷纷打算做“手写识别”“网络共享白板”等应用程序。可惜Silverlight的InkPresenter只是WPF中InkPresenter的阉割版,想要扩展它不是说不可以,而是一件相当伤脑筋的麻烦事——至少,到目前为止,我都没有见到过真正的Silverlight网络共享白板。

博客园的webabcd的在4个月前的一篇文章的评论中,也说到了打算做Silverlight 网络白板的问题。

WXWinter(冬)在3个月前用Silverlight+WCF完成了一个“近似”的“网络共享白板”。(围观连接:http://www.cnblogs.com/foundation/archive/2008/12/02/1345506.html

为什么我说他是“近似”的呢?

这得先从InkPresenter的原理说起:用户用鼠标画在InkPresenter上的笔迹,都被保存为

StrokeCollection类型的inkPresenter.Strokes里。顾名思义,StrokeCollectionStroke
的集合。Stroke可以通俗地理解成“一笔”。而这“一笔”是“一条线”,它是包由很多“点”构成的,Stroke把关键的点(有转折的点)保存在Stroke.StylusPoints里,StylusPoint则是具体点每个点。

可惜的是,StylusPoint里除了X和Y坐标外,几乎没有提供任何可供编程的接口和方法,连Visible这种属性都没有提供。Stroke稍好一些,但也提供得不多。

WXWinter(冬)的“网络共享白板”是以Stroke为单位的。当用户画完“一整笔”后,Silverlight程序将描述这”一整笔”的Stroke通过WCF发送到服务器,同时通过Timer定时取得服务器上最新版本的所有Stroke

用过基于socket的“网络白板”的人都知道,WXWinter(冬)的方法只是一个近似的方法。第一,它没有真正的“实时”,这个问题不大,就算是不直接使用socket,Shareach也示范了使用WCF的解决方式第二,它的数据是以“线”为单位的,在实际使用时,对方看不到你画线的过程,只是会突然发现自己的屏幕上出现一条别人画的线,这是一件比较囧的事。

说了这么多,终于进入正题了:我最近一直在思考以上提到的第二个问题,如何直播或回放用户画线的过程,而不是让那些笔迹一整条一整条地跳出来呢?我认为,首先要把“点”从“线”中分离出来,对“点”编程而不是操作“线”;其次记录用户画每个点的准确时间;第三,使用动画。本文展示一个回放用户在InkPresenter上涂鸦过程的Demo。

效果图:

无标题

 

现场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
Get Microsoft Silverlight  

实现步骤草图:

1.InkPresenter的XAML代码及基本操作

  <InkPresenter Name='inkPresenter' Canvas.Left='10' Canvas.Top='10' 
                MouseLeftButtonDown
='onInkPresenterDown' MouseMove='onInkPresenterMove' MouseLeftButtonUp='onInkPresenterUp'>

       
<InkPresenter.Resources>
           
<Storyboard Duration="0:0:0" Completed="onStrokePlaybackTimerTick" x:Name="strokePlaybackTimer" />
       
</InkPresenter.Resources>
       
<MediaElement Name='mediaElement' Source="http://azuredrive.blob.core.windows.net/netdrive1/file_c6184705-b9f7-49e9-a2e9-1e76a01a4565.wmv" Width='720' Height='480' 
                  AutoPlay
='False'   MediaEnded="onMediaEnded"/>

   
</InkPresenter>


InkPresenter的基本操作请参考webabcd的这篇文章

2.用视频(或音频)的时间轴来作为涂鸦事件的时间轴,记录每一笔的开始时间
仔细看看上文的InkPresenter的XAML代码。它的Resources里是动画信息,它的内容仅仅是一个WMV媒体文件。我们之前提到了要保存每一个笔画的时间,就可以直接使用这个媒体文件的时间轴。

具体操作是这样的:

   if (isRecording)
           
{
               
//捕获鼠标
                inkPresenter.CaptureMouse();

                newStroke
= new Stroke();
               
//设置该笔画的属性。本Demo中全部使用默认属性。
                newStroke.DrawingAttributes = defaultDrawingAttributes;
               
//记录该笔的第一个点的信息
                newStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkPresenter));
                inkPresenter.Strokes.Add(newStroke);
               
//记录该笔第一个点画下的时间
                strokeStartTimes.Add(mediaElement.Position.Seconds);
            }

3.考虑到可能与服务器或其他网络用户的交互,我们用单一的string保存“点”的信息,
用int数组保存时间信息

  //用于保存每笔每个点的信息,分别用';'和','隔开
        string inkStringForPlayback = null;
       
//用于保存每笔的开始时间

        List<int> strokeStartTimes = new List<int>();

同时提供string与Strokes互相转换的两个函数:

private string ConvertInkToString(StrokeCollection strokes)
        {
           
           
string serializedStylusPoints = "";
            if (strokes != null)
            {
               
int strokeCount = strokes.Count;

                for (int i = 0; i < strokeCount; i++)
                {
                    Stroke stroke
= strokes[i];
                    
                    int packetCount = stroke.StylusPoints.Count;
                    for (int j = 0; j < packetCount; j++)
                    {
                        StylusPoint stylusPoint
= stroke.StylusPoints[j];
                        serializedStylusPoints += stylusPoint.X.ToString();
                        serializedStylusPoints += ",";
                        serializedStylusPoints += stylusPoint.Y.ToString();
                        if (j != packetCount - 1)
                        {
                            serializedStylusPoints
+= ",";
                        }
                       
else
                       
{
                            serializedStylusPoints
+= ";";
                        }

                    }

                }

            }

           
return serializedStylusPoints;
        }


     
  private StrokeCollection ConvertStringToInk(string serializedStylusPoints)
        {
            StrokeCollection strokes
= new StrokeCollection();
            string[] strokeStrings = serializedStylusPoints.Split(';');
            for (var i = 0; i < strokeStrings.Length - 1; i++)
            {
                Stroke stroke
= new Stroke();
                stroke.DrawingAttributes = defaultDrawingAttributes;
                string[] stylusPoints = strokeStrings[i].Split(',');
                for (var j = 0; j < stylusPoints.Length / 2; j++)
                {
                    StylusPoint stylusPoint
= new StylusPoint();
 
                    stylusPoint.X = double.Parse(stylusPoints[2 * j]);
                    stylusPoint.Y = double.Parse(stylusPoints[2 * j + 1]);
                    stroke.StylusPoints.Add(stylusPoint);
                }
                strokes.Add(stroke);
            }

           
return strokes;
        }


4.根据时间轴,动态画出每一笔、每个点。

private void onStrokePlaybackTimerTick(object sender,  EventArgs e)
       
{
           
if (strokesForPlayback.Count == 0) return;
            Stroke currentStroke
= strokesForPlayback[playbackStrokeIndex];
           
if (playbackPointIndex == 0)
           
{
               
if (mediaElement.Position.Seconds < strokeStartTimes[playbackStrokeIndex])
               
{
                    strokePlaybackTimer.Begin();
                   
return;
                }

                strokeToPlayback
= new Stroke();
                inkPresenter.Strokes.Add(strokeToPlayback);
                strokeToPlayback.DrawingAttributes
= currentStroke.DrawingAttributes;
            }

            strokeToPlayback.StylusPoints.Add(currentStroke.StylusPoints[playbackPointIndex]);
            playbackPointIndex
++;
           
if (playbackPointIndex < currentStroke.StylusPoints.Count)
           
{
               
                strokeToPlayback.StylusPoints.Add(currentStroke.StylusPoints[playbackPointIndex]);
                playbackPointIndex
++;
            }

           
if (playbackPointIndex == currentStroke.StylusPoints.Count)
           
{
                playbackPointIndex
= 0;
                playbackStrokeIndex
++;
               
if (playbackStrokeIndex == strokesForPlayback.Count)
               
{
                   
return;
                }

            }

            strokePlaybackTimer.Begin();
        }
posted on 2009-03-30 21:14  流牛木马  阅读(2677)  评论(4编辑  收藏  举报