GUI(图形用户界面)简介

Python 有很多图形用户界面工具集, 其中的三巨头是Tkinter,wxPython和PyQt。他们都可以支持Windows, macOS和Linux操作系统,而且PyQt还支持移动端。

一个图形用户界面就是一个包含窗口、按键、还有很多其他小部件组成的的应用程序,用户可以根据这些小部件和你的应用进行交互,举一个形象的例子比如说浏览器,通过浏览器的地址栏,用户可以输入地址,访问网站,通过上一步或下一步按钮,用户可以返回或者前进到历史操作等等。

通过这篇文章,你可以学习到用Python图形用户界面工具集--wxPython构建一个图形用户界面应用。其中包括的内容有:

  • 使用wxPython快速入门
  • 定义一个GUI(graphical user interface)
  • 建立一个图形用户界面应用骨架
  • 建立一个可以正常工作的图形用户界面的应用

下面让我们开始吧

使用wxPython快速入门

wxPython工具集是用Python封装C++的一个叫wxWidgets的库。wxPython的初始版本是在1998年发布的,因此wxPython拥有很长一段时间了。wxPython不同于像PyQt或者TKinter的地方在于wxPython尽可能的使用本机平台上的实际小部件。这使得wxPython应用程序看起来就像运行本机的操作系统一样。

PyQt和Tkinter都自己绘制它们的小部件,这就是为什么它们并不总是匹配本机小部件,尽管PyQt用起来非常接近wxPython了。

这倒不是说wxPython不支持自定义小部件。事实上,wxPython有很多自定义的小部件,其中包括几十个核心小部件。为了学习这些小部件,我会在文末提供一个参考文章,里面包含很多小部件的应用实例,下载下来可以在没有网络的时候参考学习,实在是太便利了 哈哈。

安装wxpthon

本篇文章使用最新版本的wxPython4,这个版本也被叫做Phoenix(凤凰)版本。wxPython3版本和wxPython版本只支持Python2。Robin Dunn是这个工具的主要维护者,他创建了wxPython4版本,在wxPython版本中,他弃用了很多别名并清理了大量代码,使wxPython更加Pythonic,更容易维护!!!这里加鸡腿 哈哈

如果你正在用比较老的版本,想迁移到wxPython4版本上来,可以参考一下链接

wxPython4可以兼容Python2.7和Python3,你可以使用pip来安装wxPython4,在之前的版本是不支持这种安装方法的,现在你可以在你的终端上敲下如下命令进行安装:

pip install wxpython

注意: 在Mac OS X上,您需要安装一个编译器(如XCode)才能成功完成安装。在pip安装程序正常工作之前,Linux可能还需要您安装一些依赖项, 比如:freeglut3-dev, libgstreamer-plugins-base0.10-dev,和 libwebkitgtk-3.0-dev。

我的机器是一部ubuntu的虚拟机,在安装的时候竟然报错了!!!天呐,当时可真是郁闷啊,不过我还有其他的绝招,如果你用上述方法安装失败的话,可以尝试一下下面这个方法:

pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04/wxpython

注意:记得把ubuntu的版本改成你系统的版本哦

定义一个GUI界面

正如我们上面提到的那样,一个图形用户界面(GUI)是用户通过在窗口上的按钮或其他部件与你的应用进行交互的接口,用户界面有一些共同的元素:

  • 主窗口

  • 菜单

  • 工具栏

  • 按键

  • 文本输入

  • 标签

所有这些元素通常被叫做部件。wxPython支持很多的部件,开发者将通过这些部件开发出一套逻辑用来和用户进行交互。

事件循环

一个GUI是通过用户触发之后才开始工作的,触发这个动作就叫做一个事件。当用户在应用程序使用鼠标按下按钮或其他小部件时,会触发事件。在内部,GUI工具包其实运行着一个称为事件循环的无限循环。

当你正在编写一个图形用户界面的时候,你要做到的就是当哪个部件被触发时,你的应用程序将会做出对应的事件处理。在处理事件循环时特别要注意的是,他们可以被终止。当你终止一个事件循环,GUI将不会对用户进行响应。任何一个动作被处理,GUI做出响应的时间不应该超过0.25秒,响应每个动作应该尽可能的用独立的线程或者进程处理。这样将防止你的GUI反应卡顿,给用户一个更好的体检。

