Редактирование: WxPython FAQ Layout Management

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Внимание: Вы не представились системе. Ваш IP-адрес будет записан в историю изменений этой страницы.

ПРЕДУПРЕЖДЕНИЕ: Длина этой страницы составляет 30 килобайт. Страницы, размер которых приближается к 32 КБ или превышает это значение, могут неверно отображаться в некоторых браузерах. Пожалуйста, рассмотрите вариант разбиения страницы на меньшие части.

Правка может быть отменена. Пожалуйста, просмотрите сравнение версий, чтобы убедиться, что это именно те изменения, которые вас интересуют, и нажмите «Записать страницу», чтобы изменения вступили в силу.
Текущая версия Ваш текст
Строка 1: Строка 1:
 +
A typical application consists of various widgets. Those widgets are placed inside container widgets.
 +
A programmer must manage the layout of the application. This is not an easy task.
 +
In wxPython we have two options.
 +
* absolute positioning
 +
* sizers
 +
== Absolute Positioning ==
 +
The programmer specifies the position and the size of each widget in pixels.
 +
When you use absolute positioning, you have to understand several things.
 +
 +
*the size and the position of a widget do not change, if you resize a window
 +
*applications look different (crappy) on various platforms
 +
*changing fonts in your application might spoil the layout
 +
*if you decide to change your layout, you must completely redo your layout, which is tedious and time consuming
 +
 +
There might be situations, where we can possibly use absolute positioning. For example, my tutorials. I do
 +
not want to make the examples too difficult, so I often use absolute positioning to explain a topic.
 +
But mostly, in real world programs, programmers use sizers.
 +
In our example we have a simple skeleton of a text editor. If we resize the window, the size of out wx.TextCtrl
 +
does not change as we would expect.
 +
 +
<center>
 +
[[image: wxPython_faq_absolute1.png | center]] [[image: wxPython_faq_absolute2.png | center]]
 +
</center>
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# absolute.py
 +
 +
import wx
 +
 +
class Absolute(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(250, 180))
 +
        panel = wx.Panel(self, -1)
 +
 +
        menubar = wx.MenuBar()
 +
        file = wx.Menu()
 +
        edit = wx.Menu()
 +
        help = wx.Menu()
 +
 +
        menubar.Append(file, '&File')
 +
        menubar.Append(edit, '&Edit')
 +
        menubar.Append(help, '&Help')
 +
        self.SetMenuBar(menubar)
 +
 +
        wx.TextCtrl(panel, -1, pos=(-1, -1), size=(250, 150))
 +
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
app = wx.App(0)
 +
Absolute(None, -1, '')
 +
app.MainLoop()
 +
</source>
 +
 +
<source lang="python">
 +
wx.TextCtrl(panel, -1, pos=(-1, -1), size=(250, 150))
 +
</source>
 +
 +
We do the absolute positioning in the constructor of the wx.TextCtrl. In our case, we provide the default position for the widget. The width is 250px and the height 150px.
 +
 +
== Using sizers ==
 +
Sizers do address all those issues, we mentioned by absolute positioning. We can choose among these sizers.
 +
*wx.BoxSizer
 +
*wx.StaticBoxSizer
 +
*wx.GridSizer
 +
*wx.FlexGridSizer
 +
*wx.GridBagSizer
 +
 +
<center>[[image: wxPython_faq_absolute1.png | center]] [[image: wxPython_faq_sizer.png | center]]</center>
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# sizer.py
 +
 +
import wx
 +
 +
class Sizer(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(250, 180))
 +
 +
        menubar = wx.MenuBar()
 +
        file = wx.Menu()
 +
        edit = wx.Menu()
 +
        help = wx.Menu()
 +
 +
        menubar.Append(file, '&File')
 +
        menubar.Append(edit, '&Edit')
 +
        menubar.Append(help, '&Help')
 +
        self.SetMenuBar(menubar)
 +
 +
        wx.TextCtrl(self, -1)
 +
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
app = wx.App(0)
 +
Sizer(None, -1, '')
 +
app.MainLoop()
 +
</source>
 +
 +
Ok, so you are saying that you don't see any sizers in the example? Well, the code example was a bit tricky.
 +
Actually, we placed the wx.TextCtrl inside the wx.Frame widget. The wx.Frame widget has a special built-in sizer.
 +
We can put only one widget inside the wx.Frame container. The child widget occupies all the space, which is not given to the borders, menu, toolbar and the statusbar.
 +
 +
== wx.BoxSizer ==
 +
This sizer enables us to put several widgets into a row or a column. We can put another sizer into an existing sizer. This way we can create very complex layouts.
 +
<source lang="python">
 +
box = wx.BoxSizer(integer orient)
 +
box.Add(wx.Window window, integer proportion=0, integer flag = 0, integer border = 0)
 +
