1. 程式人生 > >《零基礎入門學習Python》(35)--圖形使用者介面入門:EasyGui

《零基礎入門學習Python》(35)--圖形使用者介面入門:EasyGui

0. 安裝 EasyGUI

進行下載,下載好了儘量將它放在你python安裝目錄下面,方面查詢

使用 pip 進行安裝:

1. 什麼是 EasyGUI?

EasyGUI 是 Python 中一個非常簡單的 GUI 程式設計模組,不同於其他的 GUI 生成器,它不是事件驅動的。相反,所有的 GUI 互動都是通過簡地函式呼叫就可以實現。

EasyGUI 為使用者提供了簡單的 GUI 互動介面,不需要程式設計師知道任何有關 tkinter,框架,部件,回撥或 lambda 的任何細節。

EasyGUI 可以很好地相容 Python 2 和 3,並且不存在任何依賴關係。

EasyGUI 是執行在 Tkinter 上並擁有自身的事件迴圈,而 IDLE 也是 Tkinter 寫的一個應用程式並也擁有自身的事件迴圈。因此當兩者同時執行的時候,有可能會發生衝突,且帶來不可預測的結果。因此如果你發現你的 EasyGUI 程式有這樣的問題,請嘗試在 IDLE 外去執行你的程式。 

2. 一個簡單的例子

在 EasyGui 中,所有的 GUI 互動均是通過簡單的函式呼叫,下邊一個簡單的例子告訴你 EasyGui 確實很 Easy!

import easygui as g
import sys

while 1:
        g.msgbox("嗨,歡迎進入第一個介面小遊戲^_^")

        msg ="請問你希望在魚C工作室學習到什麼知識呢?"
        title = "小遊戲互動"
        choices = ["談戀愛", "程式設計", "OOXX", "琴棋書畫"]
        
        choice = g.choicebox(msg, title, choices)

        # 注意,msgbox的引數是一個字串
        # 如果使用者選擇Cancel,該函式返回None
        g.msgbox("你的選擇是: " + str(choice), "結果")

        msg = "你希望重新開始小遊戲嗎?"
        title = "請選擇"

        # 彈出一個Continue/Cancel對話方塊
        if g.ccbox(msg, title):
                pass            # 如果使用者選擇Continue
        else:
                sys.exit(0)     # 如果使用者選擇Cancel

3. EasyGUI 的各種功能演示

要執行 EasyGUI 的演示程式,在命令列呼叫 EasyGUI 是這樣的:

python easygui.py

或者可以從 IDE(例如 IDLE, PythonWin, Wing, 等等)上呼叫:

>>> import easygui
>>> easygui.egdemo()

成功呼叫後將可以嘗試 EasyGUI 擁有的各種功能,並將結果列印至控制檯

4. 匯入 EasyGUI

為了使用 EasyGUI 這個模組,你應該先匯入它。

最簡單的匯入語句是:

import easygui

如果使用上面這種形式匯入的話,那麼你使用 EasyGUI 的函式的時候,必須在函式的前面加上字首 easygui,像這樣:

easygui.msgbox(...)

另一種選擇是匯入整個 EasyGUI 包:

from easygui import *

這使得我們更容易呼叫 EasyGUI 的函式,可以直接這樣編寫程式碼:

msgbox(...)

第三種方案是使用類似下邊的 import 語句:

import easygui as g

這種方法還可以讓你保持 EasyGUI 的名稱空間,同時減少你的打字數量。

匯入之後就可以這麼呼叫 EasyGUI 的函式:

g.msgbox(...)

5. 使用 EasyGUI

一旦你的模組匯入 EasyGUI,GUI 操作就是一個簡單的呼叫 EasyGUI 函式的幾個引數的問題了。

例如,使用 EasyGUI 來實現世界上最著名的打招呼:

import easygui as g

g.msgbox("Hello, world!")

6. EasyGUI 函式的預設引數

對於所有對話方塊而言,前兩個引數都是訊息主體和對話方塊標題。

按照這個規律,在某種情況下,這可能不是理想的佈局設計(比如當對話方塊在獲取目錄或檔名的時候會選擇忽略訊息引數),但保持這種一致性且貫穿於所有的視窗部件是更為得體的考慮!