wxPython有一个特别的线程安全方法,你可以使用它来反馈到你的应用,让它知道这个线程已经完成了或者对线程进行更新。下面让我们创建一个应用框架演示一下事件是怎么工作的。

创建一个应用框架

GUI上下文中的应用程序框架是不具有任何事件处理程序的窗口小部件的用户界面。这些对于原型设计很有用。你基本上只需创建GUI并将其呈现给你的利益相关者,以便在将大量时间花在后端逻辑上。

import wx

app = wx.App()
frame = wx.Frame(parent=None, title='Hello World')
frame.Show()
app.MainLoop()

在这个例子中,有两个部分,分别是:wx.App和wx.Frame。wx.App是wxPython的应用对象,是运行GUI所必需的。wx.App这个对象调用了一个.MainLoop()的方法,它就是上一部分所说的时间循环。

wx.Frame会创建一个与用户交互的界面,在这个例子中,你告诉wxPython这个框架没有父框架,它的标题时Hello World.下面是运行代码的样子:


Hello World.jpg

默认情况下,wx.Frame将在顶部包含最小化,最大化和退出按钮。但是,您通常不会以这种方式创建应用程序,大多数wxPython代码都要求你对wx.Frame和其他小部件进行子类化,以便您可以充分利用该工具包。

让我们花一点时间重新用类来实现上面的功能:

 class MyFrame(wx.Frame):
     def __init__(self):
         super().__init__(parent=None, title='Hello World')
         self.Show()
  
if __name__ = '__main__':
     app = App()
     frame = MyFrame()
     app.MainLoop()

你可以使用这些代码作为你的应用程序的模板。但是,这个应用程序没有做什么操作,现在让我们花点时间学习一些其他小工具的用法,以便可以添加到应用上。

小工具

wxPython工具集有超过一百种小工具选择,它可以让你创建一个丰富的应用,但是这也意味着选择合适的小工具是一个艰巨的任务,这就是为什么wxPython提供的小样是非常有用的,因为它有一个搜索过滤的功能,帮助你寻找合适的小工具来用到你的应用程序中去。这个小样后面再给出地址哈。

大部分GUI应用允许用户输入一些文本然后点击按钮,让我们也添加这样的功能到我们的应用中去:

import  wx
class MyFrame(wx.Frame):
      def __init__(self):
            super().__init__(parent=None, title='Hello World')
            panel = wx.Panel(self)  

            self.text_ctrl = wx.TextCtrl(panel, pos=(5, 5))
            my_btn = wx.Button(panel, label='Press Me', pos=(5, 55))
   
            self.Show()
  
if __name__ == '__main__':
     app = wx.App()
     frame = MyFrame()
     app.MainLoop()

当你运行上面的代码,你的应用程序将会显示下面的样子:


press me.jpg

你需要添加的第一个小部件称为wx.Panel,这个工具不是必须的,但是是建议添加的。在Windows上,你实际上需要使用面板,以便框架的背景颜色为正确的灰色阴影。在Windows上没有Panel的情况下禁用Tab遍历。当你将面板窗口小部件添加到框架并且面板是框架的唯一子项时,它将自动展开以自行填充框架。

接下来就是添加一个wx.TextCtrl到面板中,几乎所有小部件的第一个参数是小部件应该进入哪个父级,在这个例子中,你想让这个文本输入框和按键房子面板的顶部, 因此panel就是它们的父级。既是把panel这个参数传到文本输入对象和按键对象的第一个参数。

你还需要告诉wxPython这些工具放着的位置,你可以通过传一个位置参数pos来传参。在wxPython中,原始位置是左上角的位置即(0,0),因此,对于文本控件,你告诉wxPython你要将其左上角从左侧(x)开始5个像素,从顶部开始5个像素(y)

然后你在添加一个按钮控件到面板上并给它一个标签叫做Press Me为了防止控件重叠,你需要设置y坐标55像素给这个按钮。

绝对位置

当你为窗口小部件的位置提供精确坐标时,你使用的技术称为绝对位置。大多数GUI工具包都提供此功能,但实际上并不推荐。