</source>
 +
 +
The orientation can be wx.VERTICAL or wx.HORIZONTAL. Adding widgets into the wx.BoxSizer is done via the Add() method. In order to understand it, we need to look at its parameters.
 +
 +
The proportion parameter defines the ratio of how will the widgets change in the defined orientation. Let's assume we have three buttons with the proportions 0, 1, and 2. They are added into a horizontal wx.BoxSizer. Button with proportion 0 will not change at all. Button with proportion 2 will change twice more than the one with proportion 1 in the horizontal dimension.
 +
With the flag parameter you can further configure the behaviour of the widgets within a wx.BoxSizer. We can control the border between the widgets. We add some space between widgets in pixels. In order to apply border we need to define sides, where the border will be used. We can combine them with the | operator. e.g wx.LEFT | wx.BOTTOM. We can choose between these flags:
 +
 +
*wx.LEFT
 +
*wx.RIGHT
 +
*wx.BOTTOM
 +
*wx.TOP
 +
*wx.ALL
 +
 +
[[image: wxPython_faq_border.png | center]]
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# border.py
 +
 +
import wx
 +
 +
class Border(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(250, 200))
 +
 +
        panel = wx.Panel(self, -1)
 +
        panel.SetBackgroundColour('#4f5049')
 +
        vbox = wx.BoxSizer(wx.VERTICAL)
 +
 +
        midPan = wx.Panel(panel, -1)
 +
        midPan.SetBackgroundColour('#ededed')
 +
 +
        vbox.Add(midPan, 1, wx.EXPAND | wx.ALL, 20)
 +
        panel.SetSizer(vbox)
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
app = wx.App()
 +
Border(None, -1, '')
 +
app.MainLoop()
 +
</source>
 +
<source lang="python">
 +
vbox.Add(midPan, 1, wx.EXPAND | wx.ALL, 20)
 +
</source>
 +
In border.py we have placed a 20 px border around a midPan panel. wx.ALL applies the border size to all four sides.
 +
 +
If we use wx.EXPAND flag, our widget will use all the space that has been allotted to it. Lastly, we can also define the alignment of our widgets. We do it with the following flags :
 +
 +
*wx.ALIGN_LEFT
 +
*wx.ALIGN_RIGHT
 +
*wx.ALIGN_TOP
 +
*wx.ALIGN_BOTTOM
 +
*wx.ALIGN_CENTER_VERTICAL
 +
*wx.ALIGN_CENTER_HORIZONTAL
 +
*wx.ALIGN_CENTER
 +
 +
=== Go To Class ===
 +
In the following example we introduce several important ideas.
 +
 +
[[image: wxPython_faq_gotoclass.png | center]]
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# gotoclass.py
 +
 +
import wx
 +
 +
class GoToClass(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(390, 350))
 +
        panel = wx.Panel(self, -1)
 +
 +
        font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
 +
        font.SetPointSize(9)
 +
 +
        vbox = wx.BoxSizer(wx.VERTICAL)
 +
 +
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
 +
        st1 = wx.StaticText(panel, -1, 'Class Name')
 +
        st1.SetFont(font)
 +
        hbox1.Add(st1, 0, wx.RIGHT, 8)
 +
        tc = wx.TextCtrl(panel, -1)
 +
        hbox1.Add(tc, 1)
 +
        vbox.Add(hbox1, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 10)
 +
 +
        vbox.Add((-1, 10))
 +
 +
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
 +
        st2 = wx.StaticText(panel, -1, 'Matching Classes')
 +
        st2.SetFont(font)
 +
        hbox2.Add(st2, 0)
 +
        vbox.Add(hbox2, 0, wx.LEFT | wx.TOP, 10)
 +
 +
        vbox.Add((-1, 10))
 +
 +
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)
 +
        tc2 = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
 +
        hbox3.Add(tc2, 1, wx.EXPAND)
 +
        vbox.Add(hbox3, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 10)
 +
 +
        vbox.Add((-1, 25))
 +
 +
        hbox4 = wx.BoxSizer(wx.HORIZONTAL)
 +
        cb1 = wx.CheckBox(panel, -1, 'Case Sensitive')
 +
        cb1.SetFont(font)
 +
        hbox4.Add(cb1)
 +
        cb2 = wx.CheckBox(panel, -1, 'Nested Classes')
 +
        cb2.SetFont(font)
 +
        hbox4.Add(cb2, 0, wx.LEFT, 10)
 +
        cb3 = wx.CheckBox(panel, -1, 'Non-Project classes')
 +
        cb3.SetFont(font)
 +
        hbox4.Add(cb3, 0, wx.LEFT, 10)
 +
        vbox.Add(hbox4, 0, wx.LEFT, 10)
 +
 +
        vbox.Add((-1, 25))
 +
 +
        hbox5 = wx.BoxSizer(wx.HORIZONTAL)
 +
        btn1 = wx.Button(panel, -1, 'Ok', size=(70, 30))
 +
        hbox5.Add(btn1, 0)
 +
        btn2 = wx.Button(panel, -1, 'Close', size=(70, 30))
 +
        hbox5.Add(btn2, 0, wx.LEFT | wx.BOTTOM , 5)
 +
        vbox.Add(hbox5, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10)
 +
 +
        panel.SetSizer(vbox)
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
 +
