wxpython中的面板布局

0 投票
2 回答
797 浏览
提问于 2025-04-17 03:34

背景

作为一个刚接触Python和图形用户界面(GUI)编程的新手,我想创建一个基于wxpython的GUI应用程序。这个应用程序会运行一系列测试,这些测试的内容保存在文本文件里,测试的步骤会在窗口中显示出来。要选择特定的测试,只需点击那个测试旁边的复选框(在下面的图中是CheckListCtrl)。测试的结果会显示在MulilineTextCtrl_2Log中。

窗口应该是这样的:

我的ASCII艺术水平不太好,不过无所谓。

|-------------------------------------------------------------|  
|WINDOW TITLExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_[]X|  
|xxx[SelectAll]xxxxxx[DeselectAll]xxxxxxxxxxxxxxxxxxxxxxxxxxxx|    
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|  
|[CheckListCtrl] [MultilineTexTCtrl_1]xxxxxxxxxxxxxxxxxxxxxxxx|  
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|  
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx[Refresh]xxx|  
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx[Execute]xxx|  
|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|   
|xxxxxxx[MulilineTextCtrl_2Log]xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|     
|_____________________________________________________________|

我在创建这个窗口的盒子布局时遇到了困难。我很难想象如何创建面板。

    TestList = [('Test1', 'Test1Description1', 'base'),       ('Test2', 'Test1Description1', 'base'),'Test3', 'Test1Description3', 'base'),]   

class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):  
    def __init__(self, parent):  
        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
        CheckListCtrlMixin.__init__(self)
        ListCtrlAutoWidthMixin.__init__(self)

class MainGUI(wx.frame):


 panel = wx.Panel(self, -1)

        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        #topPanel = wx.Panel(panel,-1)
        topPanel = wx.Panel(panel, -1,pos=(0,0))
        bottomPanel = wx.Panel(panel, -1,pos=(1,0))


        self.log = wx.TextCtrl(bottomPanel, -1, style=wx.TE_MULTILINE)
        self.list = CheckListCtrl(bottomPanel)
        self.list.InsertColumn(0, 'TestID', width=140)
        self.list.InsertColumn(1, 'TestDescription')

        for i in TestList:
            index = self.list.InsertStringItem(sys.maxint, i[0])
            self.list.SetStringItem(index, 1, i[1])
            self.list.SetStringItem(index, 2, i[2])

        vbox2 = wx.BoxSizer(wx.VERTICAL)

        sel = wx.Button(topPanel, -1, 'Select All', size=(100, -1))
        des = wx.Button(topPanel, -1, 'Deselect All', size=(100, -1))

        refresh = wx.Button(bottomPanel, -1, 'Refresh', size=(100, -1))
        apply = wx.Button(bottomPanel, -1, 'Execute', size=(100, -1))



        self.Bind(wx.EVT_BUTTON, self.OnSelectAll, id=sel.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnDeselectAll, id=des.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnRefresh, id=refresh.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnExecute, id=apply.GetId())

        vbox2.Add(sel, 0, wx.TOP, 5)
        vbox2.Add(des)
        vbox2.Add(apply,wx.RIGHT)
        vbox2.Add(refresh)


        topPanel.SetSizer(vbox2)

        vbox.Add(self.list, 1, wx.EXPAND, 3)
        vbox.Add((-1, 10))
        vbox.Add(self.log, 0.5, wx.EXPAND)
        vbox.Add((-1, 10))

        bottomPanel.SetSizer(vbox)

        hbox.Add(bottomPanel, 0, wx.EXPAND | wx.RIGHT, 5)


        panel.SetSizer(hbox)

        self.Centre()
        self.Show(True)

def OnSelectAll(self, event):
    num = self.list.GetItemCount()
    for i in range(num):
        self.list.CheckItem(i)

def OnDeselectAll(self, event):
    num = self.list.GetItemCount()
    for i in range(num):
        self.list.CheckItem(i, False)

def OnRefresh(self, event):
    num = self.list.GetItemCount()
    for i in range(num):
        if i == 0: self.log.Clear()
        if self.list.IsChecked(i):
            self.log.AppendText(self.list.GetItemText(i) + '\n')

def OnExecute(self, event):
    num = self.list.GetItemCount()
    for i in range(num):
        if i == 0: self.log.Clear()
        if self.list.IsChecked(i):
            self.log.AppendText(self.list.GetItemText(i) + '\n')   

app = wx.App()
MainGUI(None, -1, 'Tester')
app.MainLoop()   

2 个回答

1

