wxPython基础知识

4
我是一名有用的助手,可以为你翻译文本。
我正在尝试理解wxPython,但大部分文档都只是以照猫画虎的方式呈现程序,而没有解释库的基本原理。
请考虑以下代码片段:
import wx

class MyFrame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, (-1, -1), wx.Size(250, 50))
        panel = wx.Panel(self, -1)
        box = wx.BoxSizer(wx.HORIZONTAL)
        box.Add(wx.Button(panel, -1, 'Button1'), 1 )
        box.Add(wx.Button(panel, -1, 'Button2'), 1 )
        box.Add(wx.Button(panel, -1, 'Button3'), 1 )
        panel.SetSizer(box)
        self.Centre()

class MyApp(wx.App):
     def OnInit(self):
         frame = MyFrame(None, -1, 'wxboxsizer.py')
         frame.Show(True)
         return True

app = MyApp(0)
app.MainLoop()

我看到这里有三个容器 - 框架、面板和盒子。
然后有三个按钮。
1. 有人可以解释一下哪个容器放在哪个容器里吗? 2. 面板放到框架里面了吗?如果是的话,它被添加到框架的哪里了? 3. 盒子呢?它是放在面板里面还是面板被放在盒子里面了? 4. 这些按钮放在哪里?是放在盒子里吗? 5. 为什么在一个位置使用panel.SetSizer()而在另一个位置使用box.Add()?
3个回答

5

让我们慢慢开发一个wxPython应用程序,看看它是如何工作的。

以下是创建wxPython应用程序所需的最少代码。它包含了一个wx.Frame(可以理解为一个窗口),但窗口里没有内容。app.MainLoop()是一个循环,用于捕获任何事件,例如鼠标点击、关闭或最小化窗口。

import wx

app = wx.App()

frame = wx.Frame(None, -1, 'A Frame')
frame.Show()

app.MainLoop()

一个单独的窗口并不是很有趣,但它仍然相当强大。我们可以添加菜单项和标题,甚至选择样式,如无最大化按钮。让我们完成这些。

import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Frame, self).__init__(*args, **kwargs)

        menubar = wx.MenuBar() # Create a menubar
        fileMenu = wx.Menu() # Create the file menu
        fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application') # Add a quit line
        menubar.Append(fileMenu, '&File') # Add the File menu to the Menubar
        self.SetMenuBar(menubar) # Set the menubar as THE menu bar

        self.Bind(wx.EVT_MENU, self.OnQuit, fitem) # Bind the quit line

        self.Show() # Show the frame

    def OnQuit(self, e):
        self.Close()

app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER 
    | wx.SYSTEM_MENU | wx.CAPTION |  wx.CLOSE_BOX) # Some styles
app.MainLoop()

你会立即注意到,事情变得相当复杂。实际上这是为了帮助我们保持组织。我们将我们的窗口框架移到了它自己的类中,并定义了一些特征。我们请求一个菜单,并将菜单项绑定到关闭应用程序的OnQuit()方法上。这都是在最基本的层面上完成的。
现在让我们添加一个面板。一个面板就像一个黑板。它位于wx.Frame的顶部(就像一个黑板靠墙)。一旦有了一个面板,我们就可以开始添加sizer和小部件。
import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Frame, self).__init__(*args, **kwargs)

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        self.Bind(wx.EVT_MENU, self.OnQuit, fitem)

        panel = wx.Panel(self, -1) # Added a panel!

        self.Show()

    def OnQuit(self, e):
        self.Close()

app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER 
    | wx.SYSTEM_MENU | wx.CAPTION |  wx.CLOSE_BOX)
app.MainLoop()

根据您的平台,您会注意到轻微的差异。窗口现在填充了一种较浅的颜色,那就是我们的面板。现在让我们添加一些按钮。

import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Frame, self).__init__(*args, **kwargs)

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        self.Bind(wx.EVT_MENU, self.OnQuit, fitem)

        panel = wx.Panel(self, -1)
        btn = wx.Button(panel, label='I am a closing button.') # Add a button
        btn.Bind(wx.EVT_BUTTON, self.OnQuit) # Bind the first button to quit
        btn2 = wx.Button(panel, label='I am a do nothing button.') # Add a second

        self.Show()

    def OnQuit(self, e):
        self.Close()

app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER 
    | wx.SYSTEM_MENU | wx.CAPTION |  wx.CLOSE_BOX)
