1. 程式人生 > >wxPython實例代碼(購物車)

wxPython實例代碼(購物車)

python wxPython

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

表格對象

窗口裏顯示的表格的內容,單獨再這裏的數據表格類裏面實現。這裏繼承了 GridTableBase 這個類,然後重構其中的幾個方法返回表格的行數、列數、單元格的內容,就能在窗口的表格裏裏顯示出來了:

"""自定義數據表格類"""
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]

效果

登錄窗口
技術分享圖片
商品列表窗口
技術分享圖片

wxPython實例代碼(購物車)