絕大部分的 EasyGUI 函式都有預設引數,幾乎所有的元件都會顯示訊息主體和對話方塊標題。

標題預設是空字串,訊息主體通常有一個簡單的預設值。

這使得你可以儘可能少的去設定引數,比如 msgbox() 函式標題部分的引數是可選的,因此你呼叫 msgbox() 的時候只需要指定一個訊息引數即可,例如:

>>> g.msgbox('hello world!!!!')

當然你也可以指定標題引數和訊息引數,例如:

>>> g.msgbox('hello world','go go go')

在各類按鈕元件裡,預設的訊息是 “Shall I continue?”,所以你可以不帶任何引數地去呼叫它們。

這裡我們演示不帶任何引數地去呼叫 ccbox(),當選擇 “cancel” 或關閉視窗的時候返回一個布林型別的值:

import easygui as g
while True:
    if g.ccbox():
        pass         # user chose to continue
    else:
        exit()     # user chose to cancel

7. 使用關鍵字引數呼叫EasyGui函式

呼叫EasyGui函式還可以使用關鍵字引數

現在假設你需要使用一個按鈕元件,但你不想指定標題引數(第二個引數),你仍可以使用關鍵字引數的方法指定choices引數(第三個引數),像這樣:

import easygui as g
xz = ['棒','真棒','超級棒']
replay = g.choicebox('我棒不棒?',choices=xz)

8. 使用按鈕組建

根據需求,EasyGui在buttonbox()上建立了一系列的函式供呼叫。

8.1 msgbox()

>>> help(g.msgbox)
Help on function msgbox in module easygui.boxes.derived_boxes:

msgbox(msg='(Your message goes here)', title=' ', ok_button='OK', image=None, root=None)
    Display a message box
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :param str ok_button: text to show in the button
    :param str image: Filename of image to display
    :param tk_widget root: Top-level Tk widget
    :return: the text of the ok_button

重寫OK按鈕最簡單的方法是使用關鍵字引數:

import easygui as g
g.msgbox('我一定要學會Python', ok_button='加油')

8.2 ccbox()

>>> help(g.ccbox)
Help on function ccbox in module easygui.boxes.derived_boxes:

ccbox(msg='Shall I continue?', title=' ', choices=('C[o]ntinue', 'C[a]ncel'), image=None, default_choice='Continue', cancel_choice='Cancel')
    Display a msgbox with choices of Continue and Cancel.
    
    The returned value is calculated this way::
    
        if the first choice ("Continue") is chosen,
          or if the dialog is cancelled:
            return True
        else:
            return False
    
    If invoked without a msg argument, displays a generic
    request for a confirmation
    that the user wishes to continue.  So it can be used this way::
    
        if ccbox():
            pass # continue
        else:
            sys.exit(0)  # exit the program
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :param list choices: a list or tuple of the choices to be displayed
    :param str image: Filename of image to display
    :param str default_choice: The choice you want highlighted
      when the gui appears
    :param str cancel_choice: If the user presses the 'X' close,
      which button should be pressed
    
    :return: True if 'Continue' or dialog is cancelled, False if 'Cancel'

ccbox()提供一個選擇:Continue或者Cancel,並相應的返回1(選中Continue)或者0(選中Cancel)。

【注意】:ccbox()是返回整形的1或0,不是布林型別的True或False。但你仍然可以這麼些:

import sys
import easygui as g
if g.ccbox('要再來一次嗎?',choices=('要啊要啊^_^', '算了吧T_T')):
    g.msgbox('不給玩了,再玩就玩壞了……')
else:
    sys.exit(0)

8.3 ynbox()

>>> help(g.ynbox)
Help on function ynbox in module easygui.boxes.derived_boxes:

ynbox(msg='Shall I continue?', title=' ', choices=('[<F1>]Yes', '[<F2>]No'), image=None, default_choice='[<F1>]Yes', cancel_choice='[<F2>]No')
    Display a msgbox with choices of Yes and No.
    
    The returned value is calculated this way::
    
        if the first choice ("Yes") is chosen, or if the dialog is cancelled:
            return True
        else:
            return False
    
    If invoked without a msg argument, displays a generic
    request for a confirmation
    that the user wishes to continue.  So it can be used this way::
    
        if ynbox():
            pass # continue
        else:
            sys.exit(0)  # exit the program
    
    :param msg: the msg to be displayed
    :type msg: str
    :param str title: the window title
    :param list choices: a list or tuple of the choices to be displayed
    :param str image: Filename of image to display
    :param str default_choice: The choice you want highlighted
        when the gui appears
    :param str cancel_choice: If the user presses the 'X' close, which
      button should be pressed
    
    :return: True if 'Yes' or dialog is cancelled, False if 'No'

同8.2

8.4 buttonbox()

>>> help(g.buttonbox)
Help on function buttonbox in module easygui.boxes.button_box:

buttonbox(msg='', title=' ', choices=('Button[1]', 'Button[2]', 'Button[3]'), image=None, images=None, default_choice=None, cancel_choice=None, callback=None, run=True)
    Display a msg, a title, an image, and a set of buttons.
    The buttons are defined by the members of the choices global_state.
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :param list choices: a list or tuple of the choices to be displayed
    :param str image: (Only here for backward compatibility)
    :param str images: Filename of image or iterable or iteratable of iterable to display
    :param str default_choice: The choice you want highlighted when the gui appears
    :return: the text of the button that the user selected

可以使用buttonbox()定義自己的一組按鈕,buttonbox()會顯示一組你定義好的按鈕。

當用戶點選任意一個按鈕的時候,buttonbox()返回按鈕的文字內容。如果使用者取消或者關閉視窗,那麼會返回預設選項(第一個選項)。請看示例:

import easygui as g
g.buttonbox(choices=('草莓','西瓜','芒果','巧克力'))

8.5 indexbox()

>>> help(g.indexbox)
Help on function indexbox in module easygui.boxes.derived_boxes:

indexbox(msg='Shall I continue?', title=' ', choices=('Yes', 'No'), image=None, default_choice='Yes', cancel_choice='No')
    Display a buttonbox with the specified choices.
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :param list choices: a list or tuple of the choices to be displayed
    :param str image: Filename of image to display
    :param str default_choice: The choice you want highlighted
      when the gui appears
    :param str cancel_choice: If the user presses the 'X' close,
      which button should be pressed
    :return: the index of the choice selected, starting from 0

基本上跟上邊一樣,區別就是當用戶選擇第一個按鈕的時候返回序號0,選擇第二個按鈕的時候返回序號1

8.6 boolbox()

>>> help(g.boolbox)
Help on function boolbox in module easygui.boxes.derived_boxes:

boolbox(msg='Shall I continue?', title=' ', choices=('[Y]es', '[N]o'), image=None, default_choice='Yes', cancel_choice='No')
    Display a boolean msgbox.
    
    The returned value is calculated this way::
    
        if the first choice is chosen, or if the dialog is cancelled:
            returns True
        else:
            returns False
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :param list choices: a list or tuple of the choices to be displayed
    :param str image: Filename of image to display
    :param str default_choice: The choice you want highlighted
      when the gui appears
    :param str cancel_choice: If the user presses the 'X' close, which button
      should be pressed
    :return: True if first button pressed or dialog is cancelled, False if
      second button is pressed

如果第一個按鈕被選中返回1,否則返回0.

9. 如何在buttonbox裡顯示圖片

當你呼叫一個buttonbox函式(例如:msgbox(),ynbox(),indexbox()等)的時候,你還可以為關鍵字引數image賦值,這是設定一個.gif格式的影象(注意僅支援GIF格式)

import easygui as g
g.buttonbox('大家說我長得帥嗎?', image='1.gif', choices=('帥', '不帥', '你大爺'))

10. 為使用者提供一系列選項

10.1 choicebox()

>>> help(g.choicebox)
Help on function choicebox in module easygui.boxes.choice_box:

choicebox(msg='Pick an item', title='', choices=[], preselect=0, callback=None, run=True)
    Present the user with a list of choices.
    return the choice that he selects.
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :param list choices: a list or tuple of the choices to be displayed
    :param preselect: Which item, if any are preselected when dialog appears
    :return: List containing choice selected or None if cancelled

按鈕組建方便提供使用者一個簡單的按鈕選項,但如果有很多選項,或者選項的內容特別長的話,更好的策略是為它們提供一個可選擇的列表。

choicebox()為使用者提供了一個可選擇的列表,使用序列(元祖或列表)作為選項,這些選項顯示前會按照不區分大小寫的方法排好序。

另外還可以使用鍵盤來選擇其中一個選項(比較糾結,但一點兒都不重要): 

- 例如當按下鍵盤上g鍵,將會選中第一個以g開頭的選項。再次按下g鍵,則會選中下一個以g開頭的選項。在選中最後一個以g開頭的選項時候,再次按下g鍵將重新回到在列表的開頭的第一個以g開頭的選項。 

- 如果選項中沒有以g開頭的,則會選中字元排序在g之前f的那個字元開頭的選項 

- 如果選項中沒有字元排序在g之前,那麼在列表中第一個元素將會被選中。

綜合我們之前的學習檔案功能,舉個例子:  

import easygui as g

target = ['.mp4','.avi','.rmvb','.mkv','.torrent']
vedio_list = []

import os
def serach_file(start_dir,target):
    os.chdir(start_dir)

    for each_file in os.listdir(os.curdir):
        ext = os.path.splitext(each_file)[1]
        if ext in target:
            vedio_list.append(os.getcwd() + os.sep +each_file + os.linesep)
        if os.path.isdir(each_file):
            serach_file(each_file,target)
            os.chdir(os.pardir)
start_dir = input('請輸入需要查詢的目錄:')
program_dir = os.getcwd()
serach_file(start_dir,target)

f = open(program_dir + os.sep + 'vedioList.txt','w')
f.writelines(vedio_list)
f.close()

g.choicebox(msg='在 【%s】 系列路徑下工搜尋滿足條件的檔案如下' % start_dir,choices=vedio_list)

12. 讓使用者輸入密碼

有時候我們需要讓使用者輸入密碼,就是使用者輸入的東西看上去都是**********

12.1 passwordbox()

>>> help(g.passwordbox)
Help on function passwordbox in module easygui.boxes.derived_boxes:

passwordbox(msg='Enter your password.', title=' ', default='', image=None, root=None)
    Show a box in which a user can enter a password.
    The text is masked with asterisks, so the password is not displayed.
    
    :param str msg: the msg to be displayed.
    :param str title: the window title
    :param str default: value returned if user does not change it
    :return: the text that the user entered, or None if he cancels
      the operation.

passwordbox()跟enterbox()樣式一樣,不同的是使用者輸入的內容用*顯示,返回使用者輸入的字串:

12.2 multpasswordbox()

>>> help(g.multpasswordbox)
Help on function multpasswordbox in module easygui.boxes.multi_fillable_box:

multpasswordbox(msg='Fill in values for the fields.', title=' ', fields=(), values=(), callback=None, run=True)
    Same interface as multenterbox.  But in multpassword box,
    the last of the fields is assumed to be a password, and
    is masked with asterisks.
    
    :param str msg: the msg to be displayed.
    :param str title: the window title
    :param list fields: a list of fieldnames.
    :param list values: a list of field values
    :return: String
    
    **Example**
    
    Here is some example code, that shows how values returned from
    multpasswordbox can be checked for validity before they are accepted::
    
        msg = "Enter logon information"
        title = "Demo of multpasswordbox"
        fieldNames = ["Server ID", "User ID", "Password"]
        fieldValues = []  # we start with blanks for the values
        fieldValues = multpasswordbox(msg,title, fieldNames)
    
        # make sure that none of the fields was left blank
        while 1:
            if fieldValues is None: break
            errmsg = ""
            for i in range(len(fieldNames)):
                if fieldValues[i].strip() == "":
                    errmsg = errmsg + ('"%s" is a required field.\n\n' %
                     fieldNames[i])
                if errmsg == "": break # no problems found
            fieldValues = multpasswordbox(errmsg, title,
              fieldNames, fieldValues)
    
        print("Reply was: %s" % str(fieldValues))

