C#生成CHM文件(应用篇)之代码库编辑器(2)

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


更新下程序 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,然后在主界面中使用如下代码即可

public MainForm()
 {
            InitializeComponent();

            frmIndex.Show(dockPanel);//显示目录窗体
            frmIndex.DockTo(dockPanel, DockStyle.Left);
 } 

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

目录树的实现

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

<CHMDocument Title="帮助文档">
  <Items>
    <Node Name="但是使用" Local="E:\WorkSpace\projects\AlexisEditor\AlexisEditor\bin\Debug\html_files\129317762488604234.htm" ImageNumber="1" KeyWords="" />
    <Node Name="aa" Local="E:\WorkSpace\projects\AlexisEditor\AlexisEditor\bin\Debug\html_files\129317940192503066.htm" ImageNumber="1" KeyWords="" />
    <Node Name="新建文件夹" Local="" ImageNumber="0" KeyWords="">
      <Items>
        <Node Name="aa" Local="E:\WorkSpace\projects\AlexisEditor\AlexisEditor\bin\Debug\html_files\129317940469098887.htm" ImageNumber="1" KeyWords="aaa" />
      </Items>
    </Node>
  </Items>
</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类的主要方法及一些简单说明

 


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

public void Load(string filename)
{
            System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
            doc.Load(filename);
            FromXML(doc.DocumentElement);
}

 和
private void FromXML(System.Xml.XmlElement RootElement)
{
            //this.defaultPage = RootElement.GetAttribute("DefaultTopic");
            this._title = RootElement.GetAttribute("Title");//标题
            nodeList.Clear();

            foreach (System.Xml.XmlNode node in RootElement.ChildNodes)
            {
                if (node.Name == "Items")
                {
                    NodesFromXML(nodeList, (System.Xml.XmlElement)node);
                }
            }
}

private void NodesFromXML(CHMNodeList nodes, System.Xml.XmlElement RootElement)
{
            foreach (System.Xml.XmlNode node in RootElement.ChildNodes)
            {
                if (node.Name == "Node")
                {
                    System.Xml.XmlElement element = (System.Xml.XmlElement)node;
                    CHMNode NewNode = new CHMNode();
                    NewNode.Name = element.GetAttribute("Name");
                    NewNode.Local = element.GetAttribute("Local");
                    NewNode.ImageNo = element.GetAttribute("ImageNumber");
                    NewNode.KeyWords = element.GetAttribute("KeyWords");
                    nodes.Add(NewNode);
                    foreach (System.Xml.XmlNode node2 in element.ChildNodes)
                    {
                        if (node2.Name == "Items")
                        {
                            NodesFromXML(NewNode.Nodes, (System.Xml.XmlElement)node2);
                        }
                    }
                }
            }
}

注意NodesFromXML 方法是递归的方法,大家可以仔细琢磨下这些代码,保存方法其实是类似的,不同的是将节点保存到xml中,代码如下:
public void Save(string filename)
{
            System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
            doc.AppendChild(doc.CreateElement("CHMDocument"));
            ToXML(doc.DocumentElement);
            doc.Save(filename);
            _fileName = filename;
}

private void ToXML(System.Xml.XmlElement RootElement)
{
            //RootElement.SetAttribute("DefaultTopic", this.strDefaultTopic);
            RootElement.SetAttribute("Title"this._title);
            System.Xml.XmlElement element = RootElement.OwnerDocument.CreateElement("Items");
            RootElement.AppendChild(element);
            NodesToXML(nodeList, element);
}


//nodes保存为xml
private void NodesToXML(CHMNodeList nodes, System.Xml.XmlElement RootElement)
{
            System.Xml.XmlDocument doc = RootElement.OwnerDocument;
            foreach (CHMNode node in nodes)
            {
                System.Xml.XmlElement NodeElement = doc.CreateElement("Node");
                NodeElement.SetAttribute("Name", node.Name);
                NodeElement.SetAttribute("Local", node.Local);
                NodeElement.SetAttribute("ImageNumber", node.ImageNo);
                NodeElement.SetAttribute("KeyWords", node.KeyWords);
                RootElement.AppendChild(NodeElement);
                if (node.Nodes != null && node.Nodes.Count > 0)
                {
                    System.Xml.XmlElement ItemsElement = doc.CreateElement("Items");
                    NodeElement.AppendChild(ItemsElement);
                    NodesToXML(node.Nodes, ItemsElement);
                }
            }
}
接下来看下编译方法是怎么实现,具体的代码就不贴了,前面的文章中《C#生成CHM文件(入门篇)》中就有涉及,这里要提到的就是里面的递归算法,即分别将树中的
Name及Local写入到hhc文件中及hhk(索引文件中),两个主要的方法代码如下:
//递归实现将nodes写入hhc文件中
 private void NodesHhc(CHMNodeList nodeList)
{
            if (nodeList.Count == 0 || nodeList == null)
                return;
            streamWriter.WriteLine("        <UL>");
            foreach (CHMNode node in nodeList)
            {
                if (node.Nodes != null && node.Nodes.Count > 0)//如果是父节点
                {
                    streamWriter.WriteLine("    <LI><OBJECT type=\"text/sitemap\">");
                    streamWriter.WriteLine("            <param name=\"Name\" value=\"" + node.Name + "\">");
                    streamWriter.WriteLine("        </OBJECT>");
                    NodesHhc(node.Nodes);
                }
                else//如果是子节点
                {
                    streamWriter.WriteLine("         <LI><OBJECT type=\"text/sitemap\">");
                    streamWriter.WriteLine("                <param name=\"Name\" value=\"" + node.Name + "\">");
                    streamWriter.WriteLine("                <param name=\"Local\" value=\"" + node.Local + "\">");
                    streamWriter.WriteLine("             </OBJECT>");
                }
            }
            streamWriter.WriteLine("        </UL>");
  }


//递归写hhp中的Files
private void NodesHhp(CHMNodeList nodeList)
{
            if (nodeList == null || nodeList.Count == 0)
                return;
            foreach (CHMNode node in nodeList)
            {
                if (node.Nodes == null || node.Nodes.Count == 0)
                {
                    streamWriter.WriteLine(node.Local);
                }
                else
                {
                    NodesHhp(node.Nodes);
                }
            }
}

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


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)

原文地址:https://www.cnblogs.com/alexis/p/1854253.html