在上一篇文章中,我大致介绍了代码编辑器里面的一些主要功能和大致实现方法,从这篇文章开始,我将会将里面涉及到的一些技术跟大家分享下。

更新下程序 AlexisEditor下载

看下程序的界面,有菜单里、工具栏,还有几个可以悬停的面板

程序的Soultion , 可以看到有三个项目,一个WinForm项目及两个类库项目,增加类库项目是为了更好的实现代码分离。

下面的两张图是主项目AlexisEditor的类关系图:

 

 

在来看看ChmHelper项目的类关系图(张图会在下面反复涉及)

 

Viusal Studio风格的界面的实现

 在上一篇文章中有介绍到Viusal Studio风格的界面,它的实现很简单,由上图我们看到有许多Form是继承自BaseDockForm的,而BaseDockForm是继承自DockContent的。

实现步骤如下:

添加 WeifenLuo.WinFormsUI.Docking.dll引用,新建BaseForm继承自WeifenLuo.WinFormsUI.Docking.DockContent。

在主界面中放置一个DockPanel,设置它的Dock属性为Fill,然后在主界面中使用如下代码即可

 
  
  1. public MainForm()  
  2.  {  
  3.             InitializeComponent();  
  4.  
  5.             frmIndex.Show(dockPanel);//显示目录窗体  
  6.             frmIndex.DockTo(dockPanel, DockStyle.Left);  
  7.  }  

这样目录窗体就可以自动靠左显示,并可以自动隐藏。其他的窗体可以类似实现。

目录树的实现

上篇文章中讲到使用xml存储目录,那么这个xml是什么样的格式呢?AlexisEditor的目录树如下:

 
  
  1. <CHMDocument Title="帮助文档"> 
  2.   <Items> 
  3.     <Node Name="但是使用" Local="E:\WorkSpace\projects\AlexisEditor\AlexisEditor\bin\Debug\html_files\129317762488604234.htm" ImageNumber="1" KeyWords="" /> 
  4.     <Node Name="aa" Local="E:\WorkSpace\projects\AlexisEditor\AlexisEditor\bin\Debug\html_files\129317940192503066.htm" ImageNumber="1" KeyWords="" /> 
  5.     <Node Name="新建文件夹" Local="" ImageNumber="0" KeyWords=""> 
  6.       <Items> 
  7.         <Node Name="aa" Local="E:\WorkSpace\projects\AlexisEditor\AlexisEditor\bin\Debug\html_files\129317940469098887.htm" ImageNumber="1" KeyWords="aaa" /> 
  8.       </Items> 
  9.     </Node> 
  10.   </Items> 
  11. </CHMDocument> 

根节点是CHMDocument ,如果几点有<Items>子节点,表示该节点是父节点。

每个节点有这样的属性,Name表示文章的标题,Local表示文章的实际路径,ImageNumber表示几点的图片索引,KeyWords表示文章中的关键字。

如果该节点有子节点,则子节点也是这样的,当然父节点的Local、KeyWords为空。

这样的节点对应着类CHMNode,从类关系图中可以看到还有一个Nodes属性,它表示节点的子节点的集合。它是类CHMNodeList的实例,CHMNodeList继承自CollectionBase,并重写了一些集合的主要的方法。

CHMDocument类

CHMDocument类是ChmHelper中最重要的类,它表示当前的电子书,并且负责将书籍编译为CHM电子书的重任。下面着重来看它是怎么实现的,从上述的关系图中看到它有许多的方法、属性和字段。下面分别介绍:

CHMDocument类之属性

FileName表示CHM文件名,Nodes表示电子书除了根节点以外的节点集合,OutPutText表示在生成CHM电子书的编译信息,Title表示CHM的标题

CHMDocument类之字段

streamWriter:以流实现写入文件的类的实例,strHhp、strHhc、strHhk分别是临时生成的hhp、hhc、hhk文件的文件名,默认为alexisEditor,至于config他是静态类XBookConfig类的实例,用来实现一些配置信息,如编译器的路径,是否删除临时文件等。