app.MainLoop()

现在我们有坐落在面板上的按钮。但它们看起来很糟糕,一个挤在另一个上面。我们可以使用pos=(x,y)属性手动定位它们,但这非常繁琐。让我们请来我们的朋友boxsizer。
import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Frame, self).__init__(*args, **kwargs)

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        self.Bind(wx.EVT_MENU, self.OnQuit, fitem)

        panel = wx.Panel(self, -1)
        btn = wx.Button(panel, label='I am a closing button.')
        btn.Bind(wx.EVT_BUTTON, self.OnQuit)
        btn2 = wx.Button(panel, label='I am a do nothing button.')

        vbox = wx.BoxSizer(wx.VERTICAL) # Create a vertical boxsizer
        vbox.Add(btn) # Add button 1 to the sizer
        vbox.Add(btn2) # Add button 2 to the sizer

        panel.SetSizer(vbox) # Tell the panel to use this sizer as its sizer. 

        self.Show()

    def OnQuit(self, e):
        self.Close()

app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER 
    | wx.SYSTEM_MENU | wx.CAPTION |  wx.CLOSE_BOX)
app.MainLoop()

那已经好多了!现在让我们看看水平尺寸调整器是什么样子。
import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Frame, self).__init__(*args, **kwargs)

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        self.Bind(wx.EVT_MENU, self.OnQuit, fitem)

        panel = wx.Panel(self, -1)
        btn = wx.Button(panel, label='I am a closing button.')
        btn.Bind(wx.EVT_BUTTON, self.OnQuit)
        btn2 = wx.Button(panel, label='I am a do nothing button.')

        vbox = wx.BoxSizer(wx.VERTICAL) # A vertical sizer
        hbox = wx.BoxSizer(wx.HORIZONTAL) # And a horizontal one?? but why?
        hbox.Add(btn) # Let's add the buttons first
        hbox.Add(btn2)

        panel.SetSizer(hbox) # see why we need to tell the panel which sizer to use? We might have two!

        self.Show()

    def OnQuit(self, e):
        self.Close()

app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER 
    | wx.SYSTEM_MENU | wx.CAPTION |  wx.CLOSE_BOX)
app.MainLoop()

有趣!注意我保留了我们的vbox?让我们尝试将它们结合起来。我们需要更多的按钮,可能还需要一些TextCtrls。

import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super(Frame, self).__init__(*args, **kwargs)

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        self.Bind(wx.EVT_MENU, self.OnQuit, fitem)

        panel = wx.Panel(self, -1)
        btn = wx.Button(panel, label='I am a closing button.')
        btn.Bind(wx.EVT_BUTTON, self.OnQuit)
        btn2 = wx.Button(panel, label='I am a do nothing button.')
        txt1 = wx.TextCtrl(panel, size=(140,-1))
        txt2 = wx.TextCtrl(panel, size=(140,-1))
        txt3 = wx.TextCtrl(panel, size=(140,-1))
        btn3 = wx.Button(panel, label='I am a happy button.')
        btn4 = wx.Button(panel, label='I am a bacon button.')
        btn5 = wx.Button(panel, label='I am a python button.')


        # So many sizers! 
        vbox = wx.BoxSizer(wx.VERTICAL) 
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)
        hbox4 = wx.BoxSizer(wx.HORIZONTAL)
        hbox1.Add(btn)
        hbox1.Add(btn2)
        hbox1.Add(txt1)
        hbox2.Add(txt2)
        hbox2.Add(txt3)
        hbox2.Add(btn3)
        hbox3.Add(btn4)
        hbox4.Add(btn5)
        vbox.Add(hbox1)
        vbox.Add(hbox2)
        vbox.Add(hbox3)
        vbox.Add(hbox4)

        panel.SetSizer(vbox)

        self.Show()

    def OnQuit(self, e):
        self.Close()

app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER 
    | wx.SYSTEM_MENU | wx.CAPTION |  wx.CLOSE_BOX)
app.MainLoop()

我们首先将按钮和文本小部件添加到水平尺寸器中,然后再添加到垂直尺寸器中。在wxPython中,使用尺寸器嵌套尺寸器是非常常见的做法。

__________________________
|__hbox1_|_______________|  \
|_hbox2____|______|____|_|   \___VBOX
|___hbox3______|_________|   /
|_______|__hbox4_|_______|  /