app = wx.App()
 +
GoToClass(None, -1, 'Go To Class')
 +
app.MainLoop()
 +
</source>
 +
 +
The layout is straitforward. We create one vertical sizer. We put then five horizontal sizers into it.
 +
<source lang="python">
 +
font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
 +
font.SetPointSize(9)
 +
</source>
 +
The default system font was 10px. On my platform, it was too big for this kind of window. So I set the font size to 9px.
 +
 +
<source lang="python">
 +
vbox.Add(hbox3, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 10)
 +
vbox.Add((-1, 25))
 +
</source>
 +
We already know that we can control the distance among widgets by combining the flag parameter with the border parameter. But there is one real constraint. In the Add() method we can specify only one border for all given sides. In our example, we give 10px to the right and to the left.
 +
But we cannot give 25 px to the bottom. What we can do is to give 10px to the bottom, or 0px. If we omit wx.BOTTOM.
 +
So if we need different values, we can add some extra space. With the Add() method, we can insert widgets and space as well.
 +
 +
<source lang="python">
 +
vbox.Add(hbox5, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10)
 +
</source>
 +
We place the two buttons on the right side of the window. How do we do it? Three things are important to achieve this. The proportion, the align flag and the wx.EXPAND flag. The proportion must be zero. The buttons should not change their size, when we resize our window. We must not specify wx.EXPAND flag. The buttons occopy only the area that has been alotted to it. And finally, we must specify the wx.ALIGN_RIGHT flag. The horizontal sizer spreads from the left side of the window to the right side. So if we specify wx.ALIGN_RIGHT flag, the buttons are placed to the right side. Exactly, as we wanted.
 +
 +
=== Find/Replace Dialog ===
 +
A complex example follows. We will create a find/replace dialog. This kind of dialog can be found in the Eclipse IDE.
 +
 +
[[image: wxPython_faq_find_replace.png | center]]
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# Find/Replace Dialog
 +
 +
import wx
 +
 +
