痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

痞子衡發表於2017-05-06

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是wxPython GUI構建工具wxFormBuilder

一、手工程式碼佈局GUI介面的煩惱

  如果你曾經設計過上位機軟體GUI介面,初始階段一定是純手工程式碼佈局GUI介面上的各個控制元件,相信你肯定遇到過如下煩惱:

  • 控制元件型別較難找:UI介面裡有很多控制元件型別,純手工寫程式碼需要翻看文件一個個去查詢這些控制元件的名字與用法。
  • 尺寸位置難調整:如果介面上已經布了多個控制元件,想要整體去調整這些控制元件的尺寸與位置是一件頭疼的事。
  • 效果檢視不實時:每新新增一個控制元件都需要執行才能檢視整體效果,如果程式碼新增不完善,可能無法執行看不到效果。

二、wxFormBuilder工具背景

  在講本文主角wxFormBuilder之前有必要提一下這個軟體的來歷,首先要追述到大名鼎鼎的跨平臺GUI庫wxWidgets,這個庫主要是用C++語言實現的;鑑於wxWidgets的流行,Robin Dunn用Python語言對wxWidgets做了一層封裝,封裝後便成了Python版GUI庫wxPython;下面是這兩個GUI庫的官方主頁:

  wxWidgets的各種UI控制元件功能均是通過class來實現的,這個連結 http://docs.wxwidgets.org/3.0/page_class_cat.html 列出了wxWidgets裡的所有class,wxPython並沒有實現wxWidgets裡全部class,但基本實現了大部分常用class,這個連結 https://docs.wxpython.org/wx.1moduleindex.html 列出了wxPython裡所有的class。

  知道了wxPython的class便可以開始設計GUI介面,但手工寫程式碼設計介面太繁瑣,因此wxFormBuilder應運而生,這是一款能夠視覺化設計介面的工具(並不是唯一工具,還有wxGlade、Boa Constructor等),通過該工具設計GUI介面後可自動生成wxPython程式碼,下面是wxFormBuilder的官方主頁:

三、wxFormBuilder快速上手

  使用wxFormBuilder去設計GUI介面可以不用掌握wxPython裡的各個控制元件class的具體用法,你只需要在wxFormBuilder軟體裡新增這些控制元件即可,下面痞子衡將簡介wxFormBuilder的用法:

3.1軟體介面

  安裝好wxFormBuilder軟體之後開啟這個軟體,可見到如下介面,介面主要分為四大區:專案區、控制元件區、編輯區、屬性區。軟體使用起來非常簡單,就是在【控制元件區】裡點選新增需要的控制元件,這些控制元件的效果會在【編輯區】裡實時顯示,並在【屬性區】這些控制元件的屬性,【專案區】用於顯示控制元件間的層級關係。

痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

3.2基礎佈局

  讓我們開始建立一個GUI的基礎框架,基礎框架包括:Frame(外圍輪廓)、Sizer(內部控制元件區)、menubar(頂部選單欄)、statusBar(底部狀態列)。
  第一步是新增一個Frame,這是GUI的輪廓基礎,其size(default為500; 300)決定了GUI整體介面的大小。
痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

  第二步是在Frame下新增一個Sizer,後續所有控制元件均是放在Sizer裡的。關於Sizer部分需要特別說明一下,wxPython提供的Sizer型別有如下七種:wxBoxSizer、wxWrapSizer、wxStaticBoxSizer、wxGridSizer、wxFlexGridSizer、wxGridBagSizer、wxStdDialogButtonSizer,Sizer的樣式決定了後續控制元件的整體相對位置關係,選定了Sizer即選定了GUI介面樣式。關於這七種Sizer的具體樣式請見 https://docs.wxpython.org/sizers_overview.html#sizers-overview如果你覺得單個Sizer裡的控制元件佈局太單調,你可以巢狀使用Sizer,這是實現GUI介面控制元件佈局多樣化的關鍵
痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

  第三步是在Frame頂部新增一個menubar:
痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

  第四步是在Frame底部新增一個statusBar:
痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

3.3多種控制元件

  基礎佈局搞定之後,接下來便是在Sizer裡新增控制元件,wxPython支援的控制元件非常豐富,其中比較常用的是如下幾個:button(按鈕)、staticText(靜態顯示文字框)、textCtrl(輸入輸出文字框)、Choice(核取方塊)、checkBox(選中框)、slider(滑動條)。前面痞子衡選擇的Sizer是wxBoxSizer,即自上而下佈局,因此這些控制元件在Sizer是自上而下排列的,各個控制元件的位置後續在屬性裡還可以微調,但改變不了自上而下的格局
痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

3.4控制元件屬性

  新增了所有控制元件之後,下一步便是分別設定控制元件的屬性,進一步調整控制元件。痞子衡以Button屬性為例,痞子衡勾選瞭如下4項比較重要的屬性設定,分別是name(button在後續python程式碼的物件名,一般需要按其功能修改,修改後使得程式碼閱讀/修改起來更直觀)、label(button在GUI裡顯示的標籤名,此處是MyButton,也需要按其功能修改,方便使用者使用軟體)、size(設定button的尺寸,這個尺寸最大不應超過Sizer尺寸)、flag(調整對齊方式從而調整Button在Sizer裡的位置)。另外有一個屬性不得不說,即控制元件位置pos,在wxFormBuilder裡設定這個屬性並不生效,痞子衡猜想可能跟Sizer樣式有關,因為Sizer決定了控制元件間相對位置關係,因此控制元件的pos不能隨意設定
痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

