InkPresenter,这个东西再熟悉不过,没错就是手写板,我们可以在它上边想怎么画怎么画,其实他的原理很简单,就是捕捉鼠标的轨迹,然后使用指定的颜色和宽度组成线条,然后不停的记录和显示。
先看个效果
就是一个简单的手写(鼠标).
下面开始详细介绍InkPresenter的使用。
首先看下要使用到的类,以及方法:
1.InkPresenter,InkPresenter成为墨迹控件,继承自Canvas类,它不单单是一个控件,而是一个可以接收SL中输入的接口;
主要属性Strokes,一个集合(StrokeCollection),表示要显示的画笔。
2.Stroke,表示单个墨迹画笔,属性DrawingAttributes表示当前画笔的属性,指定 Stroke 的外观。StylusPoints(StylusPointCollection类型集合),返回Stroke的触笔
接触点。
DrawingAttributes属性如下:
名称 | 说明 | |
---|---|---|
Color | 获取或设置 Stroke 的颜色。 | |
FitToCurve | 获取或设置一个值,该值指示是否使用贝塞尔曲线平滑法来呈现 Stroke。 | |
Height | 获取或设置用于绘制 Stroke 的触笔的高度。 | |
IgnorePressure | 获取或设置一个值,该值指示呈现的 Stroke 的粗细是否会随应用的压力而更改。 | |
IsHighlighter | 获取或设置一个值,该值指示 Stroke 看起来是否像一支荧光笔。 | |
StylusTip | 获取或设置用于绘制 Stroke 的触笔的形状。 | |
StylusTipTransform | 获取或设置 Matrix,它指定要在触笔笔尖上执行的变换。 | |
Width | 获取或设置用于绘制 Stroke 的触笔的宽度。 |
有了以上两个类,就可以完成墨迹控件的使用,下面开始看看如何在案例中使用:
<InkPresenter x:Name="inkTest" Height="400" Width="600"
Background="AliceBlue"
MouseMove="inkTest_MouseMove"
MouseLeftButtonDown="inkTest_MouseLeftButtonDown"
MouseLeftButtonUp="inkTest_MouseLeftButtonUp">
</InkPresenter>
可以看到仅仅一个标签就完成了一个InkPresenter的定义,在这里有一点要注意,有些童鞋发现拉过来一个控件之后,包括鼠标的事件代码都写了,可是就是不能画,首先
要确保当前的InkPresenter是否定义了BackGround(或者给InkPresenter添加了其他的元素),其次看看画笔的颜色问题。
下面看下在Inkpresenter中嵌套一个元素:
<InkPresenter x:Name="inkTest" Height="400" Width="600"
MouseMove="inkTest_MouseMove"
MouseLeftButtonDown="inkTest_MouseLeftButtonDown"
MouseLeftButtonUp="inkTest_MouseLeftButtonUp">
<Rectangle RadiusX="15" RadiusY="15" Margin="5" Height="400" Width="600">
<Rectangle.Fill>
<ImageBrush ImageSource="Chrysanthemum.jpg" Opacity="0.5" ></ImageBrush>
</Rectangle.Fill>
</Rectangle>
</InkPresenter>
可以看到在InkPresenter中嵌套了一个Rectangle,同时指定Rectangle的背景图片,这样就可以在这个图片上进行画东西了,效果也就是本文开头的那副图片。
大家可能注意到了在Ink上定义了三个鼠标事件,没错这个事件也是重点。
MouseMove事件用于记录鼠标在移动的过程中将轨迹写入到Stroke的StylusPoints集合中去;
MouseLeftButtonDown事件,用于捕获鼠标的坐标同时记录一个新的Stroke(新的画笔)开始,标识一个状态,以便在Move事件中进行点的记录;
MouseLeftButtonUp事件用于释放当前的鼠标捕获,同时释放Stroke,标识当前的Stroke已经结束;
UIElement.CaptureMouse()方法用于鼠标捕获当前的元素;
UIElement.ReleaseMouseCapture()方法,如果该元素具有鼠标捕获,则释放鼠标捕获。
从这里开始,编写后台代码:
Color currentColor = Colors.Black;//定义默认颜色
Stroke newStroke;//定义全局的画笔,用于在MouseDown中实例化,同时在Up中清空该对象
IsolatedStorageSettings setting = IsolatedStorageSettings.ApplicationSettings;//定义独立缓存,用于保存
//Ink鼠标移动事件
private void inkTest_MouseMove(object sender, MouseEventArgs e)
{
//如果不为空则说明已经按下了鼠标
if (newStroke != null)
{
//将鼠标移动中的点的轨迹添加到Stroke的StylusPoints
newStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkTest));
}
}
//Ink鼠标左键按下事件
private void inkTest_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//捕获鼠标(必须的)
inkTest.CaptureMouse();
//实例化画笔
newStroke = new Stroke();
//设置画笔颜色
newStroke.DrawingAttributes.Color = currentColor;
//指定轮廓的颜色
newStroke.DrawingAttributes.OutlineColor = Colors.Black;
//将鼠标点下的点添加到画笔中区
newStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkTest));
//将画笔添加到InkPresenter的Strokes(画笔集合)
inkTest.Strokes.Add(newStroke);
}
//Ink鼠标左键松开事件
private void inkTest_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
newStroke = null;
//释放鼠标捕获
inkTest.ReleaseMouseCapture();
}
以上代码已经完成了,画图的显示,其实整个原理很简单
在鼠标左键按下时候开始进行鼠标的捕获,同时创建Stroke(画笔),并且将点放到Stroke的StylusPoints中,在此还可以设置Stroke的DrawingAttributes,然后将Stroke添加
到Ink的Strokes中去。
在鼠标的移动事件中,记录这些鼠标经过的点,添加到Stroked的StylusPoints中去。
在鼠标左键放开事件中的任务很简单就是将Stroke对象清空,保证Move事件中不会对空对象(未按下左键)进行操作,同时释放鼠标的捕获.
下面开始进行Ink的保存工作,保存Ink的原理也很简单,将Xaml代码通过程序转换成Xml的形式,保存的最终是将xml文件保存到独立存储中
private void SaveInk()
{
XElement element = ConvertStrokeToString(inkTest.Strokes);
using (IsolatedStorageFile stroage = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream fs = stroage.CreateFile("Ink.xml"))
{
using (StreamWriter sw = new StreamWriter(fs))
{
sw.WriteLine(element);
}
}
}
HtmlPage.Window.Alert("保存成功!");
}
该方法传递一个StrokeCollection 类型的参数,也就是Ink的Strokes属性,通过循环处理形成Xml节点
private XElement ConvertStrokeToString(StrokeCollection originStrokes)
{
//添加命名空间
string xmlnsString = "";
XNamespace xmls = xmlnsString;
XElement XStroke = new XElement(xmls + "StrokeCollection", new XAttribute("xmlns", xmlnsString));
//创建笔画
XElement mystroke;
//遍历当前Ink上的所有Stroke
foreach (Stroke item in originStrokes)
{
//实例化XElement对象,并且把Stroke的属性一一对应放到XElement节点中去
mystroke = new XElement(xmls + "Stroke",
new XElement(xmls + "Stroke.DrawingAttributes",
new XElement(xmls + "DrawingAttributes",
new XAttribute("Color", item.DrawingAttributes.Color),
new XAttribute("OutlineColor", item.DrawingAttributes.OutlineColor),
new XAttribute("Width", item.DrawingAttributes.Width),
new XAttribute("Height", item.DrawingAttributes.Height))));
//定义StylusPoint节点
XElement mypoints = new XElement(xmls + "Stroke.StylusPoints");
//遍历Stroke的StylusPoints
foreach (StylusPoint sp in item.StylusPoints)
{
XElement mypoint = new XElement(xmls + "StylusPoint",
new XAttribute("X", sp.X),
new XAttribute("Y", sp.Y));
mypoints.Add(mypoint);
}
//StylusPoint节点添加到Stroke节点中
mystroke.Add(mypoints);
//将Stroke节点添加到根节点中
XStroke.Add(mystroke);
}
return XStroke;
}
下面这个方法用于加载Xml(从独立存储中),将Xml转换为Ink所需的对象,返回Ink的Strokes对象所需的StrokeCollection类型
private StrokeCollection ConvertStringToStroke(string xmlName)
{
StrokeCollection strokeCollection = new StrokeCollection();
XNamespace xmlnsString = "";
//使用独立存储来得到保存的Ink文件xml
using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
{
//打开指定的文件
using (IsolatedStorageFileStream fs = storage.OpenFile(xmlName, FileMode.Open, FileAccess.Read))
{
//得到读取流
using (StreamReader sr = new StreamReader(fs))
{
string content = sr.ReadToEnd();
//加载xml
XElement document = XElement.Parse(content);
//得到所有的Stroke标签
var strokes = from stroke in document.Elements(xmlnsString + "Stroke") select stroke;
//遍历Stroke节点
foreach (XElement item in strokes)
{
//得到DrawingAttribute节点
XElement drawingAttribute = item.Element(xmlnsString + "Stroke.DrawingAttributes").Element(xmlnsString + "DrawingAttributes");
//实例化Stroke
Stroke newStroke = new Stroke();
//实例化Stroke的StylusPoint
StylusPoint point = new StylusPoint();
//实例化 StylusPointCollection
StylusPointCollection pointCollection = new StylusPointCollection();
//实例化DrawingAttributes
DrawingAttributes attribute = new DrawingAttributes();
//赋值给DrawingAttributes对象
attribute.Color = ReturnColorFromString(drawingAttribute.Attributes("Color").First().Value);
attribute.OutlineColor = ReturnColorFromString(drawingAttribute.Attributes("OutlineColor").First().Value);
attribute.Width = Convert.ToDouble(drawingAttribute.Attributes("Width").First().Value);
attribute.Height = Convert.ToDouble(drawingAttribute.Attributes("Height").First().Value);
newStroke.DrawingAttributes = attribute;
//得到StylusPointCollection集合
var styluspoints = from p in item.Element(xmlnsString + "Stroke.StylusPoints").Elements(xmlnsString + "StylusPoint") select p;
//遍历StylusPointCollection节点
foreach (XElement pointElement in styluspoints)
{
point.X = Convert.ToDouble(pointElement.Attribute("X").Value);
point.Y = Convert.ToDouble(pointElement.Attribute("Y").Value);
pointCollection.Add(point);
}
//赋值给Stroke的StylusPoints
newStroke.StylusPoints = pointCollection;
//将Stroke添加到strokeCollection
strokeCollection.Add(newStroke);
}
}
}
}
return strokeCollection;
}
由于需要保存颜色则写了自定义方法:
将RGB码转换为Color类型对象
public Color ReturnColorFromString(string color)
{
string alpha = color.Substring(1, 2);
string red = color.Substring(3, 2);
string green = color.Substring(5, 2);
string blue = color.Substring(7, 2);
byte alphaByte = Convert.ToByte(alpha, 16);
byte redByte = Convert.ToByte(red, 16);
byte greenByte = Convert.ToByte(green, 16);
byte blueByte = Convert.ToByte(blue, 16);
return Color.FromArgb(alphaByte, redByte, greenByte, blueByte);
}
到这里简单的Ink操作,保存以及加载都已ok,望多多指教,另附上源码下载