呵呵,程序终于告一段落了,程序也终于Finish了,让大家久等了,希望不会让大家失望。

这也是比较典型的WinForm项目了,想学习WinForm开发的朋友可以照着我的步骤做下去,而且也提供了初版的源代码。

虽然项目比较小,而且几乎没涉及到什么业务上的东西,不过程序开发涉及面很大,有:

1.文件操作(包括文件的写,读取等)

2.XML操作(将字符写入XML中和读取XML、利用XML做配置文件等)

3.递归算法(树)【虽然在实际中用的不多,还是希望大家能够掌握】

4.TreeView、DataGridView、WebBrowser、OpenFileDialog等典型的WinForm控件 
 

5.WinForm中切割图片、图片拼合、读取资源文件中的资源

....
 

因为是比较小的程序就没有分层,不过程序中也用到了不同一般WinForm项目的思想,具体体现在MainForm和其他Form的关系(详细可以参考源代码)

Ok ,看下程序的界面,和开始有一点变化。下面看看程序的截图吧,程序下载在最下面

 主界面(去掉了以前没有的菜单里,只剩工具栏)

编译界面(有简陋的正在编译效果)

配置页面(为了简单起见,还是默认Csdn编辑器,如果有兴趣的话,你们自己可以结合以前给的源代码加入新的编辑器)

 

添加文件界面(可以批量导入)

最终生成的CHM电子书界面

今天的主要内容是目录窗体的实现及搜索的实现

目录窗体BookIndexForm
 

目录窗体中,有一个ContextMenuSTrip,即右击窗体出现的菜单,里面有几个比较重要的方法

添加文件夹、添加文章、删除、重命名

添加文件夹即添加父节点, 选中可以添加文件夹的节点(显然只有根节点和目录节点),然后将新增一个CHMNode,将它Add到父节点的Nodes里面


   
   
  1. /// <summary> 
  2.         /// 添加文件夹  
  3.         /// </summary> 
  4.         /// <param name="sender"></param> 
  5.         /// <param name="e"></param> 
  6.         private void AddFolderToolStripMenuItem_Click(object sender, EventArgs e)  
  7.         {  
  8.             TreeNode node = this.tvIndex.SelectedNode;//选中的节点  
  9.               
  10.             //查看是否是根节点或是目录节点  
  11.             CHMNodeList list = this.GetNodeList(node);  
  12.             if (list == null || list.Count==0)  
  13.             {  
  14.                 MessageBox.Show("请选择根节点或是目录节点");  
  15.                 return;  
  16.             }  
  17.             //创建新的节点  
  18.             CHMNode newnewNode = new CHMNode();  
  19.             newNode.Name = "新建文件夹";  
  20.             newNode.ImageNo = "0";  
  21.             //newnewNode.Nodes = new CHMNodeList();  
  22.             list.Add(newNode);  
  23.             System.Windows.Forms.TreeNode node2 = new TreeNode(newNode.Name);  
  24.             node2.Tag = newNode;  
  25.             node2.ImageIndex = 0;  
  26.             node2.SelectedImageIndex = 0;  
  27.             node.Nodes.Add(node2);//将新节点添加到树中  
  28.             node.ImageIndex = 0;  
  29.             node.SelectedImageIndex = 0;  
  30.             this.tvIndex.SelectedNode = node2;  
  31.         } 

添加文章即添加 子节点,注意这时添加的节点他的Nodes要设为null,表示他是文章节点,不能再添加其他节点了
 


   
   
  1. /// <summary> 
  2.         /// 添加文章  
  3.         /// </summary> 
  4.         /// <param name="sender"></param> 
  5.         /// <param name="e"></param> 
  6.         private void AddArticleToolStripMenuItem_Click(object sender, EventArgs e)  
  7.         {  
  8.             TreeNode node = this.tvIndex.SelectedNode;  
  9.             CHMNodeList list = this.GetNodeList(node);  
  10.             if (list == null)  
  11.             {  
  12.                 MessageBox.Show("请选择根节点或是目录节点");  
  13.                 return;  
  14.             }  
  15.             EditForm frmEdit = new EditForm();  
  16.             frmEdit.ShowDialog(this);  
  17.    
  18.             CHMNode NewNode = frmEdit.Node;  
  19.             if(NewNode==null)  
  20.             {  
  21.                 return;  
  22.             }  
  23.             list.Add(NewNode);  
  24.             System.Windows.Forms.TreeNode node2 = new TreeNode(NewNode.Name);  
  25.             node2.Tag = NewNode;  
  26.             node2.ImageIndex = 1;  
  27.             node2.SelectedImageIndex = 1;  
  28.             node.Nodes.Add(node2);//将新节点添加到树中  
  29.             node.ImageIndex = 0;  
  30.             node.SelectedImageIndex = 0;  
  31.             this.tvIndex.SelectedNode = node2;  
  32.               
  33.         } 

