Silverlight InkPresenter 实现路径回放的探索

简介:

从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 == 0return;
            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();
        }


本文转自 流牛木马 博客园博客,原文链接:http://www.cnblogs.com/azure/archive/2009/03/30/1425400.html,如需转载请自行联系原作者
相关文章
|
索引
一起谈.NET技术,Silverlight+WCF 新手实例 象棋 主界面-棋谱-回放-结局(四十)
查看本系列其他相关文章请点击:Silverlight+WCF 新手实例象棋专题索引 在线演示地址:Silverlight+WCF 新手实例 象棋 在线演示 在Silverlight+WCF 新手实例 象棋 主界面-棋谱-回放(三十九)中,我们实现了用户的棋谱回放,在文章的下面,我们曾留下了两...
944 0
Silverlight & Blend动画设计系列六:动画技巧(Animation Techniques)之对象与路径转化、波感特效
原文:Silverlight & Blend动画设计系列六:动画技巧(Animation Techniques)之对象与路径转化、波感特效   当我们在进行Silverlight & Blend进行动画设计的过程中,可能需要设计出很多效果不一的图形图像出来作为动画的基本组成元素。
1045 0
|
C# Windows
Silverlight & Blend动画设计系列十一:沿路径动画(Animation Along a Path)
原文:Silverlight & Blend动画设计系列十一:沿路径动画(Animation Along a Path)   Silverlight 提供一个好的动画基础,但缺少一种方便的方法沿任意几何路径对象进行动画处理。
940 0

热门文章

最新文章