其实你并不需要那么多额外的面板。实际上,除了像wx.Notebooks这样的情况,你几乎只需要一个面板。对我来说,最简单的方式是先在纸上画个草图,然后给各个部分画个框。这样的话,我会用一个竖着的主BoxSizer。接着,我会用两个横着的BoxSizers来放按钮和其他控件。如果你需要把其他三个单独的控件也这样放,那你可能还需要一些带间隔的BoxSizers。总之,一旦你把这些控件放进不同的BoxSizers里,就可以把它们加到主Sizer里,这样就完成了。

或者你也可以用一个GridSizer,再加几个间隔来处理最后三个控件。

2

我推荐你使用wxglade来设计你的图形用户界面(GUI)。这个工具既快速又有效,等你有了一些经验后,你可以做大部分手动操作能做到的事情。

wxglade不支持你正在使用的wx.lib小部件,比如CheckListCtrlMixinListCtrlAutoWidthMixin,也不支持像你的CheckListCtrl这样的组合。

为了能够使用这些小部件,你可以暂时在GUI中放一个可用的小部件(比如wx.panel),然后在生成代码后,手动把wx.Panel的实例替换成你自己的特殊类。

最后,你的代码有一些主要问题,会导致异常,比如wx.frame(应该是wx.Frame)、错误的缩进或未定义的TestList。你的MainGUI类没有__init__方法,也没有初始化父类wx.Frame。另外,bottomPanel被添加到了hbox的布局中,但topPanel却没有。

你写的组织结构并没有产生你描述的GUI,而是两个面板和几个小部件的结构:

enter image description here

为了更接近你的描述,你至少需要再添加几个布局。使用wxglade,它会为你处理所有事情。

这里有一个wxglade生成的代码,适合你的设计(把wx.ListCtrl替换成你的CheckListCtrl):

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
# generated by wxGlade HG on Tue Oct 04 12:45:03 2011

import wx

# begin wxGlade: extracode
# end wxGlade

class MainGUI(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MainGUI.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.button_3 = wx.Button(self, -1, "Select All")
        self.button_4 = wx.Button(self, -1, "DeselectAll")
        self.list_ctrl_1 = wx.ListCtrl(self, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        self.text_ctrl_1 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)
        self.button_1 = wx.Button(self, -1, "Refresh")
        self.button_2 = wx.Button(self, -1, "Execute")
        self.text_ctrl_2 = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE)

        self.__set_properties()
        self.__do_layout()
        # end wxGlade

    def __set_properties(self):
        # begin wxGlade: MainGUI.__set_properties
        self.SetTitle("frame_1")
        self.SetBackgroundColour(wx.Colour(255, 170, 5))
        self.list_ctrl_1.SetMinSize((200, 264))
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MainGUI.__do_layout
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_3 = wx.BoxSizer(wx.VERTICAL)
        sizer_4 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_4.Add(self.button_3, 0, wx.ALL, 5)
        sizer_4.Add(self.button_4, 0, wx.ALL, 5)
        sizer_4.Add((20, 20), 1, wx.EXPAND, 0)
        sizer_1.Add(sizer_4, 0, wx.EXPAND, 0)
        sizer_2.Add(self.list_ctrl_1, 0, wx.ALL|wx.EXPAND, 5)
        sizer_2.Add(self.text_ctrl_1, 0, wx.ALL|wx.EXPAND, 5)
        sizer_3.Add(self.button_1, 0, wx.ALL, 5)
        sizer_3.Add(self.button_2, 0, wx.ALL, 5)
        sizer_2.Add(sizer_3, 0, wx.EXPAND, 0)
        sizer_1.Add(sizer_2, 1, wx.EXPAND, 0)
        sizer_1.Add(self.text_ctrl_2, 0, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer_1)
        sizer_1.Fit(self)
        self.Layout()
        # end wxGlade

# end of class MainGUI

if __name__ == "__main__":
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame_1 = MainGUI(None, -1, "")
    app.SetTopWindow(frame_1)
    frame_1.Show()
    app.MainLoop()

还有它的外观:

enter image description here

注意,正如其他回答中提到的,不需要把小部件放在面板中(widget(panel,..))再把面板放在布局中。这样是可以的,但更好的做法是直接把小部件放在框架下(widget(self,..)),然后把它们放入布局中(sizerx.Add(widget,..)),再把布局放入其他布局中(sizery.Add(sizerx))来构建结构。
通常你不需要修改这段代码,而是通过子类化来添加你的功能或其他需要的内容。这样你就可以随时使用wxglade来改变小部件的位置,或者添加额外的小部件或布局,并重新生成完整的文件。

撰写回答