multpasswordbox()跟multenterbox()使用相同的介面,但當它顯示的時候,最後一個輸入框顯示為密碼的形式*

from easygui import *
msg = "驗證登陸資訊"
title = "密碼函式multpasswordbox示例"
fieldNames = ["伺服器 ID", "使用者 ID", "密碼"]
fieldValues = []  # we start with blanks for the values
fieldValues = multpasswordbox(msg, title, fieldNames)
print("輸入的資訊是: %s" % str(fieldValues))

13. 顯示文字

EasyGui還提供函式用於顯示文字。

13.1 textbox()

>>> help(g.textbox)
Help on function textbox in module easygui.boxes.text_box:

textbox(msg='', title=' ', text='', codebox=False, callback=None, run=True)
    Display a message and a text to edit
    
    Parameters
    ----------
    msg : string
        text displayed in the message area (instructions...)
    title : str
        the window title
    text: str, list or tuple
        text displayed in textAreas (editable)
    codebox: bool
        if True, don't wrap and width is set to 80 chars
    callback: function
        if set, this function will be called when OK is pressed
    run: bool
        if True, a box object will be created and returned, but not run
    
    Returns
    -------
    None
        If cancel is pressed
    str
        If OK is pressed returns the contents of textArea

textbox()函式預設會以比例字型(引數codebox = 1設定為等寬字型)來顯示文字內容(會自動換行哦),這個函式適合用於顯示一般的書面文字。

【注意】:text引數(第三個引數)可以是字串,列表或者元祖型別。

13.2 codebox()

>>> help(g.codebox)
Help on function codebox in module easygui.boxes.derived_boxes:

codebox(msg='', title=' ', text='')
    Display some text in a monospaced font, with no line wrapping.
    This function is suitable for displaying code and text that is
    formatted using spaces.
    
    The text parameter should be a string, or a list or tuple of lines to be
    displayed in the textbox.
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :param str text: what to display in the textbox

codebox()以等寬字型顯示文字內容,相當於testbox(codebox = 1)

14. 目錄與檔案

GUI程式設計中一個常見的場景是要求使用者輸入目錄及檔名,EasyGui提供了一些基本函式讓使用者來瀏覽檔案系統,選擇一個目錄或檔案。

14.1 diropenbox()

>>> help(g.diropenbox)
Help on function diropenbox in module easygui.boxes.diropen_box:

diropenbox(msg=None, title=None, default=None)
    A dialog to get a directory name.
    Note that the msg argument, if specified, is ignored.
    
    Returns the name of a directory, or None if user chose to cancel.
    
    If the "default" argument specifies a directory name, and that
    directory exists, then the dialog box will start with that directory.
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :param str default: starting directory when dialog opens
    :return: Normalized path selected by user

diropenbox()函式用於提供一個對話方塊,返回使用者選擇的目錄名(帶完整路徑),如果使用者選擇Cancel則返回None。  default引數用於設定預設的開啟目錄(請確保設定的目錄已存在。)

>>> g.diropenbox()

14.2 fileopenbox()

>>> help(g.fileopenbox)
Help on function fileopenbox in module easygui.boxes.fileopen_box:

fileopenbox(msg=None, title=None, default='*', filetypes=None, multiple=False)
    A dialog to get a file name.
    
    **About the "default" argument**
    
    The "default" argument specifies a filepath that (normally)
    contains one or more wildcards.
    fileopenbox will display only files that match the default filepath.
    If omitted, defaults to "\*" (all files in the current directory).
    
    WINDOWS EXAMPLE::
    
        ...default="c:/myjunk/*.py"
    
    will open in directory c:\myjunk\ and show all Python files.
    
    WINDOWS EXAMPLE::
    
        ...default="c:/myjunk/test*.py"
    
    will open in directory c:\myjunk\ and show all Python files
    whose names begin with "test".
    
    
    Note that on Windows, fileopenbox automatically changes the path
    separator to the Windows path separator (backslash).
    
    **About the "filetypes" argument**
    
    If specified, it should contain a list of items,
    where each item is either:
    
    - a string containing a filemask          # e.g. "\*.txt"
    - a list of strings, where all of the strings except the last one
      are filemasks (each beginning with "\*.",
      such as "\*.txt" for text files, "\*.py" for Python files, etc.).
      and the last string contains a filetype description
    
    EXAMPLE::
    
        filetypes = ["*.css", ["*.htm", "*.html", "HTML files"]  ]
    
    .. note:: If the filetypes list does not contain ("All files","*"), it will be added.
    
    If the filetypes list does not contain a filemask that includes
    the extension of the "default" argument, it will be added.
    For example, if default="\*abc.py"
    and no filetypes argument was specified, then
    "\*.py" will automatically be added to the filetypes argument.
    
    :param str msg: the msg to be displayed.
    :param str title: the window title
    :param str default: filepath with wildcards
    :param object filetypes: filemasks that a user can choose, e.g. "\*.txt"
    :param bool multiple: If true, more than one file can be selected
    :return: the name of a file, or None if user chose to cancel

fileopenbox()函式用於提供一個對話方塊,返回使用者選擇的檔名(帶完整路徑),如果使用者選擇Cancel則返回None

關於default引數的設定方法:  - default引數指定一個預設路徑,通常包含一個或多個萬用字元。  - 如果設定了default引數,fileopenbox()顯示預設的檔案路徑和格式。  - default預設的引數是*,即匹配所有格式的檔案。

