天天看點

C#生成CHM檔案(應用篇)之代碼庫編輯器(5)【總結、程式、源代碼】

經曆了快一個月的開發(因為都是在閑暇時間做的,實際實際可能不到一周),AlexisEditor總算完成了。

這邊說明一下為什麼有些網友不能新增文章的問題。

原因是路徑中有中文字元,我一直用的是英文系統,是以沒有出現這樣的情況。感謝網友國中生的net夢 在xp、.Net2.0 平台下的測試 ,發現了這個bug。

具體來說一下這個bug,是因為WebBrowser導航發生改變的時候會對Uri進行編碼,而我們這邊不需要進行編碼,于是就可以用反編碼就行了。

<a target="_blank" href="http://blog.51cto.com/attachment/201105/104051672.jpg"></a>

解決方案如下

 //特别注意,如果路徑中有中文,url會對其進行編碼

if (System.Web.HttpUtility.UrlDecode(e.Url.AbsolutePath.ToString()).Replace('/', '\\') == saveUrl) 

再次更新下程式(也許是最後一次更新了)

<a target="_blank" href="http://blog.51cto.com/attachment/201105/2621421_1306377846.rar">源代碼下載下傳(vs2010版)</a>

<a target="_blank" href="http://blog.51cto.com/attachment/201105/2621421_1306377919.rar">源代碼下載下傳(vs2005版)</a>

<a target="_blank" href="http://blog.51cto.com/attachment/201105/2621421_1306377948.rar">程式下載下傳(XP版,如果你的IE版本是6.0,請下載下傳此版本) </a>

呵呵,如果覺得好的話,請推薦之!

下面将我這個系列遇到的問題和經驗總結下,然後着手學習WPF和SilverLight的知識,希望裡面的知識點能夠對你有幫助。

篇幅可能有點長,為了友善起見,增加導航

 一、在WinForm實作類似CSS Sprites(CSS圖像拼合技術)

 二、WebBrowser控件的使用技巧

 三、XML的妙用之存儲樹

 四、Visual Studio界面風格WinForm實作

 五、 WinForm中的狀态欄初探

 六、 C#調用系統的cmd指令

 七、 TreeView節點重命名

 八、DataGridView中的一些技巧

 九、Lucene.Net簡單的應用

 十、簡易版的log類

一、在WinForm實作類似CSS Sprites(CSS圖像拼合技術)       

在WinForm我們會用到許多的小圖檔,可能要求是ico格式的,而且像素一般是16*16的,如果将這麼多的ico圖檔放在一個檔案夾裡,當然 是可以,不過,如果一張圖檔2k,那麼50張圖檔就是100k,浪費空間。我們可以像web那樣做,将許多圖檔拼合到一張圖檔中,然後寫一個靜态類來調用 圖檔中的第幾個圖形。

如下圖一張480*16 bmp格式的圖檔(示範需要,放大了)

<a target="_blank" href="http://blog.51cto.com/attachment/201105/105139874.jpg"></a>

設定圖檔的背景為比較明顯的顔色(為了後面顯示透明),總共就有30個圖形,于是我們就可以周遊然後将圖檔存到一個List中了,詳細代碼如下:

