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

原文地址:https://www.cnblogs.com/yinghuochong/p/2175285.html