class FindReplace(wx.Dialog):
 +
    def __init__(self, parent, id, title):
 +
        wx.Dialog.__init__(self, parent, id, title, size=(255, 365))
 +
 +
        vbox_top = wx.BoxSizer(wx.VERTICAL)
 +
        panel = wx.Panel(self, -1)
 +
 +
        vbox = wx.BoxSizer(wx.VERTICAL)
 +
 +
        # panel1
 +
 +
        panel1 = wx.Panel(panel, -1)
 +
        grid1 = wx.GridSizer(2, 2)
 +
        grid1.Add(wx.StaticText(panel1, -1, 'Find: ', (5, 5)), 0,  wx.ALIGN_CENTER_VERTICAL)
 +
        grid1.Add(wx.ComboBox(panel1, -1, size=(120, -1)))
 +
        grid1.Add(wx.StaticText(panel1, -1, 'Replace with: ', (5, 5)), 0, wx.ALIGN_CENTER_VERTICAL)
 +
        grid1.Add(wx.ComboBox(panel1, -1, size=(120, -1)))
 +
 +
        panel1.SetSizer(grid1)
 +
        vbox.Add(panel1, 0, wx.BOTTOM | wx.TOP, 9)
 +
 +
        # panel2
 +
 +
        panel2 = wx.Panel(panel, -1)
 +
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
 +
 +
        sizer21 = wx.StaticBoxSizer(wx.StaticBox(panel2, -1, 'Direction'), orient=wx.VERTICAL)
 +
        sizer21.Add(wx.RadioButton(panel2, -1, 'Forward', style=wx.RB_GROUP))
 +
        sizer21.Add(wx.RadioButton(panel2, -1, 'Backward'))
 +
        hbox2.Add(sizer21, 1, wx.RIGHT, 5)
 +
 +
        sizer22 = wx.StaticBoxSizer(wx.StaticBox(panel2, -1, 'Scope'), orient=wx.VERTICAL)
 +
        # we must define wx.RB_GROUP style, otherwise all 4 RadioButtons would be mutually exclusive
 +
        sizer22.Add(wx.RadioButton(panel2, -1, 'All', style=wx.RB_GROUP))
 +
        sizer22.Add(wx.RadioButton(panel2, -1, 'Selected Lines'))
 +
        hbox2.Add(sizer22, 1)
 +
 +
        panel2.SetSizer(hbox2)
 +
        vbox.Add(panel2, 0, wx.BOTTOM, 9)
 +
 +
        # panel3
 +
 +
        panel3 = wx.Panel(panel, -1)
 +
        sizer3 = wx.StaticBoxSizer(wx.StaticBox(panel3, -1, 'Options'), orient=wx.VERTICAL)
 +
        vbox3 = wx.BoxSizer(wx.VERTICAL)
 +
        grid = wx.GridSizer(3, 2, 0, 5)
 +
        grid.Add(wx.CheckBox(panel3, -1, 'Case Sensitive'))
 +
        grid.Add(wx.CheckBox(panel3, -1, 'Wrap Search'))
 +
        grid.Add(wx.CheckBox(panel3, -1, 'Whole Word'))
 +
        grid.Add(wx.CheckBox(panel3, -1, 'Incremental'))
 +
        vbox3.Add(grid)
 +
        vbox3.Add(wx.CheckBox(panel3, -1, 'Regular expressions'))
 +
        sizer3.Add(vbox3, 0, wx.TOP, 4)
 +
 +
        panel3.SetSizer(sizer3)
 +
        vbox.Add(panel3, 0, wx.BOTTOM, 15)
 +
 +
        # panel4
 +
 +
        panel4 = wx.Panel(panel, -1)
 +
        sizer4 = wx.GridSizer(2, 2, 2, 2)
 +
        sizer4.Add(wx.Button(panel4, -1, 'Find', size=(120, -1)))
 +
        sizer4.Add(wx.Button(panel4, -1, 'Replace/Find', size=(120, -1)))
 +
        sizer4.Add(wx.Button(panel4, -1, 'Replace', size=(120, -1)))
 +
        sizer4.Add(wx.Button(panel4, -1, 'Replace All', size=(120, -1)))
 +
 +
        panel4.SetSizer(sizer4)
 +
        vbox.Add(panel4, 0, wx.BOTTOM, 9)
 +
 +
        # panel5
 +
 +
        panel5 = wx.Panel(panel, -1)
 +
        sizer5 = wx.BoxSizer(wx.HORIZONTAL)
 +
        sizer5.Add((191, -1), 1, wx.EXPAND | wx.ALIGN_RIGHT)
 +
        sizer5.Add(wx.Button(panel5, -1, 'Close', size=(50, -1)))
 +
 +
        panel5.SetSizer(sizer5)
 +
        vbox.Add(panel5, 1, wx.BOTTOM, 9)
 +
 +
        vbox_top.Add(vbox, 1, wx.LEFT, 5)
 +
        panel.SetSizer(vbox_top)
 +
 +
        self.Centre()
 +
        self.ShowModal()
 +
        self.Destroy()
 +
 +
 +
app = wx.App()
 +
FindReplace(None, -1, 'Find/Replace')
 +
app.MainLoop()
 +
</source>
 +
 +
(Remark for Windows users, put self.SetClientSize(panel.GetBestSize()) line before the ShowModal() method.)
 +
Before we actually code our layout, we should have an idea of how we are going to achieve our goal. A simple sketch of a more complex dialog or window might be handy. If we look at the dialog screenshot, we clearly see, that we can divite it into five parts. The close button will also have a separate panel. Each part will be a unique wx.Panel. Together we have then 6 panels.
 +
The first panel is a parent panel. It will host all the five panels, that we have identified.
 +
All five panels reside in one column. So the parent panel will have a vertical wx.BoxSizer. Apart from wx.BoxSizer-s we use wx.GridSizer-s as well. The wx.GridSizer will be explained in the next section. Well, there is not much to explain, since the usage of the wx.GridSizer is pretty straightforward.
 +
 +
<source lang="python">
 +
sizer4 = wx.GridSizer(2, 2, 2, 2)
 +
sizer4.Add(wx.Button(panel4, -1, 'Find', size=(120, -1)))
 +
sizer4.Add(wx.Button(panel4, -1, 'Replace/Find', size=(120, -1)))
 +
sizer4.Add(wx.Button(panel4, -1, 'Replace', size=(120, -1)))
 +
sizer4.Add(wx.Button(panel4, -1, 'Replace All', size=(120, -1)))
 +
</source>
 +
In our example the wx.GridSizer is very useful. We need four buttons of the same size in a particular panel. It is a job for wx.GridSizer, since it organizes all widgets in a grid of cells. Those cells all have the same size and the same width.
 +
 +
== wx.GridSizer ==
 +
wx.GridSizer lays out widgets in two dimensional table. Each cell within the table has the same size.
 +
<source lang="python">
 +
wx.GridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)
 +
</source>
 +
 +
In the constructor we specify the number of rows and columns in the table. And the vertical and horizontal space between our cells.
 +