System.Resources.ResourceManager resource = new System.Resources.ResourceManager("AlexisEditor.Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());  

            Bitmap bitmap = (Bitmap)resource.GetObject("bookicons");  

            //将加載的位圖的圖檔提取出來,并放在list中  

            imageList = new ImageList();  

            iconList = new List&lt;Icon&gt;();  

            for (int i = 0; i &lt; bitmap.Width / 16; i++)  

            {  

                Bitmap img = bitmap.Clone(new Rectangle(16 * i, 0, 16, 16), bitmap.PixelFormat);//切割圖示  

                img.MakeTransparent(Color.Magenta);//設定過濾色  

                imageList.Images.Add(img);  

                System.IntPtr iconHandle = img.GetHicon();  

                System.Drawing.Icon icon = Icon.FromHandle(iconHandle);  

                iconList.Add(icon);  

            }  

首先從資源檔案中擷取名為bookicons的位圖,然後周遊,将每個圖形存入到imageList中或是自定義的List,

這邊給出了如何将bmp圖檔轉換為Icon圖檔的代碼

System.IntPtr iconHandle = img.GetHicon();                   

System.Drawing.Icon icon = Icon.FromHandle(iconHandle);  

整個IconHelper類的代碼如下:

private static ImageList imageList;  

       private static List&lt;Icon&gt; iconList;  

       static IconHelper()  

       {  

           System.Resources.ResourceManager resource = new System.Resources.ResourceManager("AlexisEditor.Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());  

           Bitmap bitmap = (Bitmap)resource.GetObject("bookicons");  

           //将加載的位圖的圖檔提取出來,并放在list中  

           imageList = new ImageList();  

           iconList = new List&lt;Icon&gt;();  

           for (int i = 0; i &lt; bitmap.Width / 16; i++)  

           {  

               Bitmap img = bitmap.Clone(new Rectangle(16 * i, 0, 16, 16), bitmap.PixelFormat);//切割圖示  

               img.MakeTransparent(Color.Magenta);//設定過濾色  

               imageList.Images.Add(img);  

               System.IntPtr iconHandle = img.GetHicon();  

               System.Drawing.Icon icon = Icon.FromHandle(iconHandle);  

               iconList.Add(icon);  

           }  

       }  

       public static Image GetBuildImage()  

           return (Image)resource.GetObject("Build");  

       /// &lt;summary&gt; 

       /// 書籍ico圖示  

       /// &lt;/summary&gt; 

       public static Icon BookIcon  

         get { return iconList[0]; }  

       } 

二、WebBrowser控件的使用技巧

 設定目前的Uri位址

this.wbEditor.Url = new Uri(startPath + @"\CSDN_UBB\normalTemp.htm"); 

擷取目前WebBrowser中的文檔

HtmlDocument hd = this.wbEditor.Document;//擷取文檔資訊 

擷取目前Dom文檔中指定Id的元素

HtmlElement he = hd.GetElementById("content"); 

擷取指定元素的值,比如擷取TextArea中的值

首先添加mshtml的引用

IHTMLDocument2 doc = (IHTMLDocument2)this.wbEditor.Document.DomDocument;mshtml.HTMLInputElement text1 = (HTMLInputElement)doc.all.item("content"); 

 背景調用頁面中已有的js函數

((mshtml.HTMLDocumentClass)webBrowser.Document).parentWindow.execScript( "func()", "JScript" ); 

具體的應用可以參考我的源代碼

程式中使用XML存儲電子書的目錄資訊,友善hhc.exe編譯為CHM電子書。

在.Net中有現成的類來操作XML,即System.Xml.XmlDocument, 他表示一個XML文檔(XML基本知識我就不介紹了),XML有根節點,根節點裡面可以有子節點,節點有屬性等等。

可以使用XmlDocument的Load方法來将一個XML文檔加載到記憶體中,如下代碼:

System.Xml.XmlDocument doc = new System.Xml.XmlDocument();  

doc.Load(filename);  

然後擷取xml根節點裡面的一些資訊,根節點下面才是書籍目錄的資訊

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);  

                }  

            }  

        }   

 //xml轉為為nodes  

        private void NodesFromXML(CHMNodeList nodes, System.Xml.XmlElement RootElement)  

                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");  

                    nodes.Add(NewNode);  

                    foreach (System.Xml.XmlNode node2 in element.ChildNodes)  

                    {  

                        if (node2.Name == "Items")  

                        {  

                            NodesFromXML(NewNode.Nodes, (System.Xml.XmlElement)node2);  

                        }  

                    }  

        } 

實作步驟(一個小的Demo):

1.建立WinForm項目,取名為DockDemo, 

2.在工具欄中添加工具,導向 WeifenLuo.WinFormsUI.Docking.dll

3.建立MainForm,将DockPanel拖到MainForm中 ,設定其Dock屬性為Fill

4.建立SolutionForm,将其繼承有Form改為 WeifenLuo.WinFormsUI.Docking.DockContent

5.在MainForm的構造函數中執行個體化SolutionForm,代碼如下

SolutionForm  form=new SolutionForm();  

form.Show(dockPanel);//顯示目錄窗體  

form.DockTo(dockPanel, DockStyle.Right);   

同時,我們看到visual studio中,将ToolBox關閉掉可以點選 工具欄中的圖示重新調用,我們可以設定SolutionForm的屬性HideOnClose為True,即點選關閉時并不是真正的釋放,而是隐藏起來。重新顯示調form.Show(dockPanel);即可

用過Visual Studio的程式員都知道,Visual Studio下方的狀态欄提供了各種各樣的狀态給開發者,使得開發者能夠實時知道Visual Studio現在處于什麼狀态。

 代碼如下,建立ToolStripStatusLabel 對象的執行個體,添加到StatusStrip中,當編譯完後移除。(有更好的方法歡迎指導)

ToolStripStatusLabel tsl = new ToolStripStatusLabel();  

            tsl.Text = "正在編譯....";  

            ToolStripStatusLabel tslBuilding = new ToolStripStatusLabel();  

            tsl.Image = IconHelper.GetBuildImage();  

            tsl.Dock = DockStyle.Right;  

            this.statusStrip.Items.AddRange(new ToolStripItem[] { tsl,tslBuilding });  

            this.statusStrip.Text = "正在編譯...";  

            chmDocument.Compile();  

            frmOutPut.TxtOutput.Text = chmDocument.OutPutText;  

            this.statusStrip.Items.Remove(tsl);  

            this.statusStrip.Items.Remove(tslBuilding);ToolStripStatusLabel tsl = new ToolStripStatusLabel();  

            this.statusStrip.Items.Remove(tslBuilding); 

 我們有時候需要在C#中調用一些command指令,如點選Label打開浏覽器。AlexisEditor程式中編譯為CHM電子書的功能就是 調用系統自帶(一般正版的Windows系統都會自帶,其他版本的Windows可能會閹割掉)的hhc.exe,并且可以獲得目前的編譯情況

Process helpCompileProcess = new Process();  //建立新的程序,用Process啟動HHC.EXE來Compile一個CHM檔案

ProcessStartInfo processStartInfo = new ProcessStartInfo();  

               processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;  

               processStartInfo.FileName = config.HhcPath;  //調入HHC.EXE檔案   

               processStartInfo.Arguments = "\"" + strHhp + "\"";//擷取空的HHP檔案  

               processStartInfo.UseShellExecute = false;  

               processStartInfo.CreateNoWindow = true;  

               processStartInfo.RedirectStandardOutput = true;  

               helpCompileProcess.StartInfo = processStartInfo;  

               helpCompileProcess.Start();  

               helpCompileProcess.WaitForExit(); //元件無限期地等待關聯程序退出   

    string _outPutText = helpCompileProcess.StandardOutput.ReadToEnd();//程序中的資訊 

有時候我們需要重命名TreeView的節點,實作代碼如下:

首先設定TreeView的LabelEdit屬性為True,然後在觸發的事件中,設定選中的節點為編輯狀态,接下來就是編輯後觸發的事件即AfterLabelEdit 。

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;  

程式中使用DataGridView顯示查詢的結果,因為查詢的結果是自己拼湊起來的,即使用Lucene.Net檢索到多少内容,然後拼湊為DataTable,最後在綁定到

DataGridView中,因為要在DataGridView中增加連結,使得點選連結可以打開文章進行編輯,是以在前台DataGridView可視化界面中增加LinkLabel的Column。

并設定它的DataPropertyName設為背景DataTable中的名字,代碼如下

//建立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); 

注意,如果在可視化界面中不設定列的DataPropertyName屬性,運作後會出現多列,有點類似于GridView中的AutoGenerateColumn屬性

然後周遊索引檢索到的條數,将内容添加到DataTable中

if (hits != null &amp;&amp; hits.Length()&gt;0)  

      for (int i = 0; i &lt; 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("沒有查到相關記錄!");  

使用this.dgvResult.DataSource = dtResult;綁定資料源

下面來看看如何擷取DataGridView選中行中所有列的資料,當我們點選超連結時會觸發CellContentClick事件,我們在這個事件中擷取選中行的

private void dgvResult_CellContentClick(object sender, DataGridViewCellEventArgs e)  

            //點選的是超連結  

            if (e.ColumnIndex==0)  

                //擷取目前行的檔案名  

                string path=this.dgvResult.CurrentRow.Cells[3].Value.ToString();  

                //調用編輯視窗  

                GetNode(path,this.nodes);  

                CHMNode node = this.nodeOpen;  

                EditForm form = new EditForm();  

                form.Edit(node);  

                form.ShowDialog();  

這句話就是擷取我們要打開文章

string path=this.dgvResult.CurrentRow.Cells[3].Value.ToString(); 

程式中使用了Lucene.Net來搜尋文,原因是因為Lucene.Net支援全文檢索,即我們可以輸入關鍵字,在我們寫的文章中查詢有沒有比對的詞。

思路是這樣的,選擇檢索方式:标題檢索、關鍵字檢索、全文檢索,每個檢索都會搜尋不同的索引。

點選搜尋按鈕的時候生成索引的,擷取關鍵字 ,擷取檢索方式,檢索,顯示搜尋結構。

//INDEX_STORE_PATH 為索引存儲目錄  

string INDEX_STORE_PATH = Application.StartupPath+@"\index";    

//先存儲索引  

IndexWriter writer = new IndexWriter(INDEX_STORE_PATH, new StandardAnalyzer(), true);  

SetIndex(writer,this.nodes); 

而SetIndex是使用遞歸的方法周遊樹,有關寫入Index的代碼如下 

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);  

然後查詢

1.申明一個IndexSearcher 2.設定查詢路徑 3.申明一個QueryParser 4.設定查詢使用的value 5.查詢 6.Hits 對象即查詢結果

//在從索引中查詢              

IndexSearcher searcher;  

searcher = new IndexSearcher(INDEX_STORE_PATH);  

QueryParser q = null;  

q = new QueryParser("title", new StandardAnalyzer());  

Query qquery = q.Parse(KEYWORD);  

Hits hits = searcher.Search(query); 

在寫一寫“危險”代碼時,我們經常使用try。。catch語句進行異常捕獲,而且往往也會忘了對捕獲資訊的存儲。這裡提供一個簡易版的日志記錄類

public static class LogHelper  

public static void WriteLog(string log)  

string path = Application.StartupPath + "\\log.txt";  

FileStream filestream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);  

StreamWriter sw = new StreamWriter(filestream, Encoding.Default);  

sw.BaseStream.Seek(0, SeekOrigin.End);  

sw.WriteLine("*********Exception*********");  

sw.WriteLine("Time:" + DateTime.Now);  

sw.WriteLine("Message:" + log);  

sw.WriteLine();  

sw.Close();  

在捕獲到異常的時候直接調用方法即可

    本文轉自xshf12345 51CTO部落格,原文連結:http://blog.51cto.com/alexis/574473,如需轉載請自行聯系原作者