重命名:右击节点可以编辑节点的Label,

实现方法如下:

将TreeView的LabelEdit设为True,然后编写如下代码:


   
   
  1. private void ReNameMToolStripMenuItem_Click(object sender, EventArgs e)  
  2.         {  
  3.             this.tvIndex.SelectedNode.BeginEdit();  
  4.         }  
  5.    
  6.         private void tvIndex_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)  
  7.         {  
  8.             this.tvIndex.SelectedNode.Name = e.Label;  
  9.             TreeNode node = this.tvIndex.SelectedNode;  
  10.             if (node.Tag is CHMDocument)  
  11.             {  
  12.                 ((CHMDocument)node.Tag).Title = e.Label;  
  13.             }  
  14.             if (node.Tag is CHMNode)  
  15.             {  
  16.                 ((CHMNode)node.Tag).Name = e.Label;  
  17.             }  
  18.         } 

删除节点,即移除,同时还要删除文件


   
   
  1. /// <summary> 
  2.         /// 删除节点  
  3.         /// </summary> 
  4.         /// <param name="sender"></param> 
  5.         /// <param name="e"></param> 
  6.         private void DeleteDToolStripMenuItem_Click(object sender, EventArgs e)  
  7.         {  
  8.             System.Windows.Forms.TreeNode node = this.tvIndex.SelectedNode;//获取要删除的节点  
  9.             if (node == null)  
  10.             {  
  11.                 MessageBox.Show("请选择一个节点");  
  12.                 return;  
  13.             }  
  14.             System.Windows.Forms.TreeNode parent = node.Parent;//获取该节点的父节点  
  15.             if (parent == null)  
  16.             {  
  17.                 MessageBox.Show("请选择目录或页面节点");  
  18.                 return;  
  19.             }  
  20.             CHMNodeList list = GetNodeList(parent);  
  21.             if (MessageBox.Show("是否删除文章(删除同时删除本地文件)?","确认", MessageBoxButtons.YesNo)== System.Windows.Forms.DialogResult.Yes)  
  22.             {  
  23.                 list.Remove((CHMNode)node.Tag);  
  24.                 //同时删除文件  
  25.                 if (System.IO.File.Exists(((CHMNode)node.Tag).Local))  
  26.                 {  
  27.                     System.IO.File.Delete(((CHMNode)node.Tag).Local);  
  28.                 }  
  29.                 node.Remove();//移除该节点      
  30.             }  
  31.         } 

代码库搜索功能(Lucene.Net 2.0.04)

这个搜索只是简单的使用Lucene.Net实现的搜索。

 我们在点击搜索按钮的时候,才开始做索引的操作。遍历chmDocument这个类,将节点信息存储为索引,然后查的时候就去查索引,好像有点多此一举。

其实不然,因为我们要全文模糊检索,我们不能看将每个文件打开,然后找找里面有没有这个搜索词。所以就用Lucene.Net。