CHMDocument类之方法

下图是CHMDocument类的主要方法及一些简单说明

 

先来看加载方法及其调用的方法

 
  
  1. public void Load(string filename)  
  2. {  
  3.             System.Xml.XmlDocument doc = new System.Xml.XmlDocument();  
  4.             doc.Load(filename);  
  5.             FromXML(doc.DocumentElement);  

 
  
  1. private void FromXML(System.Xml.XmlElement RootElement)  
  2. {  
  3.             //this.defaultPage = RootElement.GetAttribute("DefaultTopic");  
  4.             this._title = RootElement.GetAttribute("Title");//标题  
  5.             nodeList.Clear();  
  6.    
  7.             foreach (System.Xml.XmlNode node in RootElement.ChildNodes)  
  8.             {  
  9.                 if (node.Name == "Items")  
  10.                 {  
  11.                     NodesFromXML(nodeList, (System.Xml.XmlElement)node);  
  12.                 }  
  13.             }  

 
  
  1. private void NodesFromXML(CHMNodeList nodes, System.Xml.XmlElement RootElement)  
  2. {  
  3.             foreach (System.Xml.XmlNode node in RootElement.ChildNodes)  
  4.             {  
  5.                 if (node.Name == "Node")  
  6.                 {  
  7.                     System.Xml.XmlElement element = (System.Xml.XmlElement)node;  
  8.                     CHMNode NewNode = new CHMNode();  
  9.                     NewNode.Name = element.GetAttribute("Name");  
  10.                     NewNode.Local = element.GetAttribute("Local");  
  11.                     NewNode.ImageNo = element.GetAttribute("ImageNumber");  
  12.                     NewNode.KeyWords = element.GetAttribute("KeyWords");  
  13.                     nodes.Add(NewNode);  
  14.                     foreach (System.Xml.XmlNode node2 in element.ChildNodes)  
  15.                     {  
  16.                         if (node2.Name == "Items")  
  17.                         {  
  18.                             NodesFromXML(NewNode.Nodes, (System.Xml.XmlElement)node2);  
  19.                         }  
  20.                     }  
  21.                 }  
  22.             }  

注意NodesFromXML 方法是递归的方法,大家可以仔细琢磨下这些代码,保存方法其实是类似的,不同的是将节点保存到xml中,代码如下:

 
  
  1. public void Save(string filename)  
  2. {  
  3.             System.Xml.XmlDocument doc = new System.Xml.XmlDocument();  
  4.             doc.AppendChild(doc.CreateElement("CHMDocument"));  
  5.             ToXML(doc.DocumentElement);  
  6.             doc.Save(filename);  
  7.             _fileName = filename;  
  8. }  
  9.  
  10. private void ToXML(System.Xml.XmlElement RootElement)  
  11. {  
  12.             //RootElement.SetAttribute("DefaultTopic", this.strDefaultTopic);  
  13.             RootElement.SetAttribute("Title", this._title);  
  14.             System.Xml.XmlElement element = RootElement.OwnerDocument.CreateElement("Items");  
  15.             RootElement.AppendChild(element);  
  16.             NodesToXML(nodeList, element);  
  17. }  
  18.  
  19. //nodes保存为xml  
  20. private void NodesToXML(CHMNodeList nodes, System.Xml.XmlElement RootElement)  
  21. {  
  22.             System.Xml.XmlDocument doc = RootElement.OwnerDocument;  
  23.             foreach (CHMNode node in nodes)  
  24.             {  
  25.                 System.Xml.XmlElement NodeElement = doc.CreateElement("Node");  
  26.                 NodeElement.SetAttribute("Name", node.Name);  
  27.                 NodeElement.SetAttribute("Local", node.Local);  
  28.                 NodeElement.SetAttribute("ImageNumber", node.ImageNo);  
  29.                 NodeElement.SetAttribute("KeyWords", node.KeyWords);  
  30.                 RootElement.AppendChild(NodeElement);  
  31.                 if (node.Nodes != null && node.Nodes.Count > 0)  
  32.                 {  
  33.                     System.Xml.XmlElement ItemsElement = doc.CreateElement("Items");  
  34.                     NodeElement.AppendChild(ItemsElement);  
  35.                     NodesToXML(node.Nodes, ItemsElement);  
  36.                 }  
  37.             }  

接下来看下编译方法是怎么实现,具体的代码就不贴了,前面的文章中《C#生成CHM文件(入门篇)》中就有涉及,这里要提到的就是里面的递归算法,即分别将树中的Name及Local写入到hhc文件中及hhk(索引文件中),两个主要的方法代码如下:

 
  
  1. //递归实现将nodes写入hhc文件中  
  2.  private void NodesHhc(CHMNodeList nodeList)  
  3. {  
  4.             if (nodeList.Count == 0 || nodeList == null)  
  5.                 return;  
  6.             streamWriter.WriteLine("        <UL>");  
  7.             foreach (CHMNode node in nodeList)  
  8.             {  
  9.                 if (node.Nodes != null && node.Nodes.Count > 0)//如果是父节点  
  10.                 {  
  11.                     streamWriter.WriteLine("    <LI><OBJECT type=\"text/sitemap\">");  
  12.                     streamWriter.WriteLine("            <param name=\"Name\" value=\"" + node.Name + "\">");  
  13.                     streamWriter.WriteLine("        </OBJECT>");  
  14.                     NodesHhc(node.Nodes);  
  15.                 }  
  16.                 else//如果是子节点  
  17.                 {  
  18.                     streamWriter.WriteLine("         <LI><OBJECT type=\"text/sitemap\">");  
  19.                     streamWriter.WriteLine("                <param name=\"Name\" value=\"" + node.Name + "\">");  
  20.                     streamWriter.WriteLine("                <param name=\"Local\" value=\"" + node.Local + "\">");  
  21.                     streamWriter.WriteLine("             </OBJECT>");  
  22.                 }  
  23.             }  
  24.             streamWriter.WriteLine("        </UL>");  
  25.   }  
  26. 及  
  27. //递归写hhp中的Files  
  28. private void NodesHhp(CHMNodeList nodeList)  
  29. {  
  30.             if (nodeList == null || nodeList.Count == 0)  
  31.                 return;  
  32.             foreach (CHMNode node in nodeList)  
  33.             {  
  34.                 if (node.Nodes == null || node.Nodes.Count == 0)  
  35.                 {  
  36.                     streamWriter.WriteLine(node.Local);  
  37.                 }  
  38.                 else  
  39.                 {  
  40.                     NodesHhp(node.Nodes);  
  41.                 }  
  42.             }  

至于获取文件的相对路径和绝对路径的方法相信大家都会写,这里就不介绍了。


ok,至此已经讲了
如何创建Viusl Studio风格的窗体程序?
【让要悬停的窗体继承自DockContent,在调用窗体添加DockPanel,实例化悬停窗体,调DockTo方法】
PS:说明下,如何在ToolBox中添加DockPanel图标==》右击工具箱(ToolBox),选择“选择项...”,添加WeifenLuo.WinFormsUI.Docking.dll引用即可

以及目录是怎么通过xml实现的?
【使用xml存储书籍目录信息,使用类与xml、书籍对应,使用递归的方法获取树的节点】
当然目录树中还有许多其他的问题,今天将的只是类库里面的东西,不过万变不离其中,前台基本上都市调用这些代码的。

看了这篇文章后,大家可以尝试建立个Visual Studio风格的窗体、ChmHelper类库项目的大体(具体实现个随喜好),如果关于以上两点大家还有什么不明白的,
可以留言或者发邮件给我,我会尽快回答的。

再次PS下,如果觉得好的话,请推荐。另:求visual studio编译项目的图标(BMP格式,16*16)