有点像这样。在每个hbox中,我们有许多小部件。您可以自行决定要使用多少个小部件。希望这能帮到你。


1
干得好!我可能应该在我的回答上花更多的时间。 - Mike Driscoll
我为你写了那段代码,它不是复制粘贴的。通过将self作为第一个关键字添加到框架中来添加面板。这与说(parent=self, id=-1)是相同的。你可以做 frame = wx.Frame(None, -1, 'My Frame')panel = wx.Panel(frame, -1) 以保持简单。我向你展示了创建wxPython应用程序的传统方法以及如何将各部分组合在一起。如果你想知道其原因/方法,可以参考API文档。我建议你开始编写简单的GUI程序,你会获得直观的感觉。很抱歉我不能提供更多帮助。 - pedram
你可能指的是Zetcode网站,那是我学习wxPython的地方。我一直在使用他的基本模板。创建wxFrame的基本方法是相当普遍的。我知道Mike为他的应用程序有一个稍微不同的模板(我不认为他使用*args,**kwargs),但我怀疑没有任何真正的区别,我相信他的模板适用于你需要做的任何事情。wxPython维基百科也会有许多不同的样式(我倾向于不使用那些,有些比其他更糟糕)。其实只有很少的方法可以解决问题。 - pedram
是的,我在我的教程中不使用*args和**kwargs,因为我想要明确表达,让读者始终知道参数是什么。wxPython维基需要整理,但其中有一些宝藏。 - Mike Driscoll
你可以使用Show()和Hide()以及Layout() (例如,请参见此处:http://wxpython-users.1045709.n5.nabble.com/Hiding-and-showing-widgets-with-Show-Hide-td2269966.html) - pedram
显示剩余4条评论

3

wxPython是一种复杂的GUI工具包,但其他GUI工具包也是如此。让我们来稍微分解一下。回答第一个问题,wxPython GUI通常采用以下布局之一:

frame -> panel -> sizer -> widgets
frame -> sizer -> panel -> sizer -> widgets

通常我会选第一个。如果布局比较复杂,我可能会嵌套sizers,最终得到的是这样的:

frame -> panel -> sizer -> sizer1 -> widgets
                        -> sizer2 -> widgets

2) 第一个面板应始终作为唯一的小部件添加到框架中:

wx.Frame.__init__(self, None, title="Test")
panel = wx.Panel(self)

3) boxSizer通常放置在面板中。我通常会有一个顶级的boxSizer,将其提供给面板,然后在其中创建嵌套的sizer。

4) 按钮和其他小部件都放在面板和sizer中!你将按钮的父对象设置为面板,然后为了布局面板内的小部件,将它们放在面板的sizer对象中。如果将按钮的父对象设置为框架,则会出现混乱。

5) SetSizer用于告诉wxPython哪个小部件属于该sizer。在你的代码中,你将box sizer实例提供给了面板。sizer对象的Add()方法用于向sizer本身添加小部件(和sizer)。

希望这回答了你所有的问题。你可能还会发现以下文章很有用,因为它链接到我用于wx的大部分文档:http://www.blog.pythonlibrary.org/2010/12/05/wxpython-documentation/


"按钮和其他小部件都要放在面板和尺寸器中。" - 您能解释一下这是什么意思吗?Sizer是什么 - 它不是一个容器吗? - hrs
一个 sizer 就像是一个无形的容器,它定位和调整其包含的小部件大小,就像父子小部件之间的粘合剂。 - Yoriz
是的。该面板是父级,但是大小调整器控制小部件的位置和拉伸方式。如果您没有将小部件放在面板上,则选项卡可能无法正常工作。 - Mike Driscoll

0

我强烈推荐您获取一份由Noel Rappin和Robin Dunn合著的wxPython in Action。因为Robin是wxPython的主要作者/架构师之一,所以您将获得非常好的见解和清晰的解释。

须知 在有人问之前,我绝对没有与书籍、作者或出版商有任何商业关系。


我也推荐Cody Precord的“wxPython 2.8应用开发食谱”,因为它更加新。 - Mike Driscoll
@MikeDriscoll - 这也很好,但OP似乎对食谱式的方法不太满意,而且一些原则已经改变了——这是wx库的一个好处。 - Steve Barnes
1
真的...我喜欢邓恩的书,但它也包含了一些已经过时的东西。 - Mike Driscoll

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接