wxPython是一個流行的跨平台GUI工具包。用這個就可以給程式做視窗界面了。
官方網站:www.wxpython.org
我這也沒好好學,就整了個小項目的例子。把代碼完整貼下面了,下次做界面的時候可以參考這改改。
文檔結構
主要就是幾個py檔案。resources檔案夾裡是放資源原件的,也就兩張圖檔
Cart
│ app_main.py
│
├─conf
│ settings.py
│ __init__.py
│
├─resources
│ bats.ico
│ dragon.jpg
│
└─ui
list_frame.py
list_grid_table.py
login_frame.py
my_frame.py
__init__.py
settings 檔案
這裡也沒設定,不過把資料放這裡了,主要是搞定做界面,就不連資料庫了:
# 登入的使用者名和密碼
ACCOUNTS = {
'admin': {'pwd': 'admin'},
'root': {'pwd': '123456'},
'user': {'pwd': 'user123'},
}
# 商品列名
COLUMN_NAMES = ["商品編号", "商品類别", "商品中文名", "商品英文名"]
# 商品類别
CATEGORY = ["食品", "酒類", "男裝", "女裝", "童裝"]
# 商品資訊,商品資訊有點少,最好多搞幾十條
PRODUCTS = [
{'id': "001", 'category': "食品", 'name_cn': "薯片", 'name_en': "ShuPian"},
{'id': "002", 'category': "酒類", 'name_cn': "葡萄酒", 'name_en': "PuTaoJiu"},
{'id': "003", 'category': "男裝", 'name_cn': "西裝", 'name_en': "XiZhuang"},
{'id': "004", 'category': "女裝", 'name_cn': "短裙", 'name_en': "DuanQun"},
{'id': "005", 'category': "童裝", 'name_cn': "連衣裙", 'name_en': "LianYiQun"},
]
啟動檔案
通過啟動檔案,啟動我們的項目,這裡主要是調用建立一個登入視窗,然後進入主事件循環
import wx
from ui.login_frame import LoginFrame
from ui.list_frame import ListFrame
class App(wx.App):
"""啟動子產品"""
def OnInit(self):
"""建立視窗對象"""
frame = LoginFrame()
# frame = ListFrame() # 單獨調試商品界面的時候,省的每次都要登入一下
frame.Show()
return True
if __name__ == '__main__':
app = App() # 執行個體化
app.MainLoop() # 進入主事件循環
視窗基類
先給所有的視窗定義個基類,把所有視窗共有的屬性定義在這個基類裡。之後各個視窗都基礎這個類:
"""定義Frame視窗基類"""
import sys
import wx
class MyFrame(wx.Frame):
session = {} # 模拟Web的session,保留會話的資料
def __init__(self, title, size):
super().__init__(parent=None, title=title, size=size,
style=wx.DEFAULT_FRAME_STYLE ^ wx.MAXIMIZE_BOX)
# style是定義視窗風格,具體看官網。https://docs.wxpython.org/wx.Frame.html#wx-frame
# 上面的DEFAULT就是包含了下面所有的風格的:
# wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER |
# wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN
# 上面的例子是去掉了其中的一個。官網的例子是這樣的:
# style = wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX) 去掉了2個來固定視窗大小
# 設定視窗居中
self.Center()
# 設定Frame視窗内容面闆
self.contentpanel = wx.Panel(parent=self)
# 圖示檔案
ico = wx.Icon("resources/bats.ico", wx.BITMAP_TYPE_ICO)
# 設定圖示
self.SetIcon(ico)
# 設定視窗大小,這裡設定了相同的最大和最小值,也就是固定了視窗大小。
# 因為上面的視窗風格了保留了wx.RESIZE_BORDER,是以這裡用另外一個放來來保證大小不可調整
# 這樣做有一點不好,就是滑鼠放在視窗邊緣,會變成調整視窗大小的樣子,但是拉不動視窗
self.SetSizeHints(size, size)
# 綁定關閉按鈕的點選事件
self.Bind(wx.EVT_CLOSE, self.on_close)
def on_close(self, event):
# 退出系統
self.Destroy()
sys.exit()
這裡設定固定視窗的大小不可調整,不過應該還是官網的方法更好吧。
另外還定義了視窗的圖示,和關閉視窗的所調用的方法。
self.contentpanel = wx.Panel(parent=self)
這個屬性在後面會一直使用
登入視窗
現在開始真正開始做視窗界面了,先做一個簡單的登入界面:
"""登入視窗"""
import wx
from ui.my_frame import MyFrame
from ui.list_frame import ListFrame
from conf import settings
class LoginFrame(MyFrame):
accounts = settings.ACCOUNTS
def __init__(self):
super().__init__(title="使用者登入", size=(340, 230))
# 建立界面中的控件
username_st = wx.StaticText(self.contentpanel, label="使用者名:") # 輸入框前面的提示标簽
password_st = wx.StaticText(self.contentpanel, label="密碼:")
self.username_txt = wx.TextCtrl(self.contentpanel) # 輸入框
self.password_txt = wx.TextCtrl(self.contentpanel, style=wx.TE_PASSWORD)
# 建立FlexGrid布局對象
fgs = wx.FlexGridSizer(2, 2, 20, 20) # 2行2列,行間距20,列間距20
fgs.AddMany([
# 下面套用了3個分隔,垂直居中,水準靠右,固定的最小尺寸
(username_st, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.FIXED_MINSIZE),
# 位置居中,尺寸是膨脹
(self.username_txt, 1, wx.CENTER | wx.EXPAND),
(password_st, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.FIXED_MINSIZE),
(self.password_txt, 1, wx.CENTER | wx.EXPAND),
])
# 設定FlexGrid布局對象
fgs.AddGrowableRow(0, 1) # 第一個0是指第一行,權重1
fgs.AddGrowableRow(1, 1) # 第一個1是指第二行,權重也是1
# 上面一共就2行,使用者名和密碼,就是2行的空間是一樣的
fgs.AddGrowableCol(0, 1) # 第一列,權重1,就是标簽的内容
fgs.AddGrowableCol(1, 4) # 第二列,權重4,就是輸入框,并且輸入框是膨脹的應該會撐滿
# 上面2列分成5分,第一列占1/5,第二列占4/5
# 建立按鈕對象
ok_btn = wx.Button(parent=self.contentpanel, label="确定")
cancel_btn = wx.Button(parent=self.contentpanel, label="取消")
# 綁定按鈕事件:事件類型,綁定的事件,綁定的按鈕
self.Bind(wx.EVT_BUTTON, self.ok_btn_onclick, ok_btn)
self.Bind(wx.EVT_BUTTON, self.cancel_btn_onclick, cancel_btn)
# 建立水準Box布局對象,放上面的2個按鈕
box_btn = wx.BoxSizer(wx.HORIZONTAL)
# 添加按鈕控件:居中,四周都有邊框,膨脹。border是設定邊框的大小,實際效果沒有框,但是占用空間
box_btn.Add(ok_btn, 1, wx.CENTER | wx.ALL | wx.EXPAND, border=10)
box_btn.Add(cancel_btn, 1, wx.CENTER | wx.ALL | wx.EXPAND, border=10)
# 建立垂直Box,把上面的fgs對象和box_btn對象都放進來
box_outer = wx.BoxSizer(wx.VERTICAL)
box_outer.Add(fgs, -1, wx.CENTER | wx.ALL | wx.EXPAND, border=25) # 權重是-1,就是不指定了
# (wx.ALL ^ wx.TOP)這裡隻加3面的邊框,上面就不加了
box_outer.Add(box_btn, -1, wx.CENTER | (wx.ALL ^ wx.TOP) | wx.EXPAND, border=20)
# 上面全部設定完成了,下面是設定Frame視窗内容面闆
self.contentpanel.SetSizer(box_outer) # self.contentpanel 是在父類裡定義的
def ok_btn_onclick(self, event):
username = self.username_txt.GetValue() # 取出輸入框的值
password = self.password_txt.GetValue()
if username in self.accounts:
if self.accounts[username].get('pwd') == password:
self.session['username'] = username
print("登入成功")
# 接下來要進入下一個Frame
frame = ListFrame()
frame.Show()
self.Hide() # 隐藏登入視窗
return
else:
msg = "使用者名或密碼錯誤"
else:
msg = "使用者名不存在"
print(msg)
dialog = wx.MessageDialog(self, msg, "登入失敗") # 建立對話框
dialog.ShowModal() # 顯示對話框
dialog.Destroy() # 銷毀對話框
def cancel_btn_onclick(self, event):
self.on_close(event)
商品清單視窗
"""商品清單視窗"""
import wx, wx.grid
from ui.my_frame import MyFrame
from ui.list_grid_table import ListGridTable
from conf import settings
class ListFrame(MyFrame):
def __init__(self):
super().__init__(title="商品清單", size=(1000, 700))
# 購物車
self.cart = {}
# 商品清單
self.data = settings.PRODUCTS
# 建立分隔視窗
splitter = wx.SplitterWindow(self.contentpanel, style=wx.SP_3DBORDER)
# 分隔視窗的左側面闆
self.left_panel = self.create_left_panel(splitter) # 先準備一個函數,到函數裡再去實作
# 分隔視窗的右側面闆
self.right_panel = self.create_right_panel(splitter)
# 設定分隔視窗的布局,調用SplitVertically方法就是左右布局
splitter.SplitVertically(self.left_panel, self.right_panel, 630)
# 設定整個視窗的布局,是一個垂直的Box布局
box_outer = wx.BoxSizer(wx.VERTICAL)
# 下面直接把這個Box放到内容面闆裡了,也可以最後放。
# 不過這個視窗的内容隻有一個垂直Box,是以無所謂了
# 還有其他的控件沒有寫,不過寫完都是往box_splitter裡添加
self.contentpanel.SetSizer(box_outer)
# 添加頂部對象
box_outer.Add(self.create_top_box(), 1, flag=wx.EXPAND | wx.ALL, border=20)
# 添加分隔視窗對象
box_outer.Add(splitter, 1, flag=wx.EXPAND | wx.ALL, border=10)
# 建立底部的狀态欄
self.CreateStatusBar()
self.SetStatusText("準備就緒,歡迎您:%s" % self.session['username'])
def create_top_box(self):
"""建立頂部的布局管理器"""
# 建立靜态文本
label_st = wx.StaticText(parent=self.contentpanel, label="選擇商品類别:", style=wx.ALIGN_RIGHT)
# 建立下拉清單對象,這裡取了一個name,之後可以通過name找到這個控件擷取裡面的内容
# 查找的方法用FindWindowByName,另外還可以ById和ByLabel
choice = wx.Choice(self.contentpanel, choices=settings.CATEGORY, name="choice")
# 建立按鈕對象
search_btn = wx.Button(parent=self.contentpanel, label="查詢")
reset_btn = wx.Button(parent=self.contentpanel, label="重置")
# 綁定事件
self.Bind(wx.EVT_BUTTON, self.search_btn_onclick, search_btn)
self.Bind(wx.EVT_BUTTON, self.reset_btn_onclick, reset_btn)
# 建立一個布局管理器,把上面的控件添加進去
box = wx.BoxSizer(wx.HORIZONTAL)
box.AddSpacer(200) # 添加空白
box.Add(label_st, 1, flag=wx.FIXED_MINSIZE | wx.ALL, border=10)
box.Add(choice, 1, flag=wx.FIXED_MINSIZE | wx.ALL, border=5)
box.Add(search_btn, 1, flag=wx.FIXED_MINSIZE | wx.ALL, border=5)
box.Add(reset_btn, 1, flag=wx.FIXED_MINSIZE | wx.ALL, border=5)
box.AddSpacer(300) # 添加空白
return box
def create_left_panel(self, parent):
"""建立分隔視窗的左側面闆"""
panel = wx.Panel(parent)
# 建立網格對象
grid = wx.grid.Grid(panel, name='grid')
# 綁定事件
self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.select_row_handler)
self.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.select_row_handler)
# 初始化網格
self.init_grid() # 還是到另一個函數裡去實作
# 建立水準Box的布局管理器
box = wx.BoxSizer()
# 設定Box的網格grid
box.Add(grid, 1, flag=wx.ALL, border=5)
panel.SetSizer(box)
return panel
def init_grid(self):
"""初始化網格對象"""
# 通過網格名字擷取到對象
grid = self.FindWindowByName('grid')
# 建立網格中所需要的表格,這裡的表格是一個類
table = ListGridTable(settings.COLUMN_NAMES, self.data)
# 設定網格的表格屬性
grid.SetTable(table, True)
# 擷取網格行的資訊對象,40是行高,每一行都是40,後面的清單是單獨指定每一行的,這裡是空清單
row_size_info = wx.grid.GridSizesInfo(40, [])
# 設定網格的行高
grid.SetRowSizes(row_size_info)
# 指定列寬,前面是0,後面分别指定每一列的列寬
col_size_info = wx.grid.GridSizesInfo(0, [100, 80, 130, 200])
grid.SetColSizes(col_size_info)
# 設定單元格預設字型
grid.SetDefaultCellFont(wx.Font(11, wx.FONTFAMILY_DEFAULT,
wx.FONTSTYLE_NORMAL,
wx.FONTWEIGHT_NORMAL,
faceName="微軟雅黑"))
# 設定表格标題的預設字型
grid.SetLabelFont(wx.Font(11, wx.FONTFAMILY_DEFAULT,
wx.FONTSTYLE_NORMAL,
wx.FONTWEIGHT_NORMAL,
faceName="微軟雅黑"))
# 設定網格選擇模式為行選擇模式
grid.SetSelectionMode(grid.wxGridSelectRows)
# 設定網格不能通過拖動改标高度和寬度
grid.DisableDragRowSize()
grid.DisableDragColSize()
def create_right_panel(self, parent):
"""建立分隔視窗的右側面闆"""
panel = wx.Panel(parent, style=wx.TAB_TRAVERSAL | wx.BORDER_DOUBLE)
panel.SetBackgroundColour(wx.WHITE) # 設定背景色,預設不是白色
# 顯示圖檔
img_path = "resources/dragon.jpg"
img = wx.Bitmap(img_path, wx.BITMAP_TYPE_ANY) # 第二個參數設定圖檔格式可以是任意的
img_bitmap = wx.StaticBitmap(panel, bitmap=img, name='img_bitmap')
# 商品類别
category = "商品類别:【還未選中商品】"
category_st = wx.StaticText(panel, label=category, name='category') # 建立控件同時指定label
# 商品名稱
name = "商品名稱:【還未選中商品】"
name_st = wx.StaticText(panel, label=name, name='name')
# 商品價格
price = "商品價格:¥{0:.2f}".format(128)
price_st = wx.StaticText(panel, label=price, name='price')
# 商品描述
description = "商品描述:%s" % "總之很好就是了"
description_st = wx.StaticText(panel, label=description, name='description')
# 建立按鈕對象
add_btn = wx.Button(panel, label="添加到購物車")
see_btn = wx.Button(panel, label="檢視購物車")
self.Bind(wx.EVT_BUTTON, self.add_btn_onclick, add_btn)
self.Bind(wx.EVT_BUTTON, self.see_btn_onclick, see_btn)
# 布局,垂直的Box布局管理器
box = wx.BoxSizer(wx.VERTICAL)
box.Add(img_bitmap, 1, flag=wx.CENTER | wx.ALL, border=30)
box.Add(category_st, 1, flag=wx.EXPAND | wx.ALL, border=10)
box.Add(name_st, 1, flag=wx.EXPAND | wx.ALL, border=10)
box.Add(price_st, 1, flag=wx.EXPAND | wx.ALL, border=10)
box.Add(description_st, 1, flag=wx.EXPAND | wx.ALL, border=10)
box.Add(add_btn, 1, flag=wx.EXPAND | wx.ALL, border=10)
box.Add(see_btn, 1, flag=wx.EXPAND | wx.ALL, border=10)
panel.SetSizer(box)
return panel
def search_btn_onclick(self, event):
"""查詢按鈕可以按商品類别進行篩選"""
choice = self.FindWindowByName('choice')
selected = choice.GetSelection()
if selected >= 0:
category = settings.CATEGORY[selected]
# 根據商品類别查詢商品
products = []
for item in settings.PRODUCTS:
if item.get('category') == category:
products.append(item)
# 上面已經生成了新了商品清單,将商品清單更新後,重新初始化網格
self.data = products
self.init_grid()
def reset_btn_onclick(self, event):
"""單擊重置按鈕
查詢所有的商品/擷取初始的商品清單
然後初始化網格
"""
self.data = settings.PRODUCTS
self.init_grid()
def select_row_handler(self, event):
"""選擇的網格的行事件處理
這裡會重新整理右側面闆的商品類别和名稱
"""
row_selected = event.GetRow()
if row_selected >= 0:
selected_data = self.data[row_selected]
# 商品類别
category = "商品類别:%s" % selected_data.get('category')
category_st = self.FindWindowByName('category')
category_st.SetLabelText(category) # 先建立好控件,再修改或者設定label
# 商品名稱
name = "商品名稱:%s" % selected_data.get('name_cn')
name_st = self.FindWindowByName('name')
name_st.SetLabelText(name)
# 重新整理布局,如果更換了圖檔應該是要重新整理的,沒換圖檔不用重新整理
# self.right_panel.Layout()
event.Skip() # 事件跳過,貌似這裡沒什麼用
def add_btn_onclick(self, event):
pass
def see_btn_onclick(self, event):
pass
表格對象
"""自定義資料表格類"""
from wx.grid import GridTableBase
class ListGridTable(GridTableBase):
"""自定義表格類
下面分别重構了4個方法
傳回行數、列數、每個單元格的内容、列标題
"""
def __init__(self, column_names, data):
super().__init__()
self.col_labels = column_names
self.data = data
def GetNumberRows(self):
return len(self.data)
def GetNumberCols(self):
return len(self.col_labels)
def GetValue(self, row, col):
products = self.data[row]
return {
0: products.get('id'),
1: products.get('category'),
2: products.get('name_cn'),
3: products.get('name_en'),
}.get(col)
def GetColLabelValue(self, col):
return self.col_labels[col]