随着你的应用程序变得越来越复杂,很难跟踪所有窗口小部件位置以及是否必须移动窗口小部件。重置所有这些位置将成为一场噩梦

幸运的是,所有现代GUI工具包都为此提供了解决方案,这是你将要了解的内容。

Sizers(动态大小)

wxPython工具包包括sizer,用于创建动态布局。他们为你管理小部件的位置,并在你调整应用程序窗口大小时调整它们。其他GUI工具包将sizer称为布局,这是PyQt所做的。

下面是一些你经常会用到的主要类型的sizers:

  • wx.BoxSizer

  • wx.GridSizer

  • wx.FlexGridSizer

让我们添加一个wx.BoxSizer到你的样例中:

import wx
​
class MyFrame(wx.Frame):
         def __init__(self):
                 super().__init__(parent=None, title='Hello World')
                 panel = wx.Panel(self)
                 my_sizer = wx.BoxSizer(wx.VERTICAL)
                 self.text_ctrl = wx.TextCtrl(panel)
                 my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)
                 my_btn = wx.Button(panel, label='Press Me')
                 my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5)
                 panel.SetSizer(my_sizer)
                 self.Show()
if __name__ == '__main__':
         app = wx.App()
         frame = MyFrame()
         app.MainLoop()

运行上面的代码,将会得到如下图片界面:


press me.jpg

这里,你创建了一个wx.BoxSizer对象实例,然后传递了一个wx.VERTICAL参数,这是小部件添加到sizer的方向。

在这个例子中,小部件将垂直添加,这意味着它们将从上到下一次添加一个。你也可以将BoxSizer的方向设置为wx.HORIZONTAL。当你这样做时,小部件将从左到右添加。

要将小部件添加到sizer,你将使用.Add()方法。它最多接受五个参数:

  • window(部件)

  • proportion

  • flag

  • border

  • userData

window:要添加的窗口小部件,

proportion:比例设置此特定窗口小部件应该在sizer中相对于其他窗口小部件的空间大小。默认情况下,它为0,告诉wxPython将窗口小部件保留为默认比例。

flag:只要你用管道字符:|分隔它们,你实际上可以传入多个标志 wxPython工具包使用|使用一系列按位OR添加标志

border:告诉wxPython你需要在小部件周围有多少个边框像素

userData:仅在您想要对小部件的大小进行复杂操作时使用,实际上很少见到

将按钮添加到sizer遵循完全相同的步骤。然而,为了让事情变得更有趣,我继续为wx.CENTER切换了wx.EXPAND标志,以便按钮在屏幕上居中。

如果你想学习更多关于sizers的知识, 你可以访问wxPython documentation获取更多对这个话题的讨论。

添加一个事件

虽然你的应用程序在视觉上看起来更有趣,但它仍然没有做任何事情。例如,如果按下按钮,则不会发生任何事情

让我们接下来给这个按钮添加一个任务:

import wx
​
class MyFrame(wx.Frame):    
       def __init__(self):
             super().__init__(parent=None, title='Hello World')
             panel = wx.Panel(self)
             my_sizer = wx.BoxSizer(wx.VERTICAL)
             self.text_ctrl = wx.TextCtrl(panel)
             my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)
             my_btn = wx.Button(panel, label='Press Me')
             my_btn.Bind(wx.EVT_BUTTON, self.on_press)
             my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5)
             panel.SetSizer(my_sizer)
             self.Show()
​
       def on_press(self, event):
             value = self.text_ctrl.GetValue()
             if not value:
                   print("You didn't enter anything!")
             else:
                   print(f'You typed: "{value}"')
​
if __name__ == '__main__':
       app = wx.App()
       frame = MyFrame()
       app.MainLoop()

wxPython中的小部件允许你将事件绑定附加到它们,以便它们可以响应某些类型的事件。

你想让按键按下去得到响应,你可以通过调用Bind()方法实现,Bind()方法接受你想要绑定的事件,事件发生时,调用处理程序。

在此示例中,将按钮对象绑定到wx.EVT_BUTTON事件,并告诉它在触发该事件时调用on_press()

on_press()接受第二个可以调用event的参数。这是按惯例。如果你愿意,你可以把它叫做别的东西。但是,这里的event参数指的是当调用此方法时,它的第二个参数应该是某种事件对象。

