最近一段時間有用markdown做筆記,其他都好,但是markdown插入圖片挺麻煩的,特別是想截圖之后直接插入的時候。需要首先把圖片保存了,然后還要上傳到一個地方生成鏈接才能插入。如果有個工具可以直接上傳圖片或者截圖生成markdown可以用的鏈接就好了。所以決定自己下班后寫一個,不過自己挺菜的,也就能用,代碼完全是渣不能看。。。在這里把自己的思路還有其中遇到的問題記錄一下。
首先需要選一個圖床,我選了七牛,主要是一個是有免費的空間,加上提供了SDK,這樣就能寫程序上傳了。語言挑了C#,因為感覺WPF寫界面還算方便吧。根據七牛文檔寫得,首先要用nuget下載官方的SDK,這個可以參考http://developer.qiniu.com/code/v6/sdk/csharp.html。
所要完成的功能:能夠選擇圖片上傳,能夠自動上傳截圖,生成markdown可用的鏈接
1、界面
主界面:
設置賬號界面:
2. 選擇圖片上傳功能
點擊中間區域彈出文件瀏覽框,選擇圖片后上傳。這個通過綁定控件的鼠標事件來完成,上傳圖片用到了七牛提供的API。主體函數如下,首先是通過比對文件的hash值來判斷是否本地以及遠程已經有過上傳,如果沒有上傳過,就通過七牛提供的上傳API進行上傳,並在預覽區顯示圖片。