In our example we create a skeleton of a calculator. It is a perfect example for wx.GridSizer.
 +
 +
[[image: wxPython_faq_gridsizer.png | center]]
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# gridsizer.py
 +
 +
import wx
 +
 +
class GridSizer(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(300, 250))
 +
 +
 +
        menubar = wx.MenuBar()
 +
        file = wx.Menu()
 +
        file.Append(1, '&Quit', 'Exit Calculator')
 +
        menubar.Append(file, '&File')
 +
        self.SetMenuBar(menubar)
 +
 +
        self.Bind(wx.EVT_MENU, self.OnClose, id=1)
 +
 +
        sizer = wx.BoxSizer(wx.VERTICAL)
 +
        self.display = wx.TextCtrl(self, -1, '',  style=wx.TE_RIGHT)
 +
        sizer.Add(self.display, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 4)
 +
        gs = wx.GridSizer(4, 4, 3, 3)
 +
 +
        gs.AddMany( [(wx.Button(self, -1, 'Cls'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, 'Bck'), 0, wx.EXPAND),
 +
            (wx.StaticText(self, -1, ''), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, 'Close'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '7'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '8'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '9'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '/'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '4'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '5'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '6'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '*'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '1'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '2'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '3'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '-'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '0'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '.'), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '='), 0, wx.EXPAND),
 +
            (wx.Button(self, -1, '+'), 0, wx.EXPAND) ])
 +
 +
        sizer.Add(gs, 1, wx.EXPAND)
 +
        self.SetSizer(sizer)
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
    def OnClose(self, event):
 +
        self.Close()
 +
 +
app = wx.App()
 +
GridSizer(None, -1, 'GridSizer')
 +
app.MainLoop()
 +
</source>
 +
 +
Notice how we managed to put a space between the Bck and the Close buttons. We simply put an empty wx.StaticText there.
 +
Such tricks are quite common.
 +
 +
In our example we used the AddMany() method. It is a convenience method for adding multiple widgets at one time.
 +
 +
<source lang="python">
 +
AddMany(list items)
 +
</source>
 +
 +
Widgets are placed inside the table in the order, they are added. The first row is filled first, then the second row etc.
 +
 +
 +
== wx.FlexGridSizer ==
 +
This sizer is similar to <i>wx.GridSizer</i>. It does  also lay out it's widgets in a two dimensional table. It adds some flexibility to it. <i>wx.GridSizer</i> cells are of the same size. All cells in <i>wx.FlexGridSizer</i> have the same height in a row. All cells have the same width in a column.
 +
But all rows and columns are not necessarily the same height or width.
 +
<source lang="python">
 +
wx.FlexGridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)
 +
</source>
 +
 +
<i>rows</i> and <i>cols</i> specify the number of rows and columns in a sizer. <i>vgap</i> and <i>hgap</i> add some space between widgets in both directions.
 +
 +
Many times developers have to develop dialogs for data input and modification. I find <i>wx.FlexGridSizer</i> suitable for such a task. A developer can easily set up a dialog window with this sizer. It is also possible to accomplish this with <i>wx.GridSizer</i>, but it would not look nice, because of the constraint that each cell has the same size.
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# flexgridsizer.py
 +
 +
import wx
 +
 +
class FlexGridSizer(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(290, 250))
 +
 +
        panel = wx.Panel(self, -1)
 +
 +
        hbox = wx.BoxSizer(wx.HORIZONTAL)
 +
 +
        fgs = wx.FlexGridSizer(3, 2, 9, 25)
 +
 +
        title = wx.StaticText(panel, -1, 'Title')
 +
        author = wx.StaticText(panel, -1, 'Author')
 +
        review = wx.StaticText(panel, -1, 'Review')
 +
 +
        tc1 = wx.TextCtrl(panel, -1)
 +
        tc2 = wx.TextCtrl(panel, -1)
 +
        tc3 = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
 +
 +
        fgs.AddMany([(title), (tc1, 1, wx.EXPAND), (author), (tc2, 1, wx.EXPAND),
 +
            (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)])
 +
 +
        fgs.AddGrowableRow(2, 1)
 +
        fgs.AddGrowableCol(1, 1)
 +
 +
        hbox.Add(fgs, 1, wx.ALL | wx.EXPAND, 15)
 +
        panel.SetSizer(hbox)
 +
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
app = wx.App()
 +
FlexGridSizer(None, -1, 'FlexGridSizer')
 +
app.MainLoop()
 +
</source>
 +
 +
<source lang="python">
 +
hbox = wx.BoxSizer(wx.HORIZONTAL)
 +
...
 +
hbox.Add(fgs, 1, wx.ALL | wx.EXPAND, 15)
 +
 +
</source>
 +
 +
We create a horizontal box sizer in order to put some space (15px) around the table of widgets.
 +
 +
