前言
一、代碼整體架構分析
1、數據層?(Model)?
?2、控制層?(Controller)
3、視圖層?(View)?
二、核心功能實現詳解?
1、?文件導入功能?
1.1、實現邏輯
?1.2、代碼涉及知識點講解
1.2.1、wildcard
1.2.2、wx.FileDialog?
1.2.3、dlg.ShowModal()
2、抽獎動畫控制
1.1、實現邏輯
1.2、代碼涉及知識點講解
1.3、關鍵技術點詳解
1.4、代碼示例場景
1.5、關聯代碼示例
3、抽獎結果處理
三、關鍵技術點詳解
1. wxPython組件體系
2、布局管理系統
3、事件驅動模型?
四、完成代碼?
前言
最近在學習Python,寫成文章供大家參考,同時也方便自己閱讀和熟悉代碼。有問題歡迎指正。
一、代碼整體架構分析
本程序采用經典的?MVC模式?進行設計,通過wxPython實現GUI界面,主要包含以下模塊:
1、數據層?(Model
)?
-
self.participants
:存儲待抽獎人員列表 -
self.winners
:存儲已中獎人員列表 -
文件數據源處理(CSV/TXT)
?2、控制層?(Controller
)
-
定時器控制抽獎動畫:
wx.Timer
-
狀態管理:
is_rolling
控制流程 -
事件驅動:按鈕點擊、定時器響應
3、視圖層?(View
)?
-
主界面布局:按鈕區+顯示區+名單區
-
狀態欄反饋
-
動態顏色變化增強視覺效果
二、核心功能實現詳解?
1、?文件導入功能?
1.1、實現邏輯
def on_import(self, event):wildcard = "文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv"dlg = wx.FileDialog(self, "選擇名單文件", wildcard=wildcard,style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)if dlg.ShowModal() == wx.ID_CANCEL:returnpath = dlg.GetPath()try:if path.endswith('.csv'):with open(path, 'r', encoding='utf-8-sig') as f:reader = csv.reader(f)self.participants = [row[0].strip() for row in reader if row]else:with open(path, 'r', encoding='utf-8') as f:self.participants = [line.strip() for line in f if line.strip()]self.list_ctrl.Set(self.participants)self.FindWindowByName("開始抽獎").Enable()self.SetStatusText(f"成功導入 {len(self.participants)} 位參與者")except Exception as e:wx.MessageBox(f"文件讀取失敗: {str(e)}", "錯誤", wx.OK|wx.ICON_ERROR)
?1.2、代碼涉及知識點講解
1.2.1、wildcard
wildcard = "文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv"?
在 wxPython 中,wildcard
?參數用于定義文件對話框(如?wx.FileDialog
)中的文件類型過濾規則,其格式為?描述|通配符
,多個過濾規則之間用豎線?|
?分隔。你提供的示例?"文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv"
?表示用戶可以在文件對話框中選擇兩種文件類型:?
1、格式解析
wildcard = "描述1|通配符1|描述2|通配符2|..."
-
描述:顯示在文件類型下拉框中的可讀文本(如?
"文本文件 (*.txt)"
)。 -
通配符:實際用于過濾文件擴展名的模式(如?
*.txt
)。
2、示例說明
wildcard = "文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv"
?規則1:
-
描述:
文本文件 (*.txt)
-
通配符:
*.txt
(匹配所有?.txt
?文件)
規則2:
-
描述:
CSV文件 (*.csv)
-
通配符:
*.csv
(匹配所有?.csv
?文件)
會在文件對話框中看到如下選項:文件類型過濾示例
import wxclass MyFrame(wx.Frame):def __init__(self):super().__init__(None, title="文件對話框示例")self.InitUI()def InitUI(self):panel = wx.Panel(self)btn = wx.Button(panel, label="打開文件", pos=(10, 10))btn.Bind(wx.EVT_BUTTON, self.OnOpenFile)def OnOpenFile(self, event):# 創建文件對話框并設置 wildcarddialog = wx.FileDialog(self,message="選擇文件",defaultDir="",defaultFile="",wildcard="文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv",style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)if dialog.ShowModal() == wx.ID_OK:filepath = dialog.GetPath()print("選擇的文件路徑:", filepath)dialog.Destroy()if __name__ == "__main__":app = wx.App()frame = MyFrame()frame.Show()app.MainLoop()
運行會彈出如下界面:?
?3、常見用法
- 添加“所有文件”選項:在末尾追加通配符?
*.*
?wildcard = "文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv|所有文件 (*.*)|*.*"
- 多擴展名匹配:用分號分隔多個通配符
wildcard = "圖片文件 (*.jpg;*.png)|*.jpg;*.png"
- 默認選中某一類型:將目標類型放在最前面
wildcard = "CSV文件 (*.csv)|*.csv|文本文件 (*.txt)|*.txt"
?4、注意事項
-
豎線分隔符:確保?
描述
?和?通配符
?成對出現,并用?|
?分隔。 -
通配符格式:
-
*.txt
?表示匹配當前目錄下的?.txt
?文件。 -
*.*
?表示匹配所有文件。
-
1.2.2、wx.FileDialog?
在 wxPython 中,wx.FileDialog
?是用于創建?文件選擇對話框?的類。你提供的代碼片段是創建一個“打開文件”對話框,以下是逐層解析:
1、代碼含義?
wx.FileDialog(self, # 父窗口(通常是當前窗口)"選擇名單文件", # 對話框標題wildcard=wildcard, # 文件類型過濾規則(如 "*.txt;*.csv")style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST # 對話框行為模式
)
2、參數詳解
參數 | 作用 |
---|---|
self | 父窗口(通常是?wx.Frame ?或?wx.Panel ?實例),用于對話框的依附關系。 |
"選擇名單文件" | 對話框標題(提示用戶操作目的,例如“選擇要打開的名單文件”)。 |
wildcard | 文件類型過濾規則,格式為 `"描述 |
style | 對話框行為標志的組合: |
-?wx.FD_OPEN : 對話框為“打開文件”模式(默認是打開,對應還有?wx.FD_SAVE ?保存模式)。 | |
-?wx.FD_FILE_MUST_EXIST : 用戶必須選擇已存在的文件(禁止輸入不存在的文件名)。 |
?3、典型使用流程
# 1. 創建對話框
dialog = wx.FileDialog(self, "選擇名單文件", wildcard="文本文件 (*.txt)|*.txt", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
)# 2. 顯示對話框并等待用戶操作
if dialog.ShowModal() == wx.ID_OK:# 3. 用戶點擊了“確定”,獲取文件路徑selected_path = dialog.GetPath()print("選擇的文件:", selected_path)
else:# 用戶點擊了“取消”print("操作取消")# 4. 銷毀對話框(避免內存泄漏)
dialog.Destroy()
1.2.3、dlg.ShowModal()
-
顯示一個模態對話框(如文件選擇對話框、消息框等)。
-
該方法會阻塞程序,直到用戶關閉對話框。
-
返回值表示用戶的操作:
-
wx.ID_OK
:用戶點擊“確定”或類似確認按鈕。 -
wx.ID_CANCEL
:用戶點擊“取消”或關閉對話框。
-
1、典型使用場景
1.1、文件選擇對話框
def OnOpenFile(self, event):dlg = wx.FileDialog(self, "選擇文件", style=wx.FD_OPEN)# 用戶點擊取消則直接退出if dlg.ShowModal() == wx.ID_CANCEL:dlg.Destroy() # 銷毀對話框(重要!)return# 用戶點擊確定后的操作(如讀取文件)filepath = dlg.GetPath()dlg.Destroy()self.LoadFile(filepath)
1.2、確認對話框
def OnDelete(self, event):dlg = wx.MessageDialog(self, "確定要刪除嗎?", "確認", wx.YES_NO | wx.ICON_QUESTION)# 用戶點擊取消或關閉對話框if dlg.ShowModal() == wx.ID_CANCEL:dlg.Destroy()return# 用戶點擊確定后的操作self.DeleteItem()dlg.Destroy()
注:其他代碼含義,將在完整代碼給出。
2、抽獎動畫控制
1.1、實現邏輯
def on_start(self, event):if not self.participants:wx.MessageBox("參與者列表為空!", "警告", wx.OK|wx.ICON_WARNING)returnself.is_rolling = Trueevent.GetEventObject().Disable()self.FindWindowByName("停止").Enable()self.FindWindowByName("導入名單").Disable()self.timer.Start(50)def update_display(self, event):# 1. 檢查抽獎狀態和參與者列表有效性if self.is_rolling and self.participants:# 2. 隨機選取當前顯示的中獎者self.current_roll = random.choice(self.participants)# 3. 更新顯示文本self.display.SetLabel(self.current_roll)# 4. 隨機改變文字顏色self.display.SetForegroundColour(random.choice([wx.RED, wx.BLUE, wx.GREEN, wx.BLACK]))
1.2、代碼涉及知識點講解
1、if self.is_rolling and self.participants:
-
self.is_rolling
:確保抽獎處于進行中狀態 -
self.participants
:確保參與者列表非空 -
避免在停止狀態或空列表時執行無效操作
2、self.current_roll = random.choice(self.participants)
-
通過
random.choice
從參與者列表中隨機選取一個名字 -
實現抽獎動畫的"滾動"效果
?3、self.display.SetLabel(self.current_roll)
-
self.display
:指向顯示文本的控件(如wx.StaticText
) -
SetLabel
:動態更新顯示內容 -
效果:界面上的名字快速變化模擬抽獎過程
?4、self.display.SetForegroundColour(random.choice([...]))
-
從預設顏色中隨機選擇文字顏色
-
支持顏色列表:紅、藍、綠、黑
-
效果:增強視覺動態感,使抽獎過程更生動
1.3、關鍵技術點詳解
技術點 | 作用 |
---|---|
定時器觸發 | 通過wx.Timer 周期性調用此方法(如每50ms)實現動畫效果 |
隨機選擇算法 | random.choice 實現公平隨機選擇 |
控件屬性動態修改 | 實時修改文本內容和顏色屬性 |
狀態同步機制 | self.is_rolling 確保僅在抽獎過程中更新 |
1.4、代碼示例場景
假設:
-
參與者列表:
["張三", "李四", "王五"]
-
定時器間隔:50ms
-
顏色變化頻率:每次定時器觸發都變化
?運行時效果:
第1次更新:顯示"張三"(紅色) 第2次更新:顯示"李四"(藍色) 第3次更新:顯示"王五"(綠色) 第4次更新:顯示"張三"(黑色) ... (持續直到用戶點擊停止)
1.5、關聯代碼示例
class LotteryFrame(wx.Frame):def __init__(self):self.timer = wx.Timer(self) # 創建定時器self.Bind(wx.EVT_TIMER, self.update_display) # 綁定事件def on_start(self, event):self.timer.Start(50) # 每50ms觸發一次
3、抽獎結果處理
def on_stop(self, event):self.timer.Stop()self.is_rolling = Falseif self.current_roll:self.winners.append(self.current_roll)self.participants.remove(self.current_roll)self.winner_list.Append(self.current_roll)self.list_ctrl.Set(self.participants)self.FindWindowByName("開始抽獎").Enable()self.FindWindowByName("停止").Disable()self.FindWindowByName("導入名單").Enable()self.SetStatusText(f"剩余參與者: {len(self.participants)} 人")
數據同步機制:
-
移除非中獎者保持數據一致性
-
ListBox.Append()
實時更新中獎名單 -
SetStatusText
更新狀態欄信息
三、關鍵技術點詳解
1. wxPython組件體系
組件 | 作用 | 重要方法/屬性 |
---|---|---|
wx.Frame | 主窗口容器 | CreateStatusBar() |
wx.Panel | 次級容器 | SetSizer() |
wx.Button | 按鈕控件 | Bind() ,?Enable() |
wx.ListBox | 列表顯示 | Set() ,?Append() |
wx.StaticText | 文本顯示 | SetLabel() ,?SetFont() |
wx.Timer | 定時器 | Start() ,?Stop() |
2、布局管理系統
main_sizer = wx.BoxSizer(wx.VERTICAL)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
list_sizer = wx.BoxSizer(wx.HORIZONTAL)main_sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.TOP, 10)
main_sizer.Add(self.display, 1, wx.EXPAND | wx.ALL, 20)
main_sizer.Add(list_panel, 2, wx.EXPAND | wx.ALL, 10)
布局策略:
-
垂直布局(
VERTICAL
)為主容器 -
按鈕區水平排列(
HORIZONTAL
) -
EXPAND
標志使組件填充可用空間 -
比例參數控制區域大小(0:固定大小,1:1份空間,2:2份空間)
3、事件驅動模型?
# 按鈕事件綁定
btn_start.Bind(wx.EVT_BUTTON, self.on_start)# 定時器事件綁定
self.Bind(wx.EVT_TIMER, self.update_display)
事件處理流程:
用戶點擊按鈕 → 觸發EVT_BUTTON → 調用on_start
↓
啟動定時器 → 周期觸發EVT_TIMER → 調用update_display
↓
用戶點擊停止 → 終止定時器 → 處理抽獎結果
四、完成代碼?
import wx
import csv
import randomclass LotteryFrame(wx.Frame):def __init__(self):super().__init__(None, title="幸運大抽獎", size=(800, 600))self.participants = []self.winners = []self.timer = wx.Timer(self)# 用 is_rolling 屬性記錄某個過程的狀態(如“抽獎是否正在進行中”),方便后續邏輯判斷。"""典型應用場景防止重復操作例如:禁用“開始”按鈕,直到當前操作完成。動畫/進程控制例如:標記一個動畫是否正在播放,避免中途打斷。資源鎖例如:在多線程中標記某個資源是否被占用。"""self.is_rolling = False # 初始狀態:未開始self.current_roll = ""self.init_ui()self.CreateStatusBar() # 創建狀態欄self.Show()def init_ui(self):panel = wx.Panel(self)# 頂部控制按鈕btn_open = wx.Button(panel, label="導入名單")btn_start = wx.Button(panel, label="開始抽獎")btn_stop = wx.Button(panel, label="停止")# 綁定btn_open.Bind(wx.EVT_BUTTON, self.on_import)btn_start.Bind(wx.EVT_BUTTON, self.on_start)btn_stop.Bind(wx.EVT_BUTTON, self.on_stop)# 抽獎顯示區域self.display = wx.StaticText(panel, label="準備就緒", style=wx.ALIGN_CENTRE)self.display.SetFont(wx.Font(32, wx.FONTFAMILY_DEFAULT,wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))# 列表區域list_panel = wx.Panel(panel)self.list_ctrl = wx.ListBox(list_panel, style=wx.LB_SINGLE) # 單選模式self.winner_list = wx.ListBox(list_panel, style=wx.LB_SINGLE)# 布局main_sizer = wx.BoxSizer(wx.VERTICAL) # 垂直布局btn_sizer = wx.BoxSizer(wx.HORIZONTAL) # 水平布局btn_sizer.Add(btn_open, 0, wx.ALL, 5)btn_sizer.Add(btn_start, 0, wx.ALL, 5)btn_sizer.Add(btn_stop, 0, wx.ALL, 5)list_sizer = wx.BoxSizer(wx.HORIZONTAL)list_sizer.Add(wx.StaticText(list_panel, label="參與者列表"), 0, wx.ALL, 5)list_sizer.Add(self.list_ctrl, 1, wx.EXPAND | wx.ALL, 5)list_sizer.Add(wx.StaticText(list_panel, label="中獎名單"), 0, wx.ALL, 5)list_sizer.Add(self.winner_list, 1, wx.EXPAND | wx.ALL, 5)list_panel.SetSizer(list_sizer)main_sizer.Add(btn_sizer, 0, wx.ALIGN_CENTER | wx.TOP, 10)main_sizer.Add(self.display, 1, wx.EXPAND | wx.ALL, 20)main_sizer.Add(list_panel, 2, wx.EXPAND | wx.ALL, 10)panel.SetSizer(main_sizer)# 初始化btn_start.Disable()btn_stop.Disable()self.Bind(wx.EVT_TIMER, self.update_display)# wildcard 參數用于定義文件對話框(如 wx.FileDialog)中的文件類型過濾規則,其格式為 描述|通配符,# 多個過濾規則之間用豎線 | 分隔# wildcard = "描述1|通配符1|描述2|通配符2|..."# 描述:顯示在文件類型下拉框中的可讀文本(如 "文本文件 (*.txt)")。# 通配符:實際用于過濾文件擴展名的模式(如 *.txt)。# 添加“所有文件”選項:wildcard = "文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv|所有文件 (*.*)|*.*"def on_import(self, event):# 規則1:文本文件 (*.txt)、*.txt(匹配所有 .txt 文件)# 規則2:CSV文件 (*.csv)、*.csv(匹配所有 .csv 文件)wildcard = "文本文件 (*.txt)|*.txt|CSV文件 (*.csv)|*.csv"# wx.FileDialog 是用于創建 文件選擇對話框 的類# wx.FD_FILE_MUST_EXIST:用戶必須選擇已存在的文件(禁止輸入不存在的文件名)# wx.FD_OPEN: 對話框為“打開文件”模式(默認是打開,對應還有 wx.FD_SAVE 保存模式)dlg = wx.FileDialog(self, "選擇名單文件",wildcard=wildcard, # 文件類型過濾規則(如 "*.txt;*.csv")style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)# dlg.ShowModal() 顯示模態對話框,并等待用戶操作# 該方法會返回一個整數(如 wx.ID_OK 或 wx.ID_CANCEL),表示用戶的操作結果(例如點擊了“確定”還是“取消”按鈕)# wx.ID_OK:用戶點擊“確定”或類似確認按鈕。# wx.ID_CANCEL:用戶點擊“取消”或關閉對話框。if dlg.ShowModal() == wx.ID_CANCEL:return# 獲取用戶選擇的文件路徑path = dlg.GetPath()try:if path.endswith('.csv'):# 安全打開并讀取文件# 使用上下文管理器打開文件,確保文件操作結束后自動關閉文件(避免資源泄漏)。即使代碼塊中發生異常,文件也會正確關閉。# path:要打開的文件的路徑(字符串類型)。# 'r':以只讀模式打開文件(默認模式)。# 'w':寫入模式(覆蓋原有內容)。# 'a':追加模式(在文件末尾添加內容)。# 'r+':讀寫模式。# encoding='utf-8-sig':UTF-8 with BOM。讀取時:自動識別并去除 BOM。寫入時:自動添加 BOM。# 對比普通 UTF-8:普通 utf-8 編碼不會處理 BOM,若文件包含 BOM,直接讀取時會顯示為開頭的 \ufeff 字符。with open(path, 'r', encoding='utf-8-sig') as f:# 創建CSV文件讀取器,使用 Python 的 csv 模塊創建一個 CSV 讀取器對象(reader)# ,用于逐行解析 CSV 文件內容。# 每行文本會被自動拆分為列表(例如 "Alice,30" → ["Alice", "30"])。reader = csv.reader(f)# for row in reader:遍歷 CSV 文件中的每一行,每行 row 是一個列表(例如 ["張三", "男", "30"])。# if row:過濾空行(跳過內容為空的無效行)# row[0].strip() row[0]:取每行的第一個字段(索引為0的列).strip():去除該字段首尾的空白字符(如空格、換行符等),確保數據干凈。# 將所有行的第一個字段(處理后)存入 self.participants 列表(例如 ["張三", "李四", ...])。self.participants = [row[0].strip() for row in reader if row] # 提取首列數據并處理else:with open(path, 'r', encoding='utf-8') as f:self.participants = [line.strip() for line in f ifline.strip()] # if line.strip() 跳過空行(防止將空字符串或純空白行加入列表)self.list_ctrl.Set(self.participants) # 將 self.participants(通常是一個字符串列表)中的條目顯示到列表控件中。# 通過名稱查找界面中名為“開始抽獎”的控件,并啟用該控件(使其可交互)# self.FindWindowByName("開始抽獎"):在窗口或容器中查找名為 "開始抽獎" 的控件(如按鈕、文本框等)# .Enable():啟用控件,使其可以響應用戶操作(如點擊按鈕、輸入文本等)# Enable(True) 或 Enable():啟用控件。Enable(False):禁用控件(灰色不可用狀態)。# FindWindowByName() 可以查找任意類型的控件(按鈕、文本框、列表等),只要其名稱匹配。self.FindWindowByName("開始抽獎").Enable()self.SetStatusText(f"成功導入 {len(self.participants)} 位參與者")except Exception as e:wx.MessageBox(f"文件讀取失敗: {str(e)}", "錯誤",wx.OK | wx.ICON_ERROR) # 彈出一個錯誤提示對話框# 組合標志,表示彈窗包含“確定”按鈕和錯誤圖標。# 錯誤信息動態化:{str(e)} 會將捕獲的異常(如 FileNotFoundError)轉換為字符串,顯示具體錯誤原因(例如 文件未找到: 'data.txt')# wx.OK:彈窗顯示“確定”按鈕。# wx.ICON_ERROR:顯示紅色錯誤圖標(?? 或 ?,依操作系統而定)。def on_start(self, event):if not self.participants: # 檢查參與者列表是否為空wx.MessageBox("參與者列表為空!", "警告", wx.OK | wx.ICON_WARNING)returnself.is_rolling = True# event.GetEventObject():獲取觸發當前事件的控件對象(例如按鈕、菜單項等)。觸發事件的控件實例(如 wx.Button、wx.TextCtrl 等)。event.GetEventObject().Disable() # 禁用當前按鈕("開始抽獎"按鈕)self.FindWindowByName("停止").Enable() # 啟用"停止"按鈕self.FindWindowByName("導入名單").Disable() # 禁用"導入名單"按鈕self.timer.Start(50) # 定時器(間隔50ms)快速滾動動畫def update_display(self, event):if self.is_rolling and self.participants:self.current_roll = random.choice(self.participants)self.display.SetLabel(self.current_roll)self.display.SetForegroundColour(random.choice([wx.RED, wx.BLUE, wx.GREEN, wx.BLACK]))def on_stop(self, event):self.timer.Stop()self.is_rolling = Falseif self.current_roll:self.winners.append(self.current_roll)self.participants.remove(self.current_roll)self.winner_list.Append(self.current_roll)self.list_ctrl.Set(self.participants)self.FindWindowByName("開始抽獎").Enable()self.FindWindowByName("停止").Disable()self.FindWindowByName("導入名單").Enable()self.SetStatusText(f"剩余參與者: {len(self.participants)} 人")if __name__ == "__main__":app = wx.App()frame = LotteryFrame()app.MainLoop()