經曆了快一個月的開發(因為都是在閑暇時間做的,實際實際可能不到一周),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<Icon>();
for (int i = 0; i < 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<Icon> 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<Icon>();
for (int i = 0; i < 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");
/// <summary>
/// 書籍ico圖示
/// </summary>
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 && 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("沒有查到相關記錄!");
使用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,如需轉載請自行聯系原作者