1 private bool UpLoadFile(string filepath) 2 { 3 string filename = System.IO.Path.GetFileName(filepath); 4 Qiniu.Util.Mac lMac = new Qiniu.Util.Mac(UserAccount.AccessKey, UserAccount.SecretKey); 5 string lRemoteHash = RemoteFileInfo.RemoteFileStat(lMac, UserAccount.SpaceName, filename); 6 bool lSkip = false; 7 bool lUpLoadSuccess = false; 8 //check local 9 string lLocalHash = String.Empty; 10 if (!string.IsNullOrEmpty(lRemoteHash)) 11 { 12 lLocalHash = QETag.hash(filepath); 13 14 if (historyLog.ContainsKey(lLocalHash)) 15 { 16 if(historyLog[lLocalHash].Contains(filename)) 17 { 18 lSkip = true; 19 URL = CreateURL(filename); 20 lUpLoadSuccess = true; 21 } 22 } 23 else if (string.Equals(lLocalHash, lRemoteHash)) 24 { 25 lSkip = true; 26 URL = CreateURL(filename); 27 lUpLoadSuccess = true; 28 } 29 } 30 if (!lSkip) 31 { 32 putPolicy.Scope = UserAccount.SpaceName; 33 putPolicy.SetExpires(3600 * 24 * 30); 34 string lUploadToken = Auth.createUploadToken(putPolicy, lMac); 35 UploadManager lUploadMgr = new UploadManager(); 36 lUploadMgr.uploadFile(filepath, filename, lUploadToken, null, new UpCompletionHandler(delegate (string key, ResponseInfo responseinfo, string response) 37 { 38 if (responseinfo.StatusCode != 200) 39 { 40 MessageStr = Properties.Resources.ErrorMessage; 41 } 42 else 43 { 44 MessageStr = Properties.Resources.SuccessMessage; 45 if (historyLog.ContainsKey(lLocalHash)) 46 { 47 historyLog[lLocalHash].Add(filename); 48 } 49 URL = CreateURL(filename); 50 lUpLoadSuccess = true; 51 } 52 })); 53 } 54 55 if (lUpLoadSuccess) 56 { 57 DisplayImage(filepath); 58 MessageStr = URL; 59 } 60 61 return lUpLoadSuccess; 62 }
3. 截圖上傳
為了完成這個,搜了下資料,需要用到win32的兩個函數,分別是AddClipboardFormatListener以及RemoveClipboardFormatListener,然后檢查系統消息是否是WM_CLIPBOARDUPDATE。為了給用戶提供選擇是否開啟這個,在主界面上添加了一個button來控制是否開啟。因為七牛SDK的上傳api的參數是一個文件路徑,所以這里我首先會將截圖存儲到本地再上傳。

1 private void CBViewerButton_Click(object sender, RoutedEventArgs e) 2 { 3 if(!IsViewing) 4 { 5 InitCBViewer(); 6 this.CBViewerButton.Content = "停止監控"; 7 } 8 else 9 { 10 StopCBViewer(); 11 this.CBViewerButton.Content = "監控剪切板"; 12 } 13 } 14 private void InitCBViewer() 15 { 16 WindowInteropHelper lwindowih = new WindowInteropHelper(this); 17 hWndSource = HwndSource.FromHwnd(lwindowih.Handle); 18 19 hWndSource.AddHook(this.WndProc); 20 Win32.AddClipboardFormatListener(hWndSource.Handle); 21 IsViewing = true; 22 } 23 24 private void StopCBViewer() 25 { 26 Win32.RemoveClipboardFormatListener(hWndSource.Handle); 27 hWndSource.RemoveHook(this.WndProc); 28 IsViewing = false; 29 } 30 31 private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParm, ref bool handled) 32 { 33 if(msg == Win32.WM_CLIPBOARDUPDATE) 34 { 35 ProcessClipBoard(); 36 } 37 return IntPtr.Zero; 38 } 39 40 private void ProcessClipBoard() 41 { 42 if(System.Windows.Clipboard.ContainsImage()) 43 { 44 MessageStr = Properties.Resources.UpLoading; 45 46 BmpBitmapEncoder enc = new BmpBitmapEncoder(); 47 enc.Frames.Add(BitmapFrame.Create(System.Windows.Clipboard.GetImage())); 48 49 string lFileName = CreateFileName(); 50 string lSaveFilePath = System.IO.Path.Combine(Properties.Resources.ImageSavePathDir, lFileName); 51 using (FileStream fs = new FileStream(lSaveFilePath, FileMode.OpenOrCreate, FileAccess.Write)) 52 { 53 enc.Save(fs); 54 fs.Close(); 55 } 56 57 //because unkown reason, when use wechat snapshot hotkey, the message will process twice, to avoid this, check whether we save the same file 58 string lLocalHash = QETag.hash(lSaveFilePath); 59 if(historyLog.ContainsKey(lLocalHash)) 60 { 61 File.Delete(lSaveFilePath); 62 URL = CreateURL(historyLog[lLocalHash][0]); 63 lSaveFilePath = System.IO.Path.Combine(Properties.Resources.ImageSavePathDir, historyLog[lLocalHash][0]); 64 } 65 else 66 { 67 if(!UpLoadFile(lSaveFilePath)) 68 { 69 File.Delete(lSaveFilePath); 70 } 71 } 72 } 73 }
PS:在做這個的時候我碰到一個問題就是當我用微信的截圖快捷鍵的時候,這個Clipboard事件會被觸發兩次,為了解決這個,我的做法是判斷本地的上傳記錄,比對文件的hash值。如果已經上傳過,就把后來又存儲的文件給刪除掉。目前我也想不到其他方式,暫且這樣處理吧。
4. 賬號設置
用七牛做圖床需要設置四個參數,分別是目標空間名,Accesskey, secrectkey以及域名。為了方便存儲和讀取,用了C#里的xml序列化和反序列化

1 if (File.Exists(Properties.Resources.ConfigFilePath)) 2 { 3 XmlSerializer xs = new XmlSerializer(typeof(AccountInfo)); 4 using (Stream s = File.OpenRead(Properties.Resources.ConfigFilePath)) 5 { 6 UserAccount = (AccountInfo)(xs.Deserialize(s)); 7 } 8 }

1 using (Stream s = File.OpenWrite(Properties.Resources.ConfigFilePath)) 2 { 3 XmlSerializer xs = new XmlSerializer(typeof(AccountInfo)); 4 xs.Serialize(s, MainWindow.UserAccount); 5 }
5. 未完成功能:歷史記錄查看等
完整代碼:
https://github.com/HaoLiuHust/QiniuUpload
PS: 代碼很爛,還需要多練