墨迹序列化示例

此示例演示如何序列化和反序列化各种格式的墨迹。 应用程序表示一个窗体,其中包含用于输入名字、姓氏和签名的字段。 用户可以将此数据保存为纯墨迹序列化格式, (ISF) 、Extensible Markup Language (XML) 使用 base64 编码的 ISF 或 HTML(引用 base64 编码的强化图形交换格式 (GIF) 图像中的墨迹)。 该应用程序还允许用户打开保存为 XML 和 ISF 格式的文件。 ISF 格式使用扩展属性来存储名字和姓氏,而 XML 和 HTML 格式将此信息存储在自定义属性中。

此示例不支持从 HTML 格式加载,因为 HTML 不适合存储结构化数据。 由于数据被分隔为名称、签名等,因此需要保留这种分隔的格式,例如 XML 或其他类型的数据库格式。

HTML 在格式设置非常重要的环境中非常有用,例如在字处理文档中。 此示例保存的 HTML 使用强化 GIF。 这些 GIF 中嵌入了 ISF,从而保留墨迹的完整保真度。 字处理应用程序可以保存包含多种类型的数据(如图像、表格、格式化文本和以 HTML 格式保存的墨迹)的文档。 此 HTML 将在无法识别墨迹的浏览器中呈现。 但是,当加载到启用了墨迹的应用程序时,原始墨迹的完整保真度可用,并且可以呈现、编辑或用于识别。

此示例使用以下功能:

收集墨迹

首先,参考与 Windows Vista 和 Windows XP 平板电脑版软件开发工具包一起安装的 Tablet PC API (SDK) 。

using Microsoft.Ink;

构造函数为窗体创建并启用 InkCollectoric

ic = new InkCollector(Signature.Handle);
ic.Enabled = true;

保存文件

方法 SaveAsMenu_Click 处理“另存为”对话框,创建保存墨迹数据的文件流,并调用与用户选择对应的 save 方法。

保存到 ISF 文件

SaveISF在 方法中,先将名字和姓氏值添加到 InkCollector 对象的 Ink 属性的 ExtendedProperties 属性中,然后再序列化墨迹并将其写入文件。 墨迹序列化后,将从 Ink 对象的 ExtendedProperties 属性中删除名字和姓氏值。

byte[] isf;

// This is the ink object which is serialized
ExtendedProperties inkProperties = ic.Ink.ExtendedProperties;

// Store the name fields in the ink object
// These fields roundtrip through the ISF format
// Ignore empty fields since strictly empty strings 
//       cannot be stored in ExtendedProperties.
if (FirstNameBox.Text.Length > 0)
{
    inkProperties.Add(FirstName, FirstNameBox.Text);
}
if (LastNameBox.Text.Length > 0)
{
    inkProperties.Add(LastName, LastNameBox.Text);
}

// Perform the serialization
isf = ic.Ink.Save(PersistenceFormat.InkSerializedFormat);

// If the first and last names were added as extended
// properties to the ink, remove them - these properties
// are only used for the save and there is no need to
// keep them around on the ink object.
if (inkProperties.DoesPropertyExist(FirstName))
{
    inkProperties.Remove(FirstName);
}
if (inkProperties.DoesPropertyExist(LastName))
{
    inkProperties.Remove(LastName);
}

// Write the ISF to the stream
s.Write(isf,0,isf.Length);

保存到 XML 文件

在 方法中 SaveXMLXmlTextWriter 对象用于创建和写入 XML 文档。 使用 Ink 对象的 Save 方法,首先将墨迹转换为 base64 编码的 Ink 序列化格式字节数组,然后将字节数组转换为要写出到 XML 文件的字符串。 表单中的文本数据也会写出到 XML 文件中。

// Get the base64 encoded ISF
base64ISF_bytes = ic.Ink.Save(PersistenceFormat.Base64InkSerializedFormat);

// Convert it to a String
base64ISF_string = utf8.GetString(base64ISF_bytes);

// Write the ISF containing node to the XML
xwriter.WriteElementString("Ink", base64ISF_string);

// Write the text data from the form
// Note that the names are stored as XML fields, rather
// than custom properties, so that these properties can 
// be most easily accessible if the XML is saved in a database.
xwriter.WriteElementString("FirstName",FirstNameBox.Text);
xwriter.WriteElementString("LastName",LastNameBox.Text);

保存到 HTML 文件