<source lang="python">
 +
fgs.AddMany([(title), (tc1, 1, wx.EXPAND), (author), (tc2, 1, wx.EXPAND),
 +
      (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)])
 +
</source>
 +
 +
We add widgets to the sizer with the <i>AddMany()</i> method. Both <i>wx.FlexGridSizer</i> and <i>wx.GridSizer</i> share this method.
 +
 +
<source lang="python">
 +
fgs.AddGrowableRow(2, 1)
 +
fgs.AddGrowableCol(1, 1)
 +
</source>
 +
 +
We make the third row and second column growable. This way we let the text controls grow, when the window is resized.
 +
The first two text controls will grow in horizontal direction, the third one will grow in both direction.
 +
We must not forget to make the widgets expandable (wx.EXPAND) in order to make it really work.
 +
 +
[[image: wxPython_faq_flexgridsizer.png | center]]
 +
 +
== wx.GridBagSizer ==
 +
The most complicated sizer in wxPython. Many programmer find it difficult to use. This kind of sizer is not typical only for wxPython. We can
 +
find it in other toolkits as well. There is no magic in using this sizer. Even though it is more complicated, it is certainly not rocket science.
 +
All we have to do is to create several layouts with it. Find all the quirks. Play with it a bit. There are more difficult things in programming. Believe me.
 +
 +
This sizer enables explicit positioning of items. Items can also optionally span more than one row and/or column. wx.GridBagSizer has a simple constructor.
 +
 +
<source lang="python">
 +
wx.GridBagSizer(integer vgap, integer hgap)
 +
</source>
 +
 +
The vertical and the horizontal gap defines the space in pixels used among all children. We add items to the grid with the Add() method.
 +
 +
<source lang="python">
 +
Add(self, item, tuple pos, tuple span=wx.DefaultSpan, integer flag=0, integer border=0, userData=None)
 +
</source>
 +
 +
Item is a widget that you insert into the grid. pos specifies the position in the virtual grid. The topleft cell has pos of (0, 0). span is an optional spanning of the widget. e.g. span of (3, 2) spans a widget across 3 rows and 2 columns. flag and border were discussed earlier by wx.BoxSizer.
 +
 +
The items in the grid can change their size or keep the default size, when the window is resized. If you want your items to grow and shrink, you can use these two methods.
 +
 +
<source lang="python">
 +
AddGrowableRow(integer row)
 +
AddGrowableCol(integer col)
 +
</source>
 +
 +
=== Rename dialog ===
 +
[[image: wxPython_faq_rename.png | center]]
 +
 +
The first example is intentionally a very simple one. So that it could be easily understood. There is no need to be afraid of wx.GridBagSizer. Once you understand it's logic, it is quite simple to use it. In our example, we will create a rename dialog. It will have one wx.StaticText, one wx.TextCtrl and two wx.Button-s.
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# rename.py
 +
 +
import wx
 +
 +
class Rename(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(320, 130))
 +
 +
        panel = wx.Panel(self, -1)
 +
        sizer = wx.GridBagSizer(4, 4)
 +
 +
        text = wx.StaticText(panel, -1, 'Rename To')
 +
        sizer.Add(text, (0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 +
 +
        tc = wx.TextCtrl(panel, -1)
 +
        sizer.Add(tc, (1, 0), (1, 5), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 +
 +
        buttonOk = wx.Button(panel, -1, 'Ok', size=(90, 28))
 +
        buttonClose = wx.Button(panel, -1, 'Close', size=(90, 28))
 +
        sizer.Add(buttonOk, (3, 3))
 +
        sizer.Add(buttonClose, (3, 4), flag=wx.RIGHT | wx.BOTTOM, border=5)
 +
 +
        sizer.AddGrowableCol(1)
 +
        sizer.AddGrowableRow(2)
 +
        panel.SetSizerAndFit(sizer)
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
 +
app = wx.App()
 +
Rename(None, -1, 'Rename Dialog')
 +
app.MainLoop()
 +
</source>
 +
 +
We must look at the dialog window as a one big grid table.
 +
 +
<source lang="python">
 +
text = wx.StaticText(panel, -1, 'Rename To')
 +
sizer.Add(text, (0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 +
</source>
 +
 +
The text 'Rename to' goes to the left upper corner. So we specify the (0, 0) position. Plus we add some space to the bottom, left and bottom.
 +
 +
<source lang="python">
 +
tc = wx.TextCtrl(panel, -1)
 +
sizer.Add(tc, (1, 0), (1, 5), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 +
</source>
 +
 +
The wx.TextCtrl goes to the beginning of the second row (1, 0). Remember, that we count from zero. It expands 1 row and 5 columns. (1, 5). Plus we put 5 pixels of space to the left and to the right of the widget.
 +
 +
<source lang="python">
 +
sizer.Add(buttonOk, (3, 3))
 +
sizer.Add(buttonClose, (3, 4), flag=wx.RIGHT | wx.BOTTOM, border=5)
 +
</source>
 +
 +
We put two buttons into the fourth row. The third row is left empty, so that we have some space between the wx.TextCtrl and the buttons. We put the ok button into the fourth column and the close button into the fifth one. Notice that once we apply some space to one widget, it is applied to the whole row. That's why I did not specify bottom space for the ok button.
 +
A careful reader might notice, that we did not specify any space between the two buttons. e.g. we did not put any space to the right of the ok button, or to the right of the close button. In the constructor of the wx.GridBagSizer, we put some space between all widgets. So there is some space already.
 +
 +
<source lang="python">
 +
sizer.AddGrowableCol(1)
 +
sizer.AddGrowableRow(2)
 +
</source>
 +
 +
The last thing we must do, is to make our dialog resizable. We make the second column and the third row growable. Now we can expand or shrink our window. Try to comment those two lines and see what happens.
 +
 +
=== Open Resource ===
 +
The next example will be a bit more complicated. We will create an Open Resource window. This example will show a layout of a very handy dialog which you can find in Eclipse IDE.
 +
 +
[[image: wxPython_faq_openresource.png | center]]
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# openresource.py
 +
 +
import wx
 +
 +
class OpenResource(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title, size=(400, 500))
 +
 +
        panel = wx.Panel(self, -1)
 +
        sizer = wx.GridBagSizer(4, 4)
 +
 +
        text1 = wx.StaticText(panel, -1, 'Select a resource to open')
 +
        sizer.Add(text1, (0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 +
 +
        tc = wx.TextCtrl(panel, -1)
 +
        sizer.Add(tc, (1, 0), (1, 3), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 +
 +
        text2 = wx.StaticText(panel, -1, 'Matching resources')
 +
        sizer.Add(text2, (2, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 +
 +
        list1 = wx.ListBox(panel, -1, style=wx.LB_ALWAYS_SB)
 +
        sizer.Add(list1, (3, 0), (5, 3), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 +
 +
        text3 = wx.StaticText(panel, -1, 'In Folders')
 +
        sizer.Add(text3, (8, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=5)
 +
 +
        list2 = wx.ListBox(panel, -1, style=wx.LB_ALWAYS_SB)
 +
        sizer.Add(list2, (9, 0), (3, 3), wx.EXPAND | wx.LEFT | wx.RIGHT, 5)
 +
 +
        cb = wx.CheckBox(panel, -1, 'Show derived resources')
 +
        sizer.Add(cb, (12, 0), flag=wx.LEFT | wx.RIGHT, border=5)
 +
 +
        buttonOk = wx.Button(panel, -1, 'OK', size=(90, 28))
 +
        buttonCancel = wx.Button(panel, -1, 'Cancel', size=(90, 28))
 +
        sizer.Add(buttonOk, (14, 1))
 +
        sizer.Add(buttonCancel, (14, 2), flag=wx.RIGHT | wx.BOTTOM, border=5)
 +
 +
        help = wx.BitmapButton(panel, -1, wx.Bitmap('icons/help16.png'), style=wx.NO_BORDER)
 +
        sizer.Add(help, (14, 0), flag=wx.LEFT, border=5)
 +
 +
        sizer.AddGrowableCol(0)
 +
        sizer.AddGrowableRow(3)
 +
        sizer.AddGrowableRow(9)
 +
        sizer.SetEmptyCellSize((5, 5))
 +
        panel.SetSizer(sizer)
 +
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
app = wx.App()
 +
OpenResource(None, -1, 'Open Resource')
 +
app.MainLoop()
 +
</source>
 +
 +
<source lang="python">
 +
sizer.AddGrowableRow(3)
 +
sizer.AddGrowableRow(9)
 +
</source>
 +
 +
We want to have both wx.ListBox-es growable. So we make the first row of each wx.ListBox growable.
 +
 +
=== Create new class ===
 +
newclass.py example is a type of a window, that I found in JDeveloper. It is a dialog window for creating a new class in Java.
 +
 +
[[image: wxPython_faq_newclass.png | center]]
 +
 +
<source lang="python">
 +
#!/usr/bin/python
 +
# newclass.py
 +
 +
import wx
 +
 +
class NewClass(wx.Frame):
 +
    def __init__(self, parent, id, title):
 +
        wx.Frame.__init__(self, parent, id, title)
 +
 +
        panel = wx.Panel(self, -1)
 +
        sizer = wx.GridBagSizer(0, 0)
 +
 +
        text1 = wx.StaticText(panel, -1, 'Java Class')
 +
        sizer.Add(text1, (0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM, border=15)
 +
 +
        icon = wx.StaticBitmap(panel, -1, wx.Bitmap('icons/exec.png'))
 +
        sizer.Add(icon, (0, 4), flag=wx.LEFT,  border=45)
 +
 +
        line = wx.StaticLine(panel, -1 )
 +
        sizer.Add(line, (1, 0), (1, 5), wx.TOP | wx.EXPAND, -15)
 +
 +
        text2 = wx.StaticText(panel, -1, 'Name')
 +
        sizer.Add(text2, (2, 0), flag=wx.LEFT, border=10)
 +
 +
        tc1 = wx.TextCtrl(panel, -1, size=(-1, 30))
 +
        sizer.Add(tc1, (2, 1), (1, 3), wx.TOP | wx.EXPAND, -5)
 +
 +
        text3 = wx.StaticText(panel, -1, 'Package')
 +
        sizer.Add(text3, (3, 0), flag= wx.LEFT | wx.TOP, border=10)
 +
 +
        tc2 = wx.TextCtrl(panel, -1)
 +
        sizer.Add(tc2, (3, 1), (1, 3), wx.TOP | wx.EXPAND, 5)
 +
 +
        button1 = wx.Button(panel, -1, 'Browse...', size=(-1, 30))
 +
        sizer.Add(button1, (3, 4), (1, 1), wx.TOP | wx.LEFT | wx.RIGHT , 5)
 +
 +
        text4 = wx.StaticText(panel, -1, 'Extends')
 +
        sizer.Add(text4, (4, 0), flag=wx.TOP | wx.LEFT, border=10)
 +
 +
        combo = wx.ComboBox(panel, -1, )
 +
        sizer.Add(combo, (4, 1), (1, 3), wx.TOP | wx.EXPAND,  5)
 +
 +
        button2 = wx.Button(panel, -1, 'Browse...', size=(-1, 30))
 +
        sizer.Add(button2, (4, 4), (1, 1), wx.TOP | wx.LEFT | wx.RIGHT , 5)
 +
 +
        sb = wx.StaticBox(panel, -1, 'Optional Attributes')
 +
        boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
 +
        boxsizer.Add(wx.CheckBox(panel, -1, 'Public'), 0, wx.LEFT | wx.TOP, 5)
 +
        boxsizer.Add(wx.CheckBox(panel, -1, 'Generate Default Constructor'), 0,  wx.LEFT, 5)
 +
        boxsizer.Add(wx.CheckBox(panel, -1, 'Generate Main Method'), 0, wx.LEFT | wx.BOTTOM, 5)
 +
        sizer.Add(boxsizer, (5, 0), (1, 5), wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT , 10)
 +
        button3 = wx.Button(panel, -1, 'Help', size=(-1, 30))
 +
        sizer.Add(button3, (7, 0), (1, 1),  wx.LEFT, 10)
 +
 +
        button4 = wx.Button(panel, -1, 'Ok', size=(-1, 30))
 +
        sizer.Add(button4, (7, 3), (1, 1),  wx.LEFT, 10)
 +
 +
        button5 = wx.Button(panel, -1, 'Cancel', size=(-1, 30))
 +
        sizer.Add(button5, (7, 4), (1, 1),  wx.LEFT | wx.BOTTOM | wx.RIGHT, 10)
 +
 +
        sizer.AddGrowableCol(2)
 +
        sizer.Fit(self)
 +
        panel.SetSizer(sizer)
 +
        self.Centre()
 +
        self.Show(True)
 +
 +
 +
app = wx.App()
 +
NewClass(None, -1, 'Create Java Class')
 +
app.MainLoop()
 +
</source>
 +
 +
<source lang="python">
 +
line = wx.StaticLine(panel, -1 )
 +
sizer.Add(line, (1, 0), (1, 5), wx.TOP | wx.EXPAND, -15)
 +
</source>
 +
 +
Notice, that we have used negative number for setting the top border. We could use wx.BOTTOM with border 15. We would get the same result.
 +
 +
<source lang="python">
 +
icon = wx.StaticBitmap(panel, -1, wx.Bitmap('icons/exec.png'))
 +
sizer.Add(icon, (0, 4), flag=wx.LEFT,  border=45)
 +
</source>
 +
 +
We put an wx.StaticBitmap into the first row of the grid. We place it on the right side of the row. By using images we can make our applications look better.
 +
 +
<source lang="python">
 +
sizer.Fit(self)
 +
</source>
 +
 +
We did not set the size of the window explicitly. If we call Fit() method, the size of the window will exactly cover all widgets available.
 +
Try to comment this line and see what happens.
 +
 +
[[Категория:wxWidgets]]
 +
[[Категория:Python]]

Пожалуйста, обратите внимание, что все ваши добавления могут быть отредактированы или удалены другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см. Wiki.crossplatform.ru:Авторское право). НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!