加分程式是我為了班上所做的一個計分軟體,視窗中有數字鍵盤,鍵入號碼就可以加分,主要是看在老師常使用電腦,但卻仍然依靠小老師計分數,手癢做出來的小品。
這是最新版本的運行畫面(裡面的數據是亂輸的)。
先丟網址:https://github.com/lancatlin/AddPoint/
歡迎下載!!
我的加分程式主要是為我班導設計,由於上課通常是1分1分慢慢加,所以我的程式是設計輸入號碼,再按加扣分。比較厲害的功能是還有分組計分功能,不過其實它只是將一組的所有成員分數加總,你並不能把分數直接加在「組」上,(所以才說是為班導設計)。歷史紀錄會儲存最近的五十筆,也有返回功能,但是只要重新啟動程式後的紀錄就不能返回(可以用扣回去的)。還有多班級功能,一個程式下方會有很多個班級的檔案資料夾,所以科任老師可以將程式放於隨身碟,到班上電腦執行,這樣在統計時也可以在自己的電腦處理。最後,還有一個(很沒意義的)加密功能,嗯…真的很沒意義,誰會去想篡改這種資料? 不過我就是做了,老師可以設定一組密碼,在每次打開的時候輸入才能進去程式,不過密碼本身也得找地方存吧? 所以你還得「加密」密碼,然後在加密加密密碼的密碼……好沒這回事,加密密碼的密碼就藏在程式碼中了,文章的下一篇將會詳細說明我的加密方法。(程式要被駭了!!!)
Scratch版本
還不會Python之前我只會scratch,想到這個專案時就從scratch開始,功能不多,只有基本的加分和扣分, 可說是完全的「加分程式」,用一個列表儲存分數,12個鍵盤分身(用不同造型,程式內容雷同)輸入號碼,由於scratch沒有存取外部檔案的功能,所以一定得用scratch開,不能夠用匯出swf檔開,而且每次都必須「儲存」,只要離開時沒存到,上次的紀錄就沒了。要輸出檔案的時候,必須用列表的「輸出」才能取得單純分數的文字檔案,實在不方便。
但是這支程式有一個我很滿意之處,就是透過「分身」來製作鍵盤的十二個按鍵,什麼意思呢?這十二個按鍵是用同一支程式運行的,個體差異在於名稱變數所對應到的動作。這樣寫的優點在於可以重複利用程式碼,對於事後維護比較方便,而且十分精簡!
雖說沒有做到很好,不過班導仍然非常開心XD。
Python Tkinter版
到了二上,學會了python,看著舊的版本實在忍不住,於是就著手做了新的Python版本。
我用的是最簡單的GUI:Tkinter(以下簡稱tk),很輕巧也很方便使用,介面也還可以。(就是上方照片的模樣)。下面針對各個文件的程式進行說明。
視窗設計:AddPoint2.py
關於視窗設計,我使用一個ListBox來顯示每位同學的分數,上面裝一個ScrollBar(滑輪?我不清楚中文)來上下拉動。其實一般電腦使用只要滑鼠游標在上頭,滾輪就可以上下移動了,何必裝個ScrollBar?(我真的不是故意想秀英文,而且我也沒東西秀),是為了讓老師在電子白板上能用,電子白板上沒有滑輪,要能夠上下拉動就必須要有ScrollBar。
至於加分的按鍵,就如同上方圖片所看到的,我是用14顆按鈕來做設計。偷偷說個題外話,其實在使用Label或著Button時,不需要寫一個變數去存它,為什麼?因為你根本不會再碰它了!Label是文本標籤,Button是按鈕,文本標籤如果你想改變上面的字,必須要用StringVar來處理,你就算把Label存了也沒用,只是白白浪費你的命名空間而已。Button也是同理,再指定Button的command後,你就不需要理它了,你就只要等待獵物上門按鈕按下,讓函數做對應的動作就好。
一講到題外話,我又想到我過去做Tk的一個愚蠢錯誤,程式碼如下
my_entry = Entry(window, width=8).grid(row = 0, colomn=2)
print(my_entry.get())
大致看起來好像沒什麼問題?編譯跑起來也都對,但是當你使用my_entry.get()時,Python就會溫柔的告訴你:「不好意思,您所指定的變數是一個None喔!」(我開玩笑的,Python編譯器不會說中文),那時候我在那邊傻了整整兩個小時,後來還到Python Taiwan去發問(感謝Python Taiwan社團總是有許多熱心的前輩為人解惑),搞了半天,原來是我在設定Entry變數的時候直接在後面放grid(),這怎麼了嗎?grid是無返回值的呀!grid()是Tk的排版管理員,負責將元件擺放到你設定的位置上,但是本來Entry會返回一個Entry給你,但是如果直接在後面加grid,grid又是沒返回值的函數(就算有也不會是那個Entry),結果你寫好的變數,裡面是空的!(None)
又回到剛才所說的,不要把Label或Button存到變數中,這沒有意義!本來可以一行解決的拖到兩行,而且害你少了一個可以使用的名字。
講一講有點遠了,回到剛才的話題:用14個按鈕來當作鍵盤,說到這邊實在也是有點羞愧,因為我的作法是將每個按鍵的文字存在一個陣列裡,用一個for迴圈製造14個Button(這部份還不錯,自動排列),但是我將每一個Button的command都指定到同一個函數—self.button_up,但是每一個按鍵有不同的功能阿!0~9要用同一個函數沒錯,但是像清除、復原這些,功能差這麼多總該要分開放了吧?我在設定command時,偷偷加了一項參數i,就是for迴圈的變數,用途就是區分「這是第幾顆按鈕」,再利用這些索引對應到的名字用if else做分別的動作。雖然現在看來不喜歡,但是也很難提出更好的作法(我那小小的腦袋想不出來),雖然很醜,但是也簡化了很多動作,雖然很醜…
self.button=['1','2','3','4','5','6','7','8','9','C','0','回復','扣分','加分']
for i in range(14):
Button(f32, text=self.button[i], width=4, height=1, font=_font(30), command=lambda x=i:self.button_up(x))\
.grid(row=i//3, colomn=i%3, padx=9, pady=10)
加了分,那加錯怎麼辦?不要加錯就好啦!要做復原。在每一次的加分動作,都會把這一次的紀錄一起加到self.history裡面,history的詳細內容到下面data時再講,這裡主要講GUI界面。跟score一樣,我使用ListBox來顯示history,並且讓使用者藉由「回復」按鈕來撤銷上一次的動作,沒有撤銷紀錄上限(還是有啦!你做多少動作就是最高的上限嘛),這個部份就跟個人分數一樣啦!
資料處理:data.py
最早的版本是單純計分,並自動存檔。還蠻容易的,只要簡單做些資料處理,再用open()就完成了,不過在資料處理上倒繞了個彎,本來想說可能會需要很多功能,用物件來儲存每個人的分數,後來發現是多次一舉,改成用字典dict來儲存每個人的分數。
說到多此一舉可能還不只這個,我的分數管理是由score_manager物件負責的(後來發現沒有按照命名規則,應該為ScoreManager),有一個陣列裡面有每一個人的資料,將每一個人的分數由一個字典(dict)做儲存,裡面的標籤有name, score,現在回來看實在覺得可笑,既然只有兩個那兩個標籤,幹嘛不直接用dict儲存每個人的資料就好了?像是[1]:5,省下一層陣列還有後面for迴圈的使用。不過這還不是最誇張的,為了這些分數的更新和讀取,我還另外寫了兩個函數update和get,其中get為了格式化字串還寫了落落長的迴圈...看到這邊我實在恨不得回去全部重寫一次。
還記得剛剛提到的history嗎?在data.py也有專門一個物件負責處理history,名字就叫做history(好爛),功能很少,就只有read跟write而已,只有這樣實在沒什麼寫成物件的意義。在程式啟動時,它就會先去讀取資料夾內的history.sc檔案,如果沒有就寫一個新的,這就是read,然後write就是主程序把歷史的陣列傳給它,它再挑出前50寫進文件裡,並且用換行符號相隔。(剛剛接獲通知,我有個地方的路徑寫錯了,難怪第一次啟動都會有個不在班級內的history.sc跑出來)
class history:
def __init__(self, room):
self.path = room+'//history.sc' #設定路徑
def read(self):
try:
with open(self.path, 'r') as file:
data = file.read().split('\n')
print(data)
return data
except FileNotFoundError:
with open('history.sc', 'w') as file: #這裡的路徑寫錯了,應該為self.path
return []
後來因應班上英文課的分組,想說如果能將每組的個人分數加總顯示出來,應該會更好,於是開始製作分組顯示的程式。關於這部份的設計我必須先澄清,小組並沒有「自己的」分數,小組只是將組員個別的分數加總,並顯示出來,你並不能加分加在一個「組」上,可以將它想像成標籤,為每一個組員貼上標籤,並將都有這標籤的組員分數加總顯示。
等等!程式要怎麼知道誰跟誰一組?當然是寫進文件裡阿!在程式執行前,使用者需要先用我的設定工具creat來做各種設定,creat程式後面會詳細說明。
就跟你想的一樣,我寫了一個物件來處理分組的資訊,剛剛看了程式碼,我發現我的team寫成了青少年teen......我的英文真的有待加強(還好班導看不到程式碼)。
team這個物件呢它使用的資料是前面的score_manager,主程式將自己的score_manager物件用參數傳送給team物件,這樣team物件就能夠存取score_manager中各個組員的分數,進而加總回報到主程式。在程式剛啟動時,team會去找有沒有team.sc這個文件,如果有就讀取,如果沒有那就自己腦補 回報說使用者沒有分組。這邊說個笑話,使用file.readlines在尾端會有一個換行符號'\n',而我為了要移除這個換行符號,大費周章用了[0: -1]切片來把它弄掉,實在很好笑,其實直接用file.read().split('\n')就好了嘛!
team.sc組員名單文件它的格式是這樣的,第一個字放小組名稱,接下來放個冒號,把組員用逗號相隔一個一個擺上去,實在是還滿麻煩的,而且格式很脆弱,你多加個空白程式就會把'6'變成' 6',結果變成完全不同的人,所以我的creat.py也有放專門幫助你輸入組員名單的程式,它會帶著你一步一步輸入每一個人,輸入空白就是換組,輸入exit就結束...好像也沒有比較簡單?
組員名單範例:
1:1,2,3,4 2:5,6,7,8 3:9,10,11,12
當然組員不需要照號碼排,而是要照實際情況做排列。
team的資料處理是用陣列裡面放字典,字典有'name', 'people', 'score'等key,這裡我比較能接受是因為好歹它也有三個key,不像剛剛只有兩個key的,實在是罪該萬死(喂!),有update和get兩個method,update會每個組的加總分數,get會給你格式化後的字串列表,以「1組(6) : 5分」的形式顯示出來,( )裡面放的是每組的第一位成員,用來幫助判斷自己是哪一組。這個team有一個不太好的地方,就是我把update和get分開來做,update是更新目前組的總分,基本上你要get當然就是要最新的啊!多做一個要幹嘛?
class teen:
def __init__(self, manager, room):
self.path = room+'/teen.sc'
self.manager = manager
self.mode = True
try:
file = open(self.path, 'r')
t = file.readlines() #就是因為這個弄了好久
self.data = []
for i in t:
i = i[0:-1] #為了把換行符號弄掉
n, a = i.split(':')
p = []
for x in a.split(','): #呃...沒有錯只是有點遜XD
p.append(int(x))
self.data.append({'name': n, 'people': p, 'score': 0})
print(self.data)
except FileNotFoundError:
self.mode = False #沒有組員名單就說沒有
def update(self):
for i in self.data:
i['score'] = 0
for j in i['people']:
i['score'] += self.manager.get(False,j) #將同一組所有成員分數加總
return self.data
def get(self):
self.update()
result = []
for i in self.data:
result.append('%s組(%s): %s' % (i['name'], i['people'][0], i['score']))
return result
資料處理大概就到這樣啦!其實這些事都不難,是我把它做複雜了,過去的我真的是把「簡單的事做難」了。
下一篇將會介紹我是如何做加密及設定的,敬請期待。
後記:
話說我過去也說要把雙系統安裝的第二篇給發一發,但已經斷坑很久了(茶)
希望大家喜歡我的作品分享!
