关于如何解决特定场景下WPF4.0中“XamlWriter.Save序列化限制”问题的一种思路

问题背景     

      笔者最近使用ArcGIS Server WPF API 开发一个能够实现所见即所得的基于Xaml的图层符号编辑功能。这个功能主要目的能够让用户以Xaml的形式来定义图层符号,并能够在编辑的同时以可视化的方式实时地预览该符号的设计效果,最后将结果保存到以Xaml文本形式存储的符号文件库(一个Xaml文件以ResourceDictionary的方式保存了多个符号)中。同时该功能还允许用户能够编辑和保存位于Xaml文件库中的符号。对于解决这类问题的一般思路往往都是:

1、首先,在系统启动时将位于Xaml文件库中的符号以反序列化的方式转换为.net的内存对象,并进行可视化的显示,这个通过.net 4.0中提供的XamlReader.Load方法就可以轻松实现;

2、然后,在用户需要对选定对象进行编辑的时,再将该对象通过XamlWrite.Save方法序列化成Xaml文本。并在用户修改该Xaml文本的同时实时地反序列化成对象,以达到实时预览的效果;

3、最后,保存时再将最新的Xaml文本保存回Xaml文本库。

      这个思路本来没有任何问题,但在实际实施的过程中,笔者发现使用XamlWriter.Save方法序列化得到的Xaml文本与原始的Xaml文本不一致,且再利用XamlRead.Load方法将序列化得到的Xaml文本反序列化后,业务对象无法正常使用。后来经过查阅msdn发现XamlWriter.Save方法在序列化Xaml对象的过程中确实存在诸多限制(详见《XamlWriter.Save的序列化限制》)。因此希望通过XamlWriter.Save来完成符号对象的序列化的路线是行不通的。

问题分析与解决思路

      经过仔细地分析,笔者发现该功能中涉及到序列化的对象主要有两种状态:一是新建对象,另一个是Xaml符号库中的对象。对于新建的对象,由于对应的Xaml文本均是用户在程序运行时输入指定,所以在保存阶段只需直接将该文本输出到符号库中即可;对于Xaml符号库中已有的对象,可以考虑在使用XamlReader.Load方法反序列化符号对象的同时液将该对象所对应的Xaml文本保存到一个内存字典中,当用户需要对该符号进行编辑时则直接从内存字典中取出该符号所对应的Xaml文本,最后当需要保存修改结果时,则是将修改后的最新Xaml文本保存回符号库。使用以上过程即可越过XamlWrite.Save来完成符号对象的保存。

实现要点

      为了实现上面的思路,笔者设计了一个名为XamlSymbolResourceDictSerializer的类,该类实现了自定义接口IXamlSymbolResourceDictSerializer,接口结构如下:

image

接口IXamlSymbolResourceDictSerializer
interface IXamlSymbolResourceDictSerializer
{
/// <summary>
/// deserialize object from the xaml stream
/// </summary>
/// <returns>return the instance of class ResourceDictionary </returns>
System.Windows.ResourceDictionary Read();
/// <summary>
/// serialize instance into xaml format
/// </summary>
/// <returns></returns>
string Save();
/// <summary>
/// Get or set the stream of the xaml ,the root node of which is ResourceDictionary
/// </summary>
System.IO.Stream Xamlstream { get; set; }
/// <summary>
/// Get the dictionary of symbols in xaml format
/// </summary>
Dictionary<string, string> XamlSymbols
{
get;
}
/// <summary>
/// the footer of the serialize xaml document
/// </summary>
string XmalRDFooter { get; set; }
/// <summary>
/// the hander of the serialize xaml document
/// </summary>
string XmalRDHander { get; set; }
}

 

     在该接口中定义了两个方法:Read和Save,分别用于实现图层符号的反序列化和序列化;

     接口中的属性对象:

         Xamlstream用于初始化时设置Xaml文本流;

         XamlSymbols用于存储该Xaml文本流中各符号对应的Xaml文本的字典;

         XamlRDHander和XamlRDFooter用于记录该Xaml文本流中ResourceDictionary的首尾标签,其中首标签中存储了该Xaml文档的命名空间。

      系统在实例化类XamlSymbolResourceDictSerializer时,在构造函数中传入Xaml文本流对象(也可使用属性Xamlstream传入),然后调用Read方法来反序列化获取Xaml文本流中的符号对象。

  Read方法

     在Read方法中主要完成了两个方面的功能:

     1、以文本解析的方式来获取:存储该Xaml文本流中各符号对应的Xaml文本的字典和该Xaml文本流中ResourceDictionary的首尾标签;

     2、通过XamlReader.Load方法反序列化Xaml文本流,以得到ResourceDictionary实例。

   Save方法

     在Save方法中主要完成了将记录下的Xaml文本流中ResourceDictionary的首尾标签和各符号对应的Xaml文本的字典进行序列化后的文本输出。