例如:  1. default = ‘c:/fishc/*.py’即顯示c:\fishc資料夾下所有的Python檔案  2. default = ‘c:/fishc/test*.py’即顯示c:\fishc資料夾下所有的名字以test開頭的Python檔案

關於filetypes引數的設定方法:  - 可以是包含檔案掩碼的字串列表。例如:filetypes = ['*.txt']  - 可以是字串列表,列表的最後一項字串是檔案型別的描述,例如:filetypes = ['*.css',['*.htm','*.html','*.HTML files']]

>>> g.fileopenbox(default = 'E:\software\*.exe')
'E:\\software\\pycharm-professional-2018.1.1.exe'


>>> g.fileopenbox(filetypes = ['*.py',['*.py','*.python file']])
'D:\\untitled\\Python_learn\\將一個數分解成列表.py'

事例3: 

提供一個資料夾瀏覽框,讓使用者選擇需要開啟的檔案,開啟並顯示檔案內容:

import easygui as g
import os

file_path = g.fileopenbox(default="*.txt")

with open(file_path) as f:
    title = os.path.basename(file_path)
    msg = "檔案【%s】的內容如下:" % title
    text = f.read()
    g.textbox(msg, title, text)

14.3 filesavebox()

>>> help(g.filesavebox)
Help on function filesavebox in module easygui.boxes.filesave_box:

filesavebox(msg=None, title=None, default='', filetypes=None)
    A file to get the name of a file to save.
    Returns the name of a file, or None if user chose to cancel.
    
    The "default" argument should contain a filename (i.e. the
    current name of the file to be saved).  It may also be empty,
    or contain a filemask that includes wildcards.
    
    The "filetypes" argument works like the "filetypes" argument to
    fileopenbox.
    
    :param str msg: the msg to be displayed.
    :param str title: the window title
    :param str default: default filename to return
    :param object filetypes: filemasks that a user can choose, e.g. " \*.txt"
    :return: the name of a file, or None if user chose to cancel

filesavebox()函式提供一個對話方塊,讓用於選擇的檔案需要儲存的路徑(帶完整路勁),如果使用者選擇Cancel則返回None。

default引數應該包含一個檔名(例如當前需要儲存的檔名),當然你也可以設定為空的,或者包含一個檔案格式掩碼的萬用字元

filetypes引數類似於fileopenbox()函式的filetypes引數。

>>> g.filesavebox(default='test.txt')

事例4

事例3的基礎上增強功能:當用戶點選OK按鈕的時候,比較當前檔案是否修改過,如果修改過,則提示覆蓋儲存,放棄儲存另存為...並實現相應功能。 

(提示:解決這道題可能需要點耐心,因為你有可能被一個小問題卡住,但請堅持,自己想辦法找到這個小問題並解決它!)

import easygui as g
import os

file_path = g.fileopenbox(default='*.txt')

with open(file_path) as old_file:
    title = os.path.basename(file_path)
    msg = '檔案【%s】的內容如下:' % title
    text = old_file.read()
    text_after = g.textbox(msg,title,text)

if text != text_after[:-1]: #textbox的返回值會在末尾追加一個換行符    
    #buttonbox(msg='', title=' ', choices=('Button1', 'Button2', 'Button3'), image=None, root=None)
    choice = g.buttonbox('檢測到檔案內容發生改變,請選擇以下操作:','警告',('覆蓋保持','放棄儲存','另存為...'))
    if choice == '覆蓋儲存':
        with open(file_path,'w') as old_file:
            old_file.write(text_after[:-1])
    if choice == '放棄儲存':
        pass
    if choice == '另存為...':
        another_path = g.filesavebox(default='.txt')
        if os.path.splitext(another_path)[1] != '.txt':
            another_path += '.txt'
        with open(another_path,'w') as new_file:
            new_file.write(text_after[:-1])

15. 記住使用者的設定

15.1 EgStore

>>> help(g.EgStore)
Help on class EgStore in module easygui.boxes.egstore:

class EgStore(builtins.object)
 |  EgStore(filename)
 |  
 |  A class to support persistent storage.
 |  
 |  You can use ``EgStore`` to support the storage and retrieval
 |  of user settings for an EasyGui application.
 |  
 |  **First: define a class named Settings as a subclass of EgStore** ::
 |  
 |      class Settings(EgStore):
 |          def __init__(self, filename):  # filename is required
 |              # specify default values for variables that this application wants to remember
 |              self.user_id = ''
 |              self.target_server = ''
 |              settings.restore()
 |  *Second: create a persistent Settings object** ::
 |  
 |      settings = Settings('app_settings.txt')
 |      settings.user_id = 'obama_barak'
 |      settings.targetServer = 'whitehouse1'
 |      settings.store()
 |  
 |      # run code that gets a new value for user_id, and persist the settings
 |      settings.user_id = 'biden_joe'
 |      settings.store()
 |  
 |  **Example C: recover the Settings instance, change an attribute, and store it again.** ::
 |  
 |      settings = Settings('app_settings.txt')
 |      settings.restore()
 |      print settings
 |      settings.user_id = 'vanrossum_g'
 |      settings.store()
 |  
 |  Methods defined here:
 |  
 |  __getstate__(self)
 |      All attributes will be pickled
 |  
 |  __init__(self, filename)
 |      Initialize a store with the given filename.
 |      
 |      :param filename: the file that backs this store for saving and loading
 |  
 |  __repr__(self)
 |      Return repr(self).
 |  
 |  __setstate__(self, state)
 |      Ensure filename won't be unpickled
 |  
 |  __str__(self)
 |      "Format this store as "key : value" pairs, one per line.
 |  
 |  kill(self)
 |      Delete this store's file if it exists.
 |  
 |  restore(self)
 |  
 |  store(self)
 |      Save this store to a pickle file.
 |      All directories in :attr:`filename` must already exist.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

GUI程式設計中一個常見的場景就是要求使用者設定一下引數,然後儲存下來,以便下次使用者使用你的程式的時候可以記住他的設定。

為了實現對使用者的設定進行儲存和恢復這一過程,EasyGui提供了一個EgStore的類。為了記住某些設定,你的應用程式必須定義一個類(暫時稱之為“設定”類,儘管你隨意的使用你想要的名稱設定它)繼承自EgStore類。

然後你的應用程式必須建立一個該類的物件(暫時稱之為“設定”物件)

設定類的建構函式(__init__方法)必須初始化所有的你想要它記住的那些值。

一旦你這樣做了,你就可以在“設定”物件中通過設定值去例項化變數,從而簡單地記住設定。之後使用setting.store()方法在硬碟上持久化設定物件。 下面是建立一個“設定”類的例子:

#-----------------------------------------------------------------------
# create "settings", a persistent Settings object
# Note that the "filename" argument is required.
# The directory for the persistent file must already exist.
#-----------------------------------------------------------------------
settingsFilename = os.path.join("C:", "FishCApp", "settings.txt")  # Windows example
settings = Settings(settingsFilename)

下面是使用“設定”物件的例子:

# we initialize the "user" and "server" variables
# In a real application, we'd probably have the user enter them via enterbox
user    = "奧巴馬"
server  = "白宮"

# we save the variables as attributes of the "settings" object
settings.userId = user
settings.targetServer = server
settings.store()    # persist the settings

# run code that gets a new value for userId
# then persist the settings with the new value
user    = "小甲魚"
settings.userId = user
settings.store()

16. 捕獲異常

  • exceptionbox()
>>> help(g.exceptionbox)
Help on function exceptionbox in module easygui.boxes.derived_boxes:

exceptionbox(msg=None, title=None)
    Display a box that gives information about
    an exception that has just been raised.
    
    The caller may optionally pass in a title for the window, or a
    msg to accompany the error information.
    
    Note that you do not need to (and cannot) pass an exception object
    as an argument.  The latest exception will automatically be used.
    
    :param str msg: the msg to be displayed
    :param str title: the window title
    :return: None

使用EasyGui編寫GUI程式,有時候難免會產生異常。當然這取決於你如何執行你的應用程式,當你的應用程式崩潰的時候,堆疊追蹤可能會被丟擲,或者被寫入到stdout標準輸出函式中。

EasyGui通過exceptionbox()函式提供了更好的方式去處理異常,異常出現的時候,exceptionbox()會顯示堆疊追蹤在一個codebox()中並且允許你做進一步的處理。

exceptionbox()很容易使用,請看下例:

import easygui as g

try:
    print('i love fishc.com')
    int('FishC') #這裡會產生異常
except:
    g.exceptionbox()

事例5:

寫一個程式統計你當前程式碼量的總和,並顯示離十萬行程式碼量還有多遠?

  • 要求1:遞迴搜尋各個資料夾
  • 要求2:顯示各個型別的原始檔和原始碼數量
  • 要求3:顯示總行數與百分比

截圖1: 

截圖2:

import easygui as g
import os

def show_result(start_dir):
    lines = 0
    total = 0
    text = ""

    for i in source_list:
        lines = source_list[i]
        total += lines
        text += "【%s】原始檔 %d 個,原始碼 %d 行\n" % (i, file_list[i], lines)
    title = '統計結果'
    msg = '您目前共累積編寫了 %d 行程式碼,完成進度:%.2f %%\n離 10 萬行程式碼還差 %d 行,請繼續努力!' % (total, total/1000, 100000-total)
    g.textbox(msg, title, text)

def calc_code(file_name):
    lines = 0
    with open(file_name) as f:
        print('正在分析檔案:%s ...' % file_name)
        try:
            for each_line in f:
                lines += 1
        except UnicodeDecodeError:
            pass # 不可避免會遇到格式不相容的檔案,這裡忽略掉......
    return lines

def search_file(start_dir) :
    os.chdir(start_dir)

    for each_file in os.listdir(os.curdir) :
        ext = os.path.splitext(each_file)[1]
        if ext in target :
            lines = calc_code(each_file) # 統計行數
            # 還記得異常的用法嗎?如果字典中不存,丟擲 KeyError,則新增字典鍵
            # 統計檔案數
            try:
                file_list[ext] += 1
            except KeyError:
                file_list[ext] = 1
            # 統計原始碼行數
            try:
                source_list[ext] += lines
            except KeyError:
                source_list[ext] = lines

        if os.path.isdir(each_file) :
            search_file(each_file) # 遞迴呼叫
            os.chdir(os.pardir) # 遞迴呼叫後切記返回上一層目錄

target = ['.c', '.cpp', '.py', '.cc', '.java', '.pas', '.asm']
file_list = {}
source_list = {}

g.msgbox("請開啟您存放所有程式碼的資料夾......", "統計程式碼量")
path = g.diropenbox("請選擇您的程式碼庫:")

search_file(path)
show_result(path)