前幾篇我已經向大家介紹了如何使用GDI+來繪圖,並做了一個的執行個體,這篇我向大家介紹下如何來做一個類似windows畫圖的工具.
個人認為如果想做一個功能強大的繪圖工具,那麼單純掌握GDI還遠遠不夠,我的目前也只能做一個比較簡單的繪圖工具了.不足之處,歡迎大家討論!
先來看一下最終效果吧:
主要實現功能:畫直線,矩形,橡皮,圓形,切換顏色,開啟圖片,儲存圖片,清除圖片,手動調節畫布大小;軟體剛啟動時,為一張空白畫布,我們可以直接在畫布上繪畫,也可以通過菜單中的“開啟”,匯入一張圖片,然後我們就可以在這張圖片上進行繪製。
平台:VS2005 WINFORM
由於代碼過多,在這裡只簡要介紹下製作步驟,提供大家工程下載.
1.對整個介面進行布局.
2.實現繪圖工具的功能
3.實現顏色拾取的功能,這裡我們直接拿上次寫的自訂控制項來用.
4.實現菜單功能
5.實現手動調節畫布大小的功能
6.測試
實現繪圖工具的功能
為了讓代碼藕合度小點,稍許用了些設計模式,因為不是很會,所以代碼還是有點亂亂的,嘿嘿!關於繪圖工具的這些功能塊全部寫在了DrawTools這個類裡.那麼在主表單中,只需要調用這個類來完成繪製就行了,而不需要過多的涉及到具體的繪圖代碼。繪圖工具這個類提供的主要工具就是:鉛筆、橡皮、直線、矩形、圓形、實心矩形、實心圓形。關於這些功能塊的代碼,並不難,只要大家對認真看過前幾篇內容,那應該都看得懂。
這裡要注意以下幾點:
1.如何防止記錄不必要的繪圖過程中的痕迹?
這個問題在第三篇中有提到過,大家不妨先去看看那一篇。為了讓代碼看起來可讀性高點,我設定了兩個Image變數,finishingImg用來儲存繪圖過程中的痕迹,orginalImg用來儲存已完成的繪圖過程和初始時的背景圖片。
2.這個類如何與主表單進行通訊?
當然如果直接將這些功能塊寫在主表單中自然沒有這個問題。但是那樣代碼會顯得很混雜,如果只是工具代碼出現問題就需要改整個項目。我在這裡通過定義方法和屬性,讓主表單通過給屬性賦值將畫板畫布以及顏色什麼的資訊傳給這個工具類,然後通過調用相應的工具方法來使用這些工具。
3.關鍵屬性
要想讓這些工具能正常使用,必須傳遞給他以下幾樣東西:目標畫板(也就是picturebox),繪圖顏色,原始畫布。
實現菜單功能
這裡就需要我們對檔案的操作有一點瞭解,大家可以去查一下相關資料。
痛點主要就是“開啟”這個功能表項目的實現
我們要實現將開啟後的圖片在修改後重新儲存就必須讓檔案在開啟後就能關閉,否則就會因為檔案開啟而無法覆蓋原檔案。就會導致編譯時間彈出“GDI 一般性錯誤”。所以根據網上其它朋友的做法就是先將開啟的圖片通過GDI+將圖片畫到另一個畫布上,然後及時關閉開啟的圖片和用來繪製該圖片的畫板。
private void openPic_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog();//執行個體化檔案開啟對話方塊 ofd.Filter = "JPG|*.jpg|Bmp|*.bmp|所有檔案|*.*";//設定對話方塊開啟檔案的括展名 if (ofd.ShowDialog() == DialogResult.OK) { Bitmap bmpformfile = new Bitmap(ofd.FileName);//擷取開啟的檔案 panel2.AutoScrollPosition = new Point(0,0);//將捲軸複位 pbImg.Size = bmpformfile.Size;//調整繪圖區大小為圖片大小 reSize.Location = new Point(bmpformfile.Width, bmpformfile.Height);//reSize為我用來實現手動調節畫布大小用的 //因為我們初始時的空白畫布大小有限,"開啟"操作可能引起畫板大小改變,所以要將畫板重新傳入工具類 dt.DrawTools_Graphics = pbImg.CreateGraphics(); Bitmap bmp = new Bitmap(pbImg.Width, pbImg.Height); Graphics g = Graphics.FromImage(bmp); g.FillRectangle(new SolidBrush(pbImg.BackColor), new Rectangle(0, 0, pbImg.Width, pbImg.Height));//不使用這句話,那麼這個bmp的背景就是透明的 g.DrawImage(bmpformfile, 0, 0,bmpformfile.Width,bmpformfile.Height);//將圖片畫到畫板上 g.Dispose();//釋放畫板所佔資源 //不直接使用pbImg.Image = Image.FormFile(ofd.FileName)是因為這樣會讓圖片一直處於開啟狀態,也就無法儲存修改後的圖片 bmpformfile.Dispose();//釋放圖片所佔資源 g = pbImg.CreateGraphics(); g.DrawImage(bmp, 0, 0); g.Dispose(); dt.OrginalImg = bmp; bmp.Dispose(); sFileName = ofd.FileName;//儲存開啟的圖片檔案的詳細路徑,用來稍後能覆蓋這個檔案 ofd.Dispose(); } }
清除映像其實就是用白色填充整個畫布
其它的都比較簡單,這就不具體講了。
實現手動調節畫布大小
網上有人說使用API,但是個人覺得還是使用其它控制項幫忙比較簡單,至少我們還看得懂。
思路:放置一個picturebox1(尺寸為5*5),將它固定在主畫板的右下角,然後改變滑鼠進入時的Cursor為箭頭形狀,設定滑鼠按下移動時的事件,讓該picturebox1 跟隨滑鼠移動。當滑鼠鬆開時,將主畫板的右下角座標調整為picturebox1的座標。
下面來看下代碼:
其中的reSize就是我們用來幫忙的picturebox控制項
private bool bReSize = false;//是否改變畫布大小 private void reSize_MouseDown(object sender, MouseEventArgs e) { bReSize = true;//當滑鼠按下時,說明要開始調節大小 } private void reSize_MouseMove(object sender, MouseEventArgs e) { if (bReSize) { reSize.Location = new Point(reSize.Location.X + e.X, reSize.Location.Y + e.Y); } } private void reSize_MouseUp(object sender, MouseEventArgs e) { bReSize = false;//大小改變結束 //調節大小可能造成畫板大小超過螢幕地區,所以事先要設定autoScroll為true. //但是捲軸的出現反而增加了我們的難度,因為捲軸上下移動並不會自動幫我們調整圖片的座標。 //這是因為GDI繪圖的座標系不只一個,好像有三個,沒有仔細瞭解,一個是螢幕座標,一個是客戶區座標,還個是文檔座標。 //捲軸的上下移動改變的是文檔的座標,但是客戶區座標不變,而location屬性就屬於客戶區座標,所以我們直接計算會出現錯誤 //這時我們就需要知道文檔座標與客戶區座標的位移量,這就是AutoScrollPostion可以提供的 pbImg.Size = new Size(reSize.Location.X - (this.panel2.AutoScrollPosition.X), reSize.Location.Y - (this.panel2.AutoScrollPosition.Y)); dt.DrawTools_Graphics = pbImg.CreateGraphics();//因為畫板的大小被改變所以必須重新賦值 //另外畫布也被改變所以也要重新賦值 Bitmap bmp = new Bitmap(pbImg.Width, pbImg.Height); Graphics g = Graphics.FromImage(bmp); g.FillRectangle(new SolidBrush(Color.White), 0, 0, pbImg.Width, pbImg.Height); g.DrawImage(dt.OrginalImg, 0, 0); g.Dispose(); g = pbImg.CreateGraphics(); g.DrawImage(bmp, 0, 0); g.Dispose(); dt.OrginalImg = bmp; bmp.Dispose(); }
效果如(仔細看白色地區的右下角):
此時就可以通過拖動那個小方塊來調節圖片大小了。
這樣,主要的問題差不多已經解決了,但還是有不足這處,歡迎大家提出寶貴的意見。