• InkPresenter 初探


    简介:

    InkPresenter 是 System.Windows.Controls 命名空间下的一个类,其主要是 实现  在界面上显示一个可以显示墨迹笔画的矩形图画。 

    InkPresenter 是派生自Canvas 他可以显示一个或多个UI元素和 StrokeCollection.

    InkPresenter 有两个重要属性,一个是Background可以设置图画的背景,一个是Children ,Children中的值将被呈现在InkPresenter图画上。

    使用InkPresenter可以实现像绘图板的效果,非常好玩,我简单实现了一个写字板的简单功能。

    功能如下:在界面上显示出一个画布,可以使用钢笔、标记笔、荧光笔在上面 写字画画什么的,

                  而且还有两个橡皮擦 一个是点橡皮擦,一个是线条橡皮擦。效果就如下图这样:

    要实现上面的功能首先要简单了解下面几个类:

        StylusPoint:表示在用户使用触笔或鼠标绘制墨迹笔画时收集的单个点。

        StylusPointCollection:表示StylusPoint的集合。

        Stroke:StylusPointCollection连续的点就可以表示为表示单个墨迹笔画,就是Stroke。

        StrokeCollection:表示一组Stroke。

    下面就是实现过程:

    1、首先绘制界面,最重要的是<InkPresenter/>标记,其他还有一些按钮和Textbox来触发事件、显示信息,xaml如下:

    View Code
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,12,12,0">
    <Grid.RowDefinitions>
    <RowDefinition Height="50"></RowDefinition>
    <RowDefinition Height="550"></RowDefinition>
    <RowDefinition Height="50"></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
    <ColumnDefinition Width="100*"></ColumnDefinition>
    <ColumnDefinition Width="610"></ColumnDefinition>
    <ColumnDefinition Width="100*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <TextBlock Grid.Column="1" Text="简单的写字板,可随便写,随便画,还有橡皮擦,哈哈哈" Name="stylusPointsInfo" VerticalAlignment="Top" HorizontalAlignment="Center" Width="471" Margin="62,25,77,0"></TextBlock>
    <Rectangle Grid.Row="1" Grid.Column="1" Width="610" Height="510" Stroke="Red"></Rectangle>
    <InkPresenter Grid.Row="1" Grid.Column="1" Background="White" x:Name="myInkPresenter" Height="500" Width="600"
    MouseLeftButtonDown
    ="myInkPresenter_MouseLeftButtonDown"
    LostMouseCapture
    ="myInkPresenter_LostMouseCapture"
    MouseLeftButtonUp
    ="myInkPresenter_MouseLeftButtonUp"
    MouseMove
    ="myInkPresenter_MouseMove"
    Opacity
    ="1"></InkPresenter>
    <Button Grid.Row="1" Content="标记" Name="markerButton" Click="markerButton_Click" Height="42" Width="80" Margin="118,238,3,270"></Button>
    <Button Grid.Row="1" Content="钢笔" Name="penButton" Click="penButton_Click" Width="80" Height="42" Margin="118,166,3,342"></Button>
    <Button Grid.Row="1" Content="荧光笔" Name="highlighterButton" Click="highlighterButton_Click" Width="80" Height="42" Margin="118,321,3,187"></Button>
    <Button Grid.Row="1" Grid.Column="2" Content="清空" Name="clearButton" Click="clearButton_Click" Height="42" Width="80" Margin="3,166,118,342"></Button>
    <Button Grid.Row="1" Grid.Column="2" Content="笔画橡皮" Name="eraserButton" Click="eraserButton_Click" Height="42" Width="80" Margin="3,238,118,270"></Button>
    <Button Grid.Row="1" Grid.Column="2" Content="点橡皮" Name="pointEraserButton" Click="pointEraserButton_Click" Height="42" Width="80" Margin="3,0,118,187" VerticalAlignment="Bottom"></Button>

    <HyperlinkButton Grid.Row="2" Grid.Column="1" Content="http://www.cnblogs.com/yinghuochong/" Height="24" HorizontalAlignment="Center" Name="hyperlinkButton1" Click="hyperlinkButton1_Click" VerticalAlignment="Top" Width="290" Margin="210,0,110,0" />
    <TextBlock Grid.Row="2" Grid.Column="1" Height="23" HorizontalAlignment="Center" Name="textBlock1" Text="萤火虫作品" VerticalAlignment="Top" Margin="142,1,384,0" Width="84" />
    </Grid>

    2、实现在界面上绘制线条,

         基本过程是,当鼠标左键按下的时候 记录下墨迹的点,并使用这些点 初始化一条笔画,然后将笔画绘制到InkPresenter 上,

                          当鼠标移动的时候,将所有经过的点加入到上面的笔画里。

                          当失去鼠标捕获的时候,将笔画对像清空,等待下一次鼠标按下。

    如下:

    鼠标按下事件 MouseLeftButtonDown:

    View Code
     private void myInkPresenter_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
    myInkPresenter.CaptureMouse();
    StylusPointCollection stylusPointCollection
    = new StylusPointCollection();
    stylusPointCollection.Add(e.StylusDevice.GetStylusPoints(myInkPresenter));
    newStroke
    = new Stroke(stylusPointCollection);
    //newStroke.DrawingAttributes = myDrawingAttributes;
    //这里要注意不能使用上面的方法直接将DrawingAttributes赋给Stroke这样会使整个InkPresenter上的线都变成该样式
    //我 在网上查到有一个DrawingAttributesChanged事件,应该是这个事件产生的影响
    //所以使用下面的 复制的方法就可以了
    newStroke.DrawingAttributes =CloneDrawingAttributes(myDrawingAttributes);
    //开始想的 当使用点橡皮的时候 直接在InkPresenter 上涂上背景色不就像擦除了吗,像下面这样
    //单上当你使用点橡皮 将一条线切开的时候 就会发现 再使用线橡皮擦的时候会出现问题的,所以下面是不合适的
    //应该使用将线条切开的方式
    //if (flag == "PointErase")
    //{
    // newStroke.DrawingAttributes.Color = Color.FromArgb(255,255,255, 255);
    // newStroke.DrawingAttributes.OutlineColor = Color.FromArgb(255, 255, 255, 255);
    //}
    myInkPresenter.Strokes.Add(newStroke);
    }

    上面的事件处理函数中有 Stroke对象有一个DrawingAttributes属性,该属性是对Stroke对象外观的描述,需要注意。

    MouseMove事件中,将经过的点加到笔画中,并且使用StylusPoint的X、Y、PressureFactor获取操作的一些信息:

    View Code
     private void myInkPresenter_MouseMove(object sender, MouseEventArgs e)
    {
    if (newStroke != null)
    {
    newStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(myInkPresenter));
    stylusPointsInfo.Text
    ="X坐标: "+ e.StylusDevice.GetStylusPoints(myInkPresenter)[0].X.ToString()
    + " Y坐标: "+e.StylusDevice.GetStylusPoints(myInkPresenter)[0].Y.ToString()
    +" 力度: "+e.StylusDevice.GetStylusPoints(myInkPresenter)[0].PressureFactor.ToString();
    }
    }

    LostMouseCapture事件将 全局的笔画对象清空就行了。

    这样就实现了线条的绘制了。

    3、实现画笔的切换

    画笔的切换主要就是修改Stroke的DrawingAttributes属性就可以了,我这里定义了三个按钮,单击时候修改他:

    View Code
     private void penButton_Click(object sender, RoutedEventArgs e)
    {
    myDrawingAttributes.Width
    = 1;
    myDrawingAttributes.Height
    = Double.Epsilon - 1;
    myDrawingAttributes.Color
    = Colors.Black;
    myDrawingAttributes.OutlineColor
    = Colors.Black;
    this.myInkPresenter.Cursor = Cursors.Stylus;
    }

    private void markerButton_Click(object sender, RoutedEventArgs e)
    {
    myDrawingAttributes.Width
    = 10;
    myDrawingAttributes.Height
    = 4;
    myDrawingAttributes.Color
    = Colors.Blue;
    myDrawingAttributes.OutlineColor
    = Colors.Blue;
    this.myInkPresenter.Cursor = Cursors.Stylus;
    }

    private void highlighterButton_Click(object sender, RoutedEventArgs e)
    {
    myDrawingAttributes.Width
    = 25;
    myDrawingAttributes.Height
    = 5;
    myDrawingAttributes.Color
    = Colors.Yellow;
    myDrawingAttributes.OutlineColor
    = Colors.Yellow;
    this.myInkPresenter.Cursor = Cursors.Stylus;
    }

    4、橡皮擦功能

        这个比较复杂 我设置了三种情况,为了区分画笔,我定义了一个 flag 用来标记是否是橡皮擦

         第一个是全部清空,只需将InkPresenter的Strokes   Clear以下就Ok了

         第二个是线条橡皮擦,主要原理是:在橡皮状态下当鼠标在InkPresenter中移动的时候如果与已经存在的笔画相交就将该笔画删除  判断是否相交是通过HitTest方法实现的,比较简单。在LeftMouseDown的时候 初始化一个StylusPointCollection对象erasePoints,在鼠标移动的时候记录下所有经过的点 并与已存在笔画进行HitTest 将相交的画线删除。

    View Code
    erasePoints.Add(e.StylusDevice.GetStylusPoints(myInkPresenter));
    StrokeCollection hitStrokes
    = myInkPresenter.Strokes.HitTest(erasePoints);
    if (hitStrokes.Count > 0)
    {
    foreach (Stroke hitStroke in hitStrokes)
    {
    myInkPresenter.Strokes.Remove(hitStroke);
    }
    }

          第三个是点橡皮擦,这个是最复杂的一个了,初我想的是橡皮擦擦的时候 将移动的轨迹重新用背景色画下不就行了?

    试了才知道这种方法可以,但是不太好,因为你再次选择笔画橡皮擦的时候即使线条已经被切断,还会将整条线擦除,因为整条线条根本就没改变。 基于此可以使用 当由某点经过画线时候将改点删除,并将改线切成两段,这样即实现了功能也不会出现上面的问题了。

    过程是:在鼠标按下的时候,这里需要注意的是在鼠标按下的这个点也可能需要处理,因为可能正好按在了某条线上,所以可以先将这个点保存起来,让他也让后面的处理过程处理;在鼠标移动的时候不断收集这些经过的点,并进行HitTest检测,若两条线相交了,就将这条线段从改点截成两段显示。这里定义了一个ProcessPointErase方法,该方法线从被截这条线的起始点开始直到与橡皮相交的点 构成一条线,然后从被截这条线的末尾开始到与橡皮相交的点 构成一条线,然后将原来的线移除,将这两条线绘制到界面上,ProcessPointErase方法  如下:

    View Code
     private void ProcessPointErase(Stroke stroke, StylusPointCollection pointErasePoints)
    {
    Stroke splitStroke1, splitStroke2, hitTestStroke;
    splitStroke1
    = new Stroke();
    hitTestStroke
    = new Stroke();
    hitTestStroke.StylusPoints.Add(stroke.StylusPoints);
    hitTestStroke.DrawingAttributes
    = stroke.DrawingAttributes;

    //通过以 stroke 上的第一个点为起始,并且将 stroke 上的每个触笔接触点添加到 splitStroke1,
    //直到到达与 pointErasePoints 相交的触笔接触点,确定相交。
    while (true)
    {
    StylusPoint sp
    =hitTestStroke.StylusPoints[0];
    hitTestStroke.StylusPoints.RemoveAt(
    0);
    if (!hitTestStroke.HitTest(pointErasePoints))
    {
    break;
    }
    splitStroke1.StylusPoints.Add(sp);
    }

    //通过以 stroke 上的最后一个点为起始,并且将每个触笔接触点添加到 splitStroke2,
    //直到到达与输入 pointErasePoints 相交的触笔接触点
    splitStroke2 = new Stroke();
    hitTestStroke
    = new Stroke();
    hitTestStroke.StylusPoints.Add(stroke.StylusPoints);
    hitTestStroke.DrawingAttributes
    = stroke.DrawingAttributes;
    while (true)
    {
    StylusPoint sp
    = hitTestStroke.StylusPoints[hitTestStroke.StylusPoints.Count-1];
    hitTestStroke.StylusPoints.RemoveAt(hitTestStroke.StylusPoints.Count
    - 1);
    if (!hitTestStroke.HitTest(pointErasePoints))
    {
    break;
    }
    splitStroke2.StylusPoints.Insert(
    0,sp);
    }
    //将原有的线删除,将拆分的两条线 绘制到屏幕
    if (splitStroke1.StylusPoints.Count > 1)
    {
    splitStroke1.DrawingAttributes
    = stroke.DrawingAttributes;
    myInkPresenter.Strokes.Add(splitStroke1);
    }
    if (splitStroke2.StylusPoints.Count > 1)
    {
    splitStroke2.DrawingAttributes
    = stroke.DrawingAttributes;
    myInkPresenter.Strokes.Add(splitStroke2);
    }
    myInkPresenter.Strokes.Remove(stroke);
    }

    程序代码在最下面。

    不明白的话可以去MSDN上看看 http://msdn.microsoft.com/zh-cn/library/dd233088(v=VS.95).aspx 我就是参考着做的。

    这样整个程序基本上就好了,然后就可以写写画画了,当然了放在Windows Phone上也很好玩,几乎不用改代码,看下图: 

    程序代码:InkPresenter.rar

  • 相关阅读:
    再战设计模式(九)之组合模式
    再战设计模式(八)之桥接模式
    再战设计模式(七)之代理模式
    nyoj 题目2 括号配对问题
    剑指offer 面试题38
    杭电 1005
    九度oj 题目1552:座位问题
    九度oj 题目1482:玛雅人的密码 清华大学机试
    九度oj 题目1496:数列区间
    九度oj 题目1495:关键点
  • 原文地址:https://www.cnblogs.com/yinghuochong/p/2175285.html
Copyright © 2020-2023  润新知