在.on_press()中,您可以通过调用其GetValue()方法来获取文本控件的内容。然后根据文本控件的内容将字符串打印到标准输出中。

既然您已经掌握了基础知识,那么让我们学习如何创建一个有用的应用程序吧!

创建一个工作的应用程序

创建新东西的第一步是弄清楚你想要创建什么。接下来,我们将学习如何创建MP3标签编辑器!创建新内容的下一步是找出哪些包可以帮助我们完成任务。

如果您在Google上搜索Python mp3标记,会发现有几种选择

  • mp3-tagger

  • eyeD3

  • mutagen

我尝试了其中几个,eyeD3有一个很好的API,你可以使用,而不会陷入MP3的ID3规范。你可以使用pip安装eyeD3,如下所示:

定义一个用户界面

在设计界面时,总是很好地勾勒出你认为用户界面的外观。你需要能够执行以下操作:

  • 打开一个或多个MP3文件

  • 显示当前的MP3标签

  • 编辑MP3标签

大多数用户界面使用菜单或按钮来打开文件或文件夹。你可以使用“文件”菜单。由于你可能希望查看多个MP3文件的标签,因此你需要找到一个可以很好地完成此操作的小部件。

有列和行的表格是理想的,因为那时你可以为MP3标签添加标签列。 wxPython工具包有一些可用于此的小部件,前两个小部件如下

  • wx.grid.Grid

  • wx.ListCtrl

在这种情况下你应该使用wx.ListCtrl,因为Grid小部件是过度的,坦率地说它也相当复杂。最后,你需要一个按钮来编辑选定的MP3标签

既然你知道你想要什么,你就可以把它画出来:


editor.jpg

上面的插图让我们了解应用程序的外观。既然您知道自己想要做什么,那就是编码的时候了!

创建用户界面

有很多种不同的方法创建一个新的应用。例如,你需要遵循Model-View-Controller吗?你如何拆分类?每个文件一个类?有很多问题你需要去考虑,当你积累了更多GUI设计之后,你将会知道如何回答这些问题。

在这个例子中,你需要两个类:

  • A wx.Panel class

  • A wx.Frame class

让我们开始导入Panel类:

import eyed3
import glob
import wx
​
class Mp3Panel(wx.Panel):
       def __init__(self, parent):
             super().__init__(parent)
             main_sizer = wx.BoxSizer(wx.VERTICAL)
             self.row_obj_dict = {}

             self.list_ctrl = wx.ListCtrl(
             self, size = (-1, 100),
             style = wx.LC_REPORT | wx.BORDER_SUNKEN
 )
             self.list_ctrl.InsertColumn(0, 'Artist', width=140)
             self.list_ctrl.Insertcolumn(1, 'Album', width=140)
             self.list_ctrl.Insertcolumn(2, 'Title', width=200)
             main_sizer.Add(self.list_ctrl, 0, wx.ALL | wx.EXPAND, 5)
             edit_button = wx.Button(self, label='Edit')
             edit_button.Bind(wx.EVT_BUTTON, self.on_edit)
             main_sizer.Add(edit_button, 0, wx.ALL | wx.CENTER, 5)
             self.SetSizer(main_sizer)

       def on_edit(self, event):
             print('in on_edit')

       def update_mp3_listing(self, folder_path):
             print(folder_path)

接下来是Frame类:

class Mp3Frame(wx.Frame):
       def __init__(self):
             super().__init__(parent=None, title='Mp3 Tag Editor')
             self.panel = Mp3Panel(self)
             self.Show()

if __name__ == '__main__':
       app = wx.App(False)
       frame = Mp3Frame()
       app.MainLoop()</pre>

这个类比第一个简单得多,因为您需要做的就是设置框架的标题并实例化面板类Mp3Panel。完成所有操作后,您的用户界面应如下所示:


editor.jpg

这个用户界面看起来一切都挺好的,但是有一个问题是没有文件菜单,这让它不能添加MP3到应用中并且编辑它们的标签,现在让我们来修复它。

制作一个功能应用程序

使应用程序正常工作的第一步是更新应用程序,使其具有“文件”菜单,因为这样你就可以将MP3文件添加到创建中。菜单几乎总是添加到wx.Frame类中,因此这是您需要修改的类(待更新)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容