实现代码

XamlSymbolResourceDictSerializer
class XamlSymbolResourceDictSerializer:IDisposable, SymbolEditor.Serialize.IXamlSymbolResourceDictSerializer
{
#region private fields

private Stream xamlstream = null;

private string _xmalRDHander = string.Empty;

private string _xmalRDFooter = "</ResourceDictionary>";

private string xamlText = string.Empty;

private Stream xamlStreamCopy = null;

StreamReader streamCopyReader
= null;

#endregion

#region public fields and attributes

private Dictionary<string, string> xamlSymbols = new Dictionary<string, string>();

public Dictionary<string, string> XamlSymbols
{
get { return xamlSymbols; }
set { xamlSymbols = value; }
}

public string XmalRDHander
{
get { return _xmalRDHander; }
set { _xmalRDHander = value; }
}

public string XmalRDFooter
{
get { return _xmalRDFooter; }
set { _xmalRDFooter = value; }
}

public Stream Xamlstream
{
get { return xamlstream; }
set
{
xamlstream
= value;
xamlStreamCopy
= new MemoryStream();
xamlstream.CopyTo(xamlStreamCopy);
xamlStreamCopy.Position
= 0;
xamlstream.Position
= 0;
}
}

#endregion

#region contructor

public XamlSymbolResourceDictSerializer(Stream xamlStream)
{
Xamlstream
= xamlStream;
}

public XamlSymbolResourceDictSerializer() { }

#endregion

#region public function

public ResourceDictionary Read()
{
InitializeXamlSymbols();
xamlstream.Position
= 0;
return XamlReader.Load(xamlstream) as ResourceDictionary;
}

public string Save()
{
StringBuilder sb
= new StringBuilder();
sb.AppendLine(_xmalRDHander);
sb.AppendLine(serializeXamlSymbols());
sb.AppendLine(_xmalRDFooter);
return sb.ToString();
}

#endregion

#region private method

private void InitializeXamlSymbols()
{
System.Xaml.XamlXmlReader reader
= new System.Xaml.XamlXmlReader(xamlstream);
int linenum = 0;
int linepos = 0;
initializeRDHander(reader);
initializeSymbols(reader);
//initializeRDFooter(reader, ref linenum, ref linepos);

reader.Close();

}

private void initializeSymbols(System.Xaml.XamlXmlReader reader)
{
int beginlinenum = reader.LineNumber;
int beginlinepos = reader.LinePosition - 2;
int endlinenum = -1;
int endlinepos = -1;
string key = string.Empty;
while (reader.Read())
{

if (reader.NodeType == System.Xaml.XamlNodeType.StartObject && reader.Type.Name.Contains("Symbol"))
{
endlinenum
= reader.LineNumber;
endlinepos
= reader.LinePosition - 2;


int linenum = endlinenum - beginlinenum;
StringBuilder sb
= new StringBuilder(linenum);

//int count=endIndex - beginIndex;
//char[] buffer = new char[count];
//streamreader.ReadBlock(buffer, beginIndex, count);
//xmalRDHander = new string(buffer);
//string firstline = streamCopyReader.ReadLine();
//sb.AppendLine(firstline);

for (int i = 0; i < linenum; i++)
{
string linestr = streamCopyReader.ReadLine();
sb.AppendLine(linestr);
}
//char[] buffer = new char[endlinepos + 1];
//string endline = streamCopyReader.ReadLine();
////streamreader.ReadBlock(buffer, (int)streamreader.BaseStream.Position, buffer.Length);
//sb.AppendLine(endline);
string xamlsymbol = sb.ToString();
xamlSymbols.Add(key, xamlsymbol);
beginlinenum
= reader.LineNumber;
beginlinepos
= reader.LinePosition - 2;
}



if (reader.NodeType == System.Xaml.XamlNodeType.StartMember && reader.Member.Name.ToUpper() == "KEY")
{
if (reader.LineNumber == beginlinenum)
{
while (reader.Read() && reader.NodeType != System.Xaml.XamlNodeType.Value) { }
key
= reader.Value.ToString();
}
}
}
}

private void initializeRDHander(System.Xaml.XamlXmlReader reader)
{
//int beginIndex = (int)xamlstream.Position;
int beginlinenum = reader.LineNumber;
int beginlinepos = reader.LinePosition;
int endlinenum = -1;
int endlinepos = -1;
//int endIndex = -99999;
while (reader.Read())
{
if (reader.NodeType == System.Xaml.XamlNodeType.StartObject && reader.Type.Name.Contains("Symbol"))
{
endlinenum
= reader.LineNumber;
endlinepos
= reader.LinePosition;
break;
}
}
if (endlinenum > 0)
{
xamlStreamCopy.Position
= 0;
int linenum = endlinenum - beginlinenum;
StringBuilder sb
= new StringBuilder(linenum);
streamCopyReader
= new StreamReader(xamlStreamCopy);

//int count=endIndex - beginIndex;
//char[] buffer = new char[count];
//streamreader.ReadBlock(buffer, beginIndex, count);
//xmalRDHander = new string(buffer);
for (int i = 0; i < linenum - 1; i++)
{
string linestr = streamCopyReader.ReadLine();
sb.AppendLine(linestr);
}
//char[] buffer = new char[endlinepos + 1];
//string endline = streamCopyReader.ReadLine();
//streamreader.ReadBlock(buffer, (int)streamreader.BaseStream.Position, buffer.Length);
//sb.AppendLine(endline.Substring(0, endlinepos -2));
_xmalRDHander = sb.ToString();
//xamlStreamCopy.Position = xamlStreamCopy.Position - endline.Length + 1;

}

}

private string serializeXamlSymbols()
{
StringBuilder sb
= new StringBuilder(xamlSymbols.Count);
foreach (string xamlsymbol in xamlSymbols.Values)
{
sb.AppendLine(xamlsymbol);
}
return sb.ToString();
}

#endregion

#region IDisposable 成员

public void Dispose()
{
xamlSymbols.Clear();
xamlSymbols
= null;
if (xamlstream != null)
{
xamlstream.Dispose();
xamlstream.Close();
}
if (xamlStreamCopy != null)
{
xamlStreamCopy.Dispose();
xamlStreamCopy.Close();
}
if(streamCopyReader!=null)
{
streamCopyReader.Dispose();
streamCopyReader.Close();
}
}

#endregion
}

 

总结

     本文采用的是“回避”战术来解决WPF4.0中“XamlWriter.Save序列化限制”的问题,该战术在本例中确实能够起到一定的积极作用,能够较好地解决图层符号可视化编辑与管理的功能需求,还能避免出现序列化时性能低下的问题。但该战术还存在一个致命的缺陷,就是它与特定的使用场景绑得太紧,不够通用。对于这一点,笔者有些思路:能否通过可配置的方式,让XamlSymbolResourceDictSerializer类能够适应各种xaml文档结构。如果有新的成果,笔者会第一时间与大家分享。如果大家有什么解决xaml序列化问题的好思路,也请和笔者联系,咱们共同学习。

posted @ 2010-09-21 15:55  乐水鱼  阅读(2820)  评论(4编辑  收藏  举报