技术开发 频道

C#实践生成CHM文件之代码库编辑器

    【IT168 技术文档】呵呵,程序终于告一段落了,程序也终于Finish了,让大家久等了,希望不会让大家失望。

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

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

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

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

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

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

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

  ....

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

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

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

1
 

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

1
 

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

1
 

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

1
 

  最终生成的CHM电子书界面  

1
 

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

  目录窗体BookIndexForm

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

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

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

/// <summary>        
/// 添加文件夹        
/// </summary>        
/// <param name="sender"></param>        
/// <param name="e"></param>        
private void AddFolderToolStripMenuItem_Click(object sender, EventArgs e)
        {            
TreeNode node
= this.tvIndex.SelectedNode;
//选中的节点                        
//查看是否是根节点或是目录节点            CHMNodeList list = this.GetNodeList(node);            
if (list == null || list.Count==0)            
{              
MessageBox.Show(
"请选择根节点或是目录节点");      
         return;        
    }            
//创建新的节点          
CHMNode newNode
= new CHMNode();
newNode.Name
= "新建文件夹";          
newNode.ImageNo
= "0";            
//newNode.Nodes = new CHMNodeList();            
list.Add(newNode);            
System.Windows.Forms.TreeNode node2
= new TreeNode(newNode.Name);            
node2.Tag
= newNode;         
node2.ImageIndex
= 0;            
node2.SelectedImageIndex
= 0;            
node.Nodes.Add(node2);
//将新节点添加到树中            
node.ImageIndex
= 0;           
node.SelectedImageIndex
= 0;            
this.tvIndex.SelectedNode
= node2;      
}

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

/// <summary>        
/// 添加文章        
/// </summary>        
/// <param name="sender"></param>        
/// <param name="e"></param>        
private void AddArticleToolStripMenuItem_Click(object sender, EventArgs e)        {            
TreeNode node
= this.tvIndex.SelectedNode;            CHMNodeList list = this.GetNodeList(node);            
if (list == null)            
{                
MessageBox.Show(
"请选择根节点或是目录节点");
                return;          
}          
EditForm frmEdit
= new EditForm();
frmEdit.ShowDialog(this);            
CHMNode NewNode
= frmEdit.Node;            
if(NewNode==null)            
{          
      return;            
}            
list.Add(NewNode);            
System.Windows.Forms.TreeNode node2
= new TreeNode(NewNode.Name);            
node2.Tag
= NewNode;            
node2.ImageIndex
= 1;            
node2.SelectedImageIndex
= 1;            
node.Nodes.Add(node2);
//将新节点添加到树中          
node.ImageIndex
= 0;            
node.SelectedImageIndex
= 0;            
this.tvIndex.SelectedNode
= node2;                  
}

 

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

  实现方法如下:

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

private void ReNameMToolStripMenuItem_Click(object sender, EventArgs e)        
{            
this.tvIndex.SelectedNode.BeginEdit();        
}        
private void tvIndex_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)        
{            
this.tvIndex.SelectedNode.Name
= e.Label;            
TreeNode node
= this.tvIndex.SelectedNode;            
if (node.Tag is CHMDocument)            
{                
((CHMDocument)node.Tag).Title
= e.Label;            
}            
if (node.Tag is CHMNode)
            {                
((CHMNode)node.Tag).Name
= e.Label;            
}        
}

 

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

/// <summary>        
/// 删除节点      
/// </summary>        
/// <param name="sender"></param>        
/// <param name="e"></param>        
private void DeleteDToolStripMenuItem_Click(object sender, EventArgs e)        
{            
System.Windows.Forms.TreeNode node
= this.tvIndex.SelectedNode;
//获取要删除的节点            
if (node == null)            {                MessageBox.Show("请选择一个节点");              
return;            
}
System.Windows.Forms.TreeNode parent
= node.Parent;
//获取该节点的父节点            
if (parent == null)          
{              
MessageBox.Show(
"请选择目录或页面节点");              
return;
           }  
CHMNodeList list
= GetNodeList(parent);            
if (MessageBox.Show("是否删除文章(删除同时删除本地文件)?","确认", MessageBoxButtons.YesNo)== System.Windows.Forms.DialogResult.Yes)          {                
list.Remove((CHMNode)node.Tag);                
//同时删除文件              
if (System.IO.File.Exists(((CHMNode)node.Tag).Local))
{                    
System.IO.File.Delete(((CHMNode)node.Tag).Local);            
   }              
node.Remove();
//移除该节点                
}        
}

 

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

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

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

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

  具体实现代码如下:

/// <summary>        
/// 查询(修改为在点击查询的时候再去索引什么)        
/// </summary>        
/// <param name="sender"></param>        
/// <param name="e"></param>        
private void btnSearch_Click(object sender, EventArgs e)        
{            
//INDEX_STORE_PATH 为索引存储目录            
string INDEX_STORE_PATH = Application.StartupPath+@"\index";              
//先存储索引            
IndexWriter writer
= new IndexWriter(INDEX_STORE_PATH, new StandardAnalyzer(), true);            
SetIndex(writer,this.nodes);            
//在从索引中查询            
string KEYWORD = this.txtKeyWords.Text.ToString();//关键字            
IndexSearcher searcher;             try            
{              
searcher
= new IndexSearcher(INDEX_STORE_PATH);                
QueryParser q
= null;                
if (rbByTitle.Checked)                
{                    
q
= new QueryParser("title", new StandardAnalyzer());                
}              
else if (rbByKeywords.Checked)                
{                    
q
= new QueryParser("keywords", new StandardAnalyzer());                
}                
else if (rbByAll.Checked)                
{                    
q
= new QueryParser("contents", new StandardAnalyzer());                
}                                
Query query
= q.Parse(KEYWORD);                Hits hits = searcher.Search(query);                
//创建DataTable用于绑定                
DataTable dtResult
= new DataTable();                
DataColumn dc1
= new DataColumn("Title", Type.GetType("System.String"));                
DataColumn dc2
= new DataColumn("KeyWords", Type.GetType("System.String"));                
DataColumn dc3
= new DataColumn("Content", Type.GetType("System.String"));                
DataColumn dc4
= new DataColumn("FilePath", Type.GetType("System.String"));                
dtResult.Columns.Add(dc1);                
dtResult.Columns.Add(dc2);                
dtResult.Columns.Add(dc3);                dtResult.Columns.Add(dc4);                
if (hits != null && hits.Length()>0)                
{                    
for (int i = 0;
i
< hits.Length(); i++)                    
{                        
Document doc
= hits.Doc(i);                        
DataRow dr
=dtResult.NewRow();                        
dr[
"Title"] = doc.Get("title");//文章标题                        
dr[
"KeyWords"] = doc.Get("keywords");//文章关键字                        
dr[
"Content"] = doc.Get("contents");//内容                        
dr[
"FilePath"] = doc.Get("filename");//文件路径                        
dtResult.Rows.Add(dr);                  
}                    
this.tcList.SelectedIndex
= 1;                    
this.dgvResult.DataSource
= dtResult;                    
this.dgvResult.Columns[
"FilePath"].Visible = false;              
}                
else              

{                    
MessageBox.Show(
"没有查到相关记录!");                
}                
searcher.Close();          
  }          
catch (Exception ex)  
          {                
LogHelper.WriteLog(ex.Message);      
     }  
     }

 

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

/// <summary>        
/// 遍历chmDocument,将节点存储为索引        
/// </summary>        
private void SetIndex(IndexWriter writer, CHMNodeList nodes)        
{            
if (this.nodes == null || this.nodes.Count == 0)            
{  
              return;        
   }            
foreach (CHMNode n in nodes)
            {              
//if (node.Nodes==null)              
if (n.ImageNo == "1")
//使用imageNo来判断
               {                  
IndexFile(n, writer);                
}              
else          
     {                  
SetIndex(writer,n.Nodes);              
}          
}          
writer.Close();
// 关闭writer      
}
private void IndexFile(CHMNode node, IndexWriter writer)        
{            
try            
{                
Document doc
= new Document();                
doc.Add(
new Field("filename", node.Local,
Field.Store.YES,
Field.Index.TOKENIZED));                
doc.Add(
new Field("title", node.Name, Field.Store.YES, Field.Index.TOKENIZED));                
doc.Add(
new Field("keywords", node.KeyWords,
Field.Store.YES,
Field.Index.TOKENIZED));                
doc.Add(
new Field("contents", new StreamReader(node.Local,
System.Text.Encoding.Default)));                writer.AddDocument(doc);            
}          
catch (FileNotFoundException fnfe)            {                
LogHelper.WriteLog(fnfe.Message);          
}              
}

 

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

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

 

  附件下载 AlexisEditor RC版

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

0
相关文章