具体实现代码如下:

 


   
   
  1. /// <summary> 
  2.         /// 查询(修改为在点击查询的时候再去索引什么)  
  3.         /// </summary> 
  4.         /// <param name="sender"></param> 
  5.         /// <param name="e"></param> 
  6.         private void btnSearch_Click(object sender, EventArgs e)  
  7.         {  
  8.             //INDEX_STORE_PATH 为索引存储目录  
  9.             string INDEX_STORE_PATH = Application.StartupPath+@"\index";    
  10.    
  11.             //先存储索引  
  12.             IndexWriter writer = new IndexWriter(INDEX_STORE_PATH, new StandardAnalyzer(), true);  
  13.             SetIndex(writer,this.nodes);  
  14.    
  15.             //在从索引中查询  
  16.             string KEYWORD = this.txtKeyWords.Text.ToString();//关键字  
  17.             IndexSearcher searcher;  
  18.    
  19.             try  
  20.             {  
  21.                 searcher = new IndexSearcher(INDEX_STORE_PATH);  
  22.                 QueryParser q = null;  
  23.    
  24.                 if (rbByTitle.Checked)  
  25.                 {  
  26.                     q = new QueryParser("title", new StandardAnalyzer());  
  27.                 }  
  28.                 else if (rbByKeywords.Checked)  
  29.                 {  
  30.                     q = new QueryParser("keywords", new StandardAnalyzer());  
  31.                 }  
  32.                 else if (rbByAll.Checked)  
  33.                 {  
  34.                     q = new QueryParser("contents", new StandardAnalyzer());  
  35.                 }  
  36.                   
  37.                 Query qquery = q.Parse(KEYWORD);  
  38.                 Hits hits = searcher.Search(query);  
  39.    
  40.                 //创建DataTable用于绑定  
  41.                 DataTable dtResult = new DataTable();  
  42.                 DataColumn dc1 = new DataColumn("Title", Type.GetType("System.String"));  
  43.                 DataColumn dc2new DataColumn("KeyWords", Type.GetType("System.String"));  
  44.                 DataColumn dc3 = new DataColumn("Content", Type.GetType("System.String"));  
  45.                 DataColumn dc4 = new DataColumn("FilePath", Type.GetType("System.String"));  
  46.                 dtResult.Columns.Add(dc1);  
  47.                 dtResult.Columns.Add(dc2);  
  48.                 dtResult.Columns.Add(dc3);  
  49.                 dtResult.Columns.Add(dc4);  
  50.    
  51.                 if (hits != null && hits.Length()>0)  
  52.                 {  
  53.                     for (int i = 0; i < hits.Length(); i++)  
  54.                     {  
  55.                         Document doc = hits.Doc(i);  
  56.                         DataRow dr=dtResult.NewRow();  
  57.                         dr["Title"] = doc.Get("title");//文章标题  
  58.                         dr["KeyWords"] = doc.Get("keywords");//文章关键字  
  59.                         dr["Content"] = doc.Get("contents");//内容  
  60.                         dr["FilePath"] = doc.Get("filename");//文件路径  
  61.                         dtResult.Rows.Add(dr);  
  62.                     }  
  63.                     this.tcList.SelectedIndex = 1;  
  64.                     this.dgvResult.DataSource = dtResult;  
  65.                     this.dgvResult.Columns["FilePath"].Visible = false;  
  66.                 }  
  67.                 else  
  68.                 {  
  69.                     MessageBox.Show("没有查到相关记录!");  
  70.                 }  
  71.                 searcher.Close();  
  72.             }  
  73.             catch (Exception ex)  
  74.             {  
  75.                 LogHelper.WriteLog(ex.Message);  
  76.             }  
  77.         } 

其中,写入索引的代码如下,也是使用遍历的(一开始想当然地做了,结果老是死循环,看来递归还是没有掌握好)

 


   
   
  1. /// <summary> 
  2.         /// 遍历chmDocument,将节点存储为索引  
  3.         /// </summary> 
  4.         private void SetIndex(IndexWriter writer, CHMNodeList nodes)  
  5.         {  
  6.             if (this.nodes == null || this.nodes.Count == 0)  
  7.             {  
  8.                 return;  
  9.             }  
  10.             foreach (CHMNode n in nodes)  
  11.             {  
  12.                 //if (node.Nodes==null)  
  13.                 if (n.ImageNo == "1")//使用imageNo来判断  
  14.                 {  
  15.                     IndexFile(n, writer);  
  16.                 }  
  17.                 else  
  18.                 {  
  19.                     SetIndex(writer,n.Nodes);  
  20.                 }  
  21.             }  
  22.             writer.Close();// 关闭writer  
  23.         }  
  24. private void IndexFile(CHMNode node, IndexWriter writer)  
  25.         {  
  26.             try  
  27.             {  
  28.                 Document doc = new Document();  
  29.                 doc.Add(new Field("filename", node.Local, Field.Store.YES, Field.Index.TOKENIZED));  
  30.                 doc.Add(new Field("title", node.Name, Field.Store.YES, Field.Index.TOKENIZED));  
  31.                 doc.Add(new Field("keywords", node.KeyWords, Field.Store.YES, Field.Index.TOKENIZED));  
  32.                 doc.Add(new Field("contents", new StreamReader(node.Local, System.Text.Encoding.Default)));  
  33.                 writer.AddDocument(doc);  
  34.             }  
  35.             catch (FileNotFoundException fnfe)  
  36.             {  
  37.                 LogHelper.WriteLog(fnfe.Message);  
  38.             }  
  39.              
  40.         } 

在搜索界面中还是用了TabControl,以前用过DevExpress的TabControl,它隐藏 TabControl标签有现成的方法的,但是MS的TabControl貌似没有这个方法,于是就弄了讨巧的方法,将TabControl放在一个Panel中,头部稍微超出Panel的边界,然后在Form_Load方法中写如下代码:
 


   
   
  1. //隐藏TabContol的标签栏  
  2.             this.tcList.SizeMode = TabSizeMode.Fixed;  
  3.             this.tcList.ItemSize = new Size(0, 1); 

附件下载 AlexisEditor RC版

 说明:如果没有重大bug或者是反馈了,这个系列就到这边结束了,后面有篇文章专门总结这一系列的,里面的知识点还是比较多的,希望里面的知识能够帮到大家。