凌晨,被安排在公司值班,因為台風“燦鴻”即將登陸,風力太大,辦公樓,車間等重要部分需要關注。所以無聊,那就分享一下,今天給朋友臨時做的一個小的考試系統輔助工具吧。其實非常小,需求也很簡單,但是可以根據實際需要進行擴充,暫時只實現了一些核心功能。界面丑了點,無所謂,湊合着用吧。
1.考試系統輔助需求
上午10點一個朋友緊急求助,單位要進行在線測評,開卷考試,題庫以及答案已經發給他們了,但是太多,好幾百道題目,翻資料都來不及。問我能不能做一個軟件,能夠快速填充答案或者找到題目,節省時間,提高准確率。經過半個小時的QQ溝通,基本明確了大概要做的,由於時間緊急,晚上就要用,盡量搞簡單的吧。總結下來,有這么幾個需求:
-
要能快速導入題庫,幾百道題,手動添加得多長時間,不敢想象,再說了,會寫點程序就是要減輕工作量;
-
要盡量自動化,直接填充答案是不可能了,不是技術上不可行,是時間來不及,在線考試的頁面都沒見到;
-
題庫類型比較多,有填空題,單選題,多選題以及判斷題,要盡量區分,第一時間找到原題和答案;
2.簡單思考后的思路
拿到需求,雖然長時間沒搞WinForm,但簡單的東西還是會的。腦子里第一個飄出的思路就是:
-
使用Aspose.Words快速導入題庫,成熟好用,以前接觸過一點點;
-
由於給定的題庫和答案已經有了,暫時就不用區分題目類型了,直接找到題目就找到了答案,然后手動填寫就好了
-
題庫數據用什么保存?顯然用啥MSSQL,Mysql都是扯淡了,那Sqlite呢?No,No,No,前不久剛剛接觸過開源的.NET NoSQL數據庫LiteDB,為啥不嘗鮮呢?使用文章看這里:
.NET平台開源項目速覽(3)小巧輕量級NoSQL文件數據庫LiteDB
.NET平台開源項目速覽(7)關於NoSQL數據庫LiteDB的分頁查詢解決過程
4.怎么更快的找到題目呢?難道要手動復制,再粘貼到界面搜索?有點慢,那就先省略一步吧,復制題干部分文字后,直接點界面搜索,然后自動獲取剪切板的數據,自動進行搜索,也就是1個題目要2次操作,復制和點擊搜索。雖然想到了 監視剪切板,但第一次的思路里面考慮到時間,就沒往下想。
5.如何搜索呢?直接暴力點吧,也就幾百道題,就算幾千道也應該不成問題,直接題干字段對比是否包括 選擇的文字部分,然后搜索,彈出相應的題目;
6.考慮到有可能一些文字(復制題干的時候不是全部復制,部分復制就可以了)包含在多個題目中,要至少考慮2個吧,否則就題目復制的時候稍微多一點,減少重復的概率;
雖然上面是簡單的思路,但實際上最終不完全是這樣子,經過實際的修補,不斷調整方向,有一些改動:
1.Aspose.Words還是不要用了,過程處理有點繁瑣,直接搞一個導入題庫的界面,文本框和按鈕,將題目手動復制進去,幾分鍾都不需要的事情,何必用Aspose.Words搞來搞去;
2.雖然區分類型很簡單,開始打算不做,后面還是做了,因為也的確比較簡單吧;
3.實現過程與代碼
為配合LiteDB數據庫的操作,寫了個簡單的題目實體類,包括簡單幾個字段,如下所示代碼:
public class Problem { /// <summary>題目編號,可以重復,注意不能使用Id</summary> public Int32 ProbId { get; set; } /// <summary>整個題目內容,同時包括選項</summary> public String ProbText { get; set; } /// <summary>答案,一般是題干答案中的東西</summary> public String Answer { get; set; } /// <summary>題目類型</summary> public String TypeName { get; set; } }
3.1 實現過程之數據導入
數據導入界面很簡單,選擇題目類型,然后復制進去,因為已經拿到Word版的題庫了,很規則,由於朋友單位內部資料,題庫不分享,自己到百度找了一個差不多格式的試卷,如下:
復制的時候,直接復制題目(要包括編號)即可,前面的 單項選擇題,類型就不要復制了,手動選一下吧。看看界面:
代碼很簡單,唯一要注意的是,對於選擇題來說,如何區分不同題目是個難點,開始沒注意這個問題,因為填空題以及判斷題都可以通過換行符來確定,換行了就是一道新題目。但是這個選擇題就尷尬了,有幾次換行,而且還不固定,最后觀察到題目的編號是一個可以利用的東西,每一次題目前都是 數字+、號,用這個來區分吧。看看實現代碼,這是最終的版本,中間修改的就不說了,涉及到LiteDB的簡單操作,可以參考上面的文章或者下面的核心導入按鈕的代碼:
//獲取題目類型 var typeName = comboBox1.Text.Trim(); using (var db = new LiteDatabase("Prob.db")) { var col = db.GetCollection<Problem>("Problem"); //先換行分割 var prolist = textBox1.Text.Trim().Split(new String[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); Problem model ; Int32 index = 0; //題目列表,多行的每次讀最后一個 List<Problem> modelList = new List<Problem>(); //根據每行的第一個字符和、號進行區分,如果不是新題目,就作為選項添加到上一個題目中去 while (index <prolist.Length ) { var item = prolist[index]; if (String.IsNullOrEmpty(item)) continue; var titles = item.Trim().Split('、'); if (titles.Length > 0) { int number; if(Int32.TryParse(titles[0],out number)) { //是數字,添加 model = new Problem(); //如果分割第1個是數字,則說明是1個新的題目 model.ProbId = number; model.ProbText = item; model.TypeName = typeName; //確定答案,答案在()里面,把括號里面是字符串分解並組合 var ans = item.Split('(', ')'); if (ans.Length < 2) model.Answer = string.Empty; if (ans.Length > 1) model.Answer = ans[1]; if (ans.Length > 3) model.Answer = ans[1] + "\r\n" + ans[3]; modelList.Add(model); } else { //不是數字,就添加到前一個實體中去,並更新題目內容 modelList.Last().ProbText += ("\r\n" + item); } } else { //不是數字,就添加到前一個實體中去,並更新題目內容 modelList.Last().ProbText += ("\r\n" + item); } index++; } col.InsertBulk(modelList); }
當然每個人遇到的題庫格式不一樣,自己在這里修改唄。。。我也沒時間做個通用的。。反正是給朋友幫忙的東西。順便分享給大家解決問題的思路。
3.2 實現過程之搜索主界面
搜索界面也是很簡單,第一次實現的時候沒有考慮監視ctrl+c,只是想點擊搜索,自動獲取剪切板內容進行搜索。再就是要注意多條記錄的問題,只顯示2條吧,如果找不到相同的,就復制多一點題目部分。每一個題目顯示題目類型,答案,如果為空就顯示手動選擇檔案。界面如下:
核心代碼如下:
//每次搜索前都要清空其他控件 txtDisp1.Text = String.Empty; txtDisp2.Text = String.Empty; lblAnswer1.Text = String.Empty; lblAnswer2.Text = String.Empty; try { //獲取剪切板數據 IDataObject iData = Clipboard.GetDataObject(); if (iData.GetDataPresent(DataFormats.Text)) txtKeyValue.Text = ((String)iData.GetData(DataFormats.Text)).Trim(); //查找滿足關鍵詞的題目 var models = list.Where(n => n.ProbText.Contains(txtKeyValue.Text)).ToList(); //最多只顯示2個包括選擇關鍵詞的題目 if (models.Count == 1) { lblTypeName1.Text = models[0].TypeName; lblAnswer1.Text = String.IsNullOrEmpty(models[0].Answer) ? "題目尋找答案" : models[0].Answer; txtDisp1.Text = models[0].ProbText; } if (models.Count >= 2) { lblTypeName1.Text = models[0].TypeName; lblAnswer1.Text = String.IsNullOrEmpty(models[0].Answer) ? "題目尋找答案" : models[0].Answer; txtDisp1.Text = models[0].ProbText; lblTypeName2.Text = models[1].TypeName; lblAnswer2.Text = String.IsNullOrEmpty(models[1].Answer) ? "題目尋找答案" : models[1].Answer; txtDisp2.Text = models[1].ProbText; } } catch(Exception err) { MessageBox.Show("出現錯誤,重啟后再試!"); }
3.3 監視復制Ctrl+C
因為還有時間,所以就想盡量改善點,然后有去百度如何 監視剪切板,貌似要用到win api,學藝不精,搞不來,看到一篇文章監視ctrl+c,其實也是個不錯的方案,一步步來嗎,哪位童鞋有C# 直接監視剪切板的 代碼,希望分享一下。
我用到的代碼來源於這里:http://www.cnblogs.com/over140/archive/2007/11/05/934452.html
自己稍微修改下就OK了,不詳細解釋了。
3.4 塞翁失馬-手賤的教訓
這個完整的東西前后共用了大約4個小時整的時間。坑爹的是中午寫的第一版代碼,讓我手動刪除目錄給刪錯了。。。后來改進的時候,無奈又重新寫了一下,其實思路都有,代碼比較簡單。
以前習慣頻繁更新SVN的,這次失手,算教訓吧,幸好不是啥重要代碼。另外,塞翁失馬焉知非福,重新寫也有好處,不用看改得亂七八糟的代碼,一次到位,代碼思路也清晰多了,初版本代碼這里有的是測試,臨時使用。。。等等。算是一種安慰吧。
4.使用方法
使用方法這再次簡單介紹一下,先手動復制題庫到導入界面,進行題庫導入;然后啟動進行搜索,可以將在線考試的頁面和該軟件同時打開,分屏放置,通過Ctrl+C或者手動復制,搜索也可以進行,查詢到題目后,對照答案填寫,然后循環使用。如果沒有答案,那就看原題,原題一般是有答案的,除非格式有變動,無法轉換。
5.資源
代碼托管到Github吧,我不會告訴你地址在這里的:https://github.com/asxinyu/ExamSystem/
不知不覺,抽了幾根煙,已經過去1個多小時了,“燦鴻”已經越來越近,辦公樓窗戶以及牆體滲水一塌糊塗,去搶險去了,搶險完了,要去睡覺,希望醒來的時候,看到大伙的點贊呢。