SaveHTML 方法使用 Strokes 集合的边界框来测试是否存在签名。 如果签名存在,则使用墨迹对象的 Save 方法将其转换为强化 GIF 格式,并写入文件。 然后,在 HTML 文件中引用 GIF。

if (ic.Ink.Strokes.GetBoundingBox().IsEmpty)
{
   MessageBox.Show("Unable to save empty ink in HTML persistence format.");
}
else
{
    FileStream gifFile;
    byte[] fortifiedGif = null;
    ...

    // Create a directory to store the fortified GIF which also contains ISF
    // and open the file for writing
    Directory.CreateDirectory(nameBase + "_files");
    using (FileStream gifFile = File.OpenWrite(nameBase + "_files\\signature.gif"))
    {

        // Generate the fortified GIF represenation of the ink
        fortifiedGif = ic.Ink.Save(PersistenceFormat.Gif);

        // Write and close the gif file
        gifFile.Write(fortifiedGif, 0, fortifiedGif.Length);
    }

加载文件

方法 OpenMenu_Click 处理“打开”对话框,打开文件,并调用与用户选择对应的加载方法。

加载 ISF 文件

方法LoadISF读取以前创建的文件,并使用 Ink 对象的 Load 方法将字节数组转换为墨迹。 暂时禁用墨迹收集器以向其分配 Ink 对象。 然后, LoadISF 方法检查 Ink 对象的 ExtendedProperties 属性中的名字和姓氏字符串。

Ink loadedInk = new Ink();
byte[] isfBytes = new byte[s.Length];

// read in the ISF
s.Read(isfBytes, 0, (int) s.Length);

// load the ink into a new ink object
// After an ink object has been "dirtied" it can never load ink again
loadedInk.Load(isfBytes);

// temporarily disable the ink collector and swap ink objects
ic.Enabled = false;
ic.Ink = loadedInk;
ic.Enabled = true;

// Repaint the inkable region
Signature.Invalidate();

ExtendedProperties inkProperties = ic.Ink.ExtendedProperties;

// Get the raw data out of this stroke's extended
// properties list, using the previously defined 
// Guid as a key to the extended property.
// Since the save method stored the first and last
// name information as extended properties, this
// information can be remove now that the load is complete.
if (inkProperties.DoesPropertyExist(FirstName))
{
    FirstNameBox.Text = (String) inkProperties[FirstName].Data;
    inkProperties.Remove(FirstName);
}
else
{
    FirstNameBox.Text = String.Empty;
}

if (inkProperties.DoesPropertyExist(LastName))
{
    LastNameBox.Text = (String) inkProperties[LastName].Data;
    inkProperties.Remove(LastName);
}
else
{
    LastNameBox.Text = String.Empty;
}

加载 XML 文件

方法LoadXML加载以前创建的 XML 文件,从 Ink 节点检索数据,并使用 Ink 对象的 Load 方法将节点中的数据转换为墨迹暂时禁用 InkCollector 以向其分配 Ink 对象。 签名框失效,并从 XML 文档检索名字和姓氏信息。

// This object encodes our byte data to a UTF8 string
UTF8Encoding utf8 = new UTF8Encoding();

XmlDocument xd = new XmlDocument();
XmlNodeList nodes;
Ink loadedInk = new Ink();

// Load the XML data into a DOM
xd.Load(s);

// Get the data in the ink node
nodes = xd.GetElementsByTagName("Ink");

// load the ink into a new ink object
// After an ink object has been "dirtied" it can never load ink again
if (0 != nodes.Count)
    loadedInk.Load(utf8.GetBytes(nodes[0].InnerXml));

// temporarily disable the ink collector and swap ink objects
ic.Enabled = false;
ic.Ink = loadedInk;
ic.Enabled = true;

// Repaint the inkable region
Signature.Invalidate();

// Get the data in the FirstName node
nodes = xd.GetElementsByTagName("FirstName");
if (0 != nodes.Count)
{
    FirstNameBox.Text = nodes[0].InnerXml;
}
else
{
    FirstNameBox.Text = String.Empty;
}

// Get the data in the LastName node
nodes = xd.GetElementsByTagName("LastName");
if (0 != nodes.Count)
{
    LastNameBox.Text = nodes[0].InnerXml;
}
else
{
    LastNameBox.Text = String.Empty;
}

关闭窗体

窗体的 Dispose 方法释放 InkCollector 对象。