3.5觸發事件

  有些控制元件是需要有響應的,比如Button,在GUI軟體實際使用中,使用者如果按下了Button,應該需要觸發某個任務,任務需要有響應函式,這個響應函式需要在【Events】裡設定,Button的響應函式在OnButtonClick裡設定,痞子衡在這裡指定了響應函式名為showMessage。在wxFormBuilder裡我們只需要指定控制元件響應函式名即可,響應函式的具體功能實現,不屬於wxFormBuilder設計範疇。
痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

3.6生成程式碼

  當GUI介面佈局全部完成之後,需選擇File->Generate Code或F8生成python程式碼,需要複製所有的python程式碼並儲存在單獨的檔案裡,痞子衡儲存在了my_win.py檔案裡。
痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

  可以簡單看一下這個my_win.py裡的內容,程式碼裡首先import了wx庫(即wxPython庫),並定義了名為MyFrame1的class,這個class主要包含兩個函式__init__()和showMessage():__init__()裡初始化了各個控制元件成員self.m_xx,這與我們在wxFormBuilder裡新增控制元件是對應的;showMessage()是Button控制元件的響應函式,但這個響應函式並沒有任何實質程式碼,當然我們可以在這個函式裡面實現Button響應功能,但一般不建議直接在wxFormBuilder生成的程式碼裡新增程式碼,因為你可能隨時調整GUI頁面佈局,那麼main_win.py裡的程式碼會重新生成,這樣會覆蓋我們自己新增的程式碼,導致維護起來比較麻煩。

# -*- coding: utf-8 -*- 

###########################################################################
## Python code generated with wxFormBuilder (version Jul 11 2018)
## http://www.wxformbuilder.org/
##
## PLEASE DO *NOT* EDIT THIS FILE!
###########################################################################

import wx
import wx.xrc

###########################################################################
## Class MyFrame1
###########################################################################

class MyFrame1 ( wx.Frame ):
    
    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
        
        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
        
        bSizer1 = wx.BoxSizer( wx.VERTICAL )
        
        self.m_button1 = wx.Button( self, wx.ID_ANY, u"MyButton", wx.Point( -1,-1 ), wx.DefaultSize, 0 )
        bSizer1.Add( self.m_button1, 0, wx.ALL, 5 )
        
        self.m_staticText1 = wx.StaticText( self, wx.ID_ANY, u"MyLabel", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.m_staticText1.Wrap( -1 )
        
        bSizer1.Add( self.m_staticText1, 0, wx.ALL, 5 )
        
        self.m_textCtrl1 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.Point( -1,-1 ), wx.DefaultSize, 0 )
        bSizer1.Add( self.m_textCtrl1, 0, wx.ALL, 5 )
        
        m_choice1Choices = []
        self.m_choice1 = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choice1Choices, 0 )
        self.m_choice1.SetSelection( 0 )
        bSizer1.Add( self.m_choice1, 0, wx.ALL, 5 )
        
        self.m_checkBox1 = wx.CheckBox( self, wx.ID_ANY, u"Check Me!", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer1.Add( self.m_checkBox1, 0, wx.ALL, 5 )
        
        self.m_slider1 = wx.Slider( self, wx.ID_ANY, 50, 0, 100, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL )
        bSizer1.Add( self.m_slider1, 0, wx.ALL, 5 )
        
        
        self.SetSizer( bSizer1 )
        self.Layout()
        self.m_menubar1 = wx.MenuBar( 0 )
        self.m_menu1 = wx.Menu()
        self.m_menuItem1 = wx.MenuItem( self.m_menu1, wx.ID_ANY, u"MyMenuItem", wx.EmptyString, wx.ITEM_NORMAL )
        self.m_menu1.Append( self.m_menuItem1 )
        
        self.m_menubar1.Append( self.m_menu1, u"MyMenu" ) 
        
        self.SetMenuBar( self.m_menubar1 )
        
        self.m_statusBar1 = self.CreateStatusBar( 1, wx.STB_SIZEGRIP, wx.ID_ANY )
        
        self.Centre( wx.BOTH )
        
        # Connect Events
        self.m_button1.Bind( wx.EVT_BUTTON, self.showMessage )
    
    def __del__( self ):
        pass
    
    
    # Virtual event handlers, overide them in your derived class
    def showMessage( self, event ):
        event.Skip()
    

四、使用wxFormBuilder生成的程式碼

  前面已經使用wxFormBuilder生成GUI介面類MyFrame1並儲存在my_win.py檔案中,此時需要建立一個主函式檔案去呼叫MyFrame1,下面是痞子衡建立的main_win.py中的程式碼:

import wx
# 匯入my_win.py中內容
import my_win

# 建立mainWin類並傳入my_win.MyFrame1
class mainWin(my_win.MyFrame1):

   # 實現Button控制元件的響應函式showMessage
   def showMessage(self, event):
       self.m_textCtrl1.Clear()
       self.m_textCtrl1.SetValue('hello world')

if __name__ == '__main__':
    # 下面是使用wxPython的固定用法
    app = wx.App()

    main_win = mainWin(None)
    main_win.Show()

    app.MainLoop()

  最後讓我們測試一下這個GUI軟體,在命令列下執行main_win.py

PS D:\my_git_repo\> python .\main_win.py

痞子衡嵌入式:極易上手的視覺化wxPython GUI構建工具(wxFormBuilder)

  至此,wxPython GUI構建工具wxFormBuilder痞子衡便介紹完畢了,掌聲在哪裡~~~

參考資料

  1. wxPython的介面設計wxformbuilde初學筆記
  2. wxPython 基礎使用教程

相關文章