今天,我釋出了CloudNotes的一個更新版本:1.0.5484.36793。這個版本與1.0.5472.20097不同的是,它擁有增強的筆記列表,與之前單調的列表系統相比,新的筆記列表不僅可以顯示筆記的摘要內容,而且還可以從筆記中抽取第一張圖片,並顯示圖片的詳細資訊:
怎麼樣?相比之前的筆記列表,現在的設計是不是能夠展示更豐富的資訊呢?
升級到最新版本
如果在讀完我的第一篇關於CloudNotes的文章,《CloudNotes:一個雲端個人筆記系統》之後,已經安裝並體驗了上一個版本的CloudNotes,那麼,當你重新開啟CloudNotes桌面客戶端時,你將在登入介面,或者主介面的狀態列部分看到發現新版本更新的通知資訊:
你可以點選這個資訊來進入新版本更新系統。然而不幸的是,更新系統會告訴你,更新失敗,原因是因為你開啟了Windows 7的使用者帳戶控制(UAC)的功能,更新程式不具備訪問C:\Program Files (x86)目錄的許可權。解決方案有如下兩種:
- 進入控制皮膚,關閉使用者帳戶控制(UAC)功能
- 【點選這裡】下載CloudNotes更新程式的補丁包,下載以後,將zip檔案解壓並覆蓋CloudNotes安裝目錄下的Updater目錄中(比如:C:\Program Files (x86)\daxnet\CloudNotes Desktop Client)的所有檔案,然後重新通過CloudNotes桌面客戶端進入升級程式,即可完成升級
如果你是第一次看到有CloudNotes這麼個玩意兒,並且打算嘗試使用的話,請直接【點選這裡】下載版本1.0.5484.36793,它是截止到本文撰寫時的最新版本,包含了這個最新的日誌列表和修復的CloudNotes更新程式。
技術實現
在此大致介紹一下我是如何實現這種全新的筆記列表介面的,並簡要介紹一下如何讓應用程式在UAC下具有訪問檔案系統資源的許可權。
增強的筆記列表
說起來也有趣,有一天我看到有個同事在用Evernote,覺得它的筆記列表檢視做得挺不錯,還有縮圖:
給人的感覺就是能夠在簡單的檢視中體現更多的筆記資訊,讓人更容易地找到需要檢視的筆記內容。於是我也在想,我的CloudNotes桌面客戶端是否也可以實現類似的效果。
經過一番研究之後,我決定對目前筆記列表所使用的樹形控制元件進行自定義:從System.Windows.Forms.TreeView類繼承一個自定義的TreeViewEx型別,並使用OwnerDrawText的自定義繪畫方式,對每個樹狀節點(TreeNode)進行重繪,以達到類似的效果。
在TreeNode進行重繪時(就是在OnDrawNode事件處理過程中),TreeViewEx會根據當前節點上的TreeNodeExItem資料,對樹狀節點進行重繪,從而達到上面截圖中展示的效果。實現起來其實並不困難,就是需要有耐心,文字的顏色、定位、圖片的尺寸等等,都需要花時間慢慢調整。這部分程式碼我也不多做解釋了,朋友們請自行參考CloudNotes.DesktopClient專案下Controls\TreeViewEx.cs原始碼檔案中的實現即可。相比之下,更為有趣技術點主要有:
從HTML中獲得純文字資訊
這就是用於顯示筆記的摘要文字部分。由於筆記是HTML格式儲存的,而摘要部分卻僅需要筆記文字的開頭一段即可,因此,就需要從HTML中去掉HTML的標記,從中提取可讀的純文字資訊。在CloudNotes中,我是使用下面的擴充套件方法來實現這個功能的。該方法同時被WebAPI和桌面客戶端使用。通過程式碼可以看到,我僅僅將筆記中的前100個文字作為筆記的摘要資訊。
/// <summary> /// Extract the description from the html. /// </summary> /// <param name="html"></param> /// <returns></returns> public static string ExtractDescription(this string html) { var plainText = html.RemoveHtmlTags(); return plainText.Substring(0, plainText.Length < 100 ? plainText.Length : 100); } /// <summary> /// Removes all the HTML tags and bad characters from the given HTML string. /// </summary> /// <param name="html">The source HTML string.</param> /// <returns></returns> private static string RemoveHtmlTags(this string html) { html = HttpUtility.UrlDecode(html); html = HttpUtility.HtmlDecode(html); html = RemoveTag(html, "<!--", "-->"); html = RemoveTag(html, "<script", "</script>"); html = RemoveTag(html, "<style", "</style>"); //replace matches of these regexes with space html = Tags.Replace(html, " "); html = NotOkCharacter.Replace(html, " "); html = SingleSpacedTrim(html); return html; }
提取筆記中的第一張圖片
為了顯示筆記圖片的縮圖,首先需要提取筆記中的第一張圖片,然後再通過影象處理函式產生縮圖。話不多說,直接上程式碼:
/// <summary> /// The regular expression for extracting the Src value from an HTML Img tag. /// </summary> public const string ImgSrcFormatPattern = @"<img[^>]*?src\s*=\s*[""']?([^'"" >]+?)[ '""][^>]*?>"; /// <summary> /// Extract the thumbnail image from the html. /// </summary> /// <param name="html"></param> /// <returns></returns> public static string ExtractThumbnailImageBase64(this string html) { var imageBase64List = html.GetImgSrcBase64FromHtml(); string result = null; if (imageBase64List != null && imageBase64List.Any()) { result = imageBase64List.First(); } return result; } /// <summary> /// /// </summary> /// <param name="html"></param> /// <returns></returns> private static IEnumerable<string> GetImgSrcBase64FromHtml(this string html) { var matchesImgSrc = Regex.Matches(html, Constants.ImgSrcFormatPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); if (matchesImgSrc.Count == 0) return null; List<string> result = new List<string>(); foreach (Match m in matchesImgSrc) { var href = m.Groups[1].Value; var pos = href.IndexOf("base64,", StringComparison.InvariantCultureIgnoreCase); pos += 7; result.Add(href.Substring(pos, href.Length - pos).Trim()); } return result; }
縮圖的產生
縮圖的產生程式碼是在TreeViewEx控制元件中實現的,基本思路其實很簡單,就是根據圖片的長寬進行等比縮小即可。程式碼如下:
private static Image FixedSize(Image imgPhoto, int width, int height, Color clearColor) { int sourceWidth = imgPhoto.Width; int sourceHeight = imgPhoto.Height; int sourceX = 0; int sourceY = 0; int destX = 0; int destY = 0; float nPercent; float nPercentW; float nPercentH; nPercentW = ((float)width / (float)sourceWidth); nPercentH = ((float)height / (float)sourceHeight); if (nPercentH < nPercentW) { nPercent = nPercentH; destX = Convert.ToInt16((width - (sourceWidth * nPercent)) / 2); } else { nPercent = nPercentW; destY = Convert.ToInt16((height - (sourceHeight * nPercent)) / 2); } int destWidth = (int)(sourceWidth * nPercent); int destHeight = (int)(sourceHeight * nPercent); Bitmap bmPhoto = new Bitmap(width, height, PixelFormat.Format24bppRgb); bmPhoto.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution); Graphics grPhoto = Graphics.FromImage(bmPhoto); grPhoto.Clear(clearColor); grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic; grPhoto.DrawImage(imgPhoto, new Rectangle(destX, destY, destWidth, destHeight), new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight), GraphicsUnit.Pixel); grPhoto.Dispose(); return bmPhoto; }
讓應用程式在UAC下具有訪問檔案系統資源的許可權
這是之前版本中CloudNotes桌面客戶端更新程式遇到的一個問題。如果使用者將CloudNotes安裝在系統目錄中,更新程式在試圖更新CloudNotes桌面客戶端的版本的時候,就會遇到錯誤,提示無法更新。根本原因是使用者在Windows系統中啟用了使用者帳戶控制(UAC),即使當前登入系統的帳戶是管理員,也並不代表該使用者對系統中的所有資源都具備管理員許可權。在這種情況下,即使是由管理員啟動的更新程式,也無法將下載並解壓好的檔案複製到系統目錄下。
要解決這一問題,就需要在.NET應用程式的Manifest裡指定requestedExecutionLevel,將其指定為requireAdministrator,於是,在執行更新程式的時候,會彈出以下標準對話方塊,讓使用者對應用程式所作出的行為進行確認:
此時只需點選“是”按鈕即可完成更新。
更改Manifest其實很簡單,只需要在應用程式專案上,通過Visual Studio的“新增專案”對話方塊,即可新增App.manifest檔案,將檔案中的requestedExecutionLevel改為requireAdministrator即可:
有關CloudNotes WebAPI以及桌面客戶端的其它內容,請大家直接上https://github.com/daxnet/CloudNotes站點直接檢視原始碼即可。不懂的地方可以在此留言。
接下來??
我是打算一步步對CloudNotes進行功能和效能完善的,接下來要做的事情還有太多。在每次完成新功能更新時,我都會出部落格文章進行介紹。如果沒有新功能,我也會穿插介紹已有功能的相關技術實現。就目前而言,打算接下來的版本逐步提供以下功能改進:
- 本地快取和伺服器同步系統 - 目前每次開啟和儲存筆記,都是與伺服器直接相連的,不僅增大伺服器負載,而且使用者體驗也不夠流暢
- 外掛系統 - 任何感興趣的朋友都可以通過外掛系統,為CloudNotes桌面客戶端編寫外掛,比如,直接將網頁抓取成日誌等
- 使用者之間的互信和筆記共享服務
- 文件結構檢視 - 通過分析HTML文件,提供層級的文件結構檢視,方便用CloudNotes進行寫作的使用者
等等等等。。。。。。讓我們一起期待吧。。