Python qt: layouts

From wikinotes

This page contains QLayout subclasses, and container widgets.

General Info

Useful Methods

layout = QtWidgets.QVBoxLayout()

layout.setAlignment( QtCore.Qt.AlignTop )  # pin objects to the top of layout
layout.setSpacing(0)                       # space between all controls in a layout
layout.setContentsMargins(0,0, 0,0)        # marginSize inside layout before controls drawn

## QLayouts do not have a way to set a maximum size for themselves
## **but** QWidgets do. You can set a maximum layout size by adding
## the layout to a QWidget
widget = QtGui.QWidget()
widget.setLayout(hbox)
widget.setMaximumSize(0, 100)

layout.addLayout(layout)                  # Nest Layout (from parent)
layout.addWidget(button)                  # Add Widget to Layout
layout.addItem(spacer)                    # Add Spacer item to Layout
layout.indexOf(spacer)                    # get the index of an item in a layout

Object Stretchiness

Controls have default size-policies. For instance, buttons stretch horizontally (not vertically) even if their parent layout should make them do otherwise. You can override their sizePolicy with setSizePolicy.

Note
In the documentation, note that setSizePolicy is a member of the QtGui.QWidget class. Remember that this library is object-oriented, and QPusButtons are in the inheritance heirarchy as QtGui.QWidget.QPushButton. This means that they will inherit all the methods/attributes that belong to QtGui.QWidget.


btn = { 'a':'', 'b':'', 'c':'' }
for ctrl in btn:
    ctrl = QtGui.QPushButton( ctrl )
    ctrl.setSizePolicy( QtGui.QSizePolicy.Fixed,  QtGui.QSizePolicy.Expanding)
    vbox.addWidget( ctrl )

win.setLayout(vbox)
win.show()

Space Between Objects

branchVertLayout.setSpacing(0)
branchVertLayout.setContentsMargins(0,0, 0,0)

Attach Controls to Form Edge

h = QtGui.QHBoxLayout()
h.addStretch(1)  # All objects will be pushed all the way to the end of the layout

Layouts

QHBoxLayout/QVBoxLayout

# Create Buttons
okButton     = QtGui.QPushButton("OK")
cancelButton = QtGui.QPushButton("Cancel")

# Layouts
hbox = QtGui.QHBoxLayout()              # Create Horizonal BoxLayout
hbox.addStretch(1)                      # Push edge of layout all the way to it's maximum (right)
hbox.addWidget(okButton)                # Add items to Layout
hbox.addWidget(cancelButton)

vbox = QtGui.QVBoxLayout()              # Create Vertical BoxLayout
vbox.addStretch(1)                      # Push edge of vertical layout to it's maximum (bottom)
vbox.addLayout(hbox)                    # Add horizontal boxLayout
                                        # Combination of both means layout hbox items are at bottomRight

self.setLayout(vbox)                    # Attach 'vbox' layout to the window (so grows/shrinks with it)

GridLayout

Gridlayouts are interesting, and different than they are treated in MEL. You attach objects to specific positions in the grid, then adjust the space between the elements.

# Simple GridLayout
# =================

# Array of ButtonNames
names = ['Cls' , 'Bck' , ''  , 'Close' ,
           '7'   , '8'   , '9' , '/'     ,
           '4'   , '5'   , '6' , '*'     ,
           '1'   , '2'   , '3' , '-'     ,
           '0'   , '.'   , '=' , '+'       ]

# Build GridLayout
grid = QtGui.QGridLayout()

j   = 0
pos = [ (0, 0), (0, 1), (0, 2), (0, 3),     # This Array is unecessary, gridLayouts
        (1, 0), (1, 1), (1, 2), (1, 3),     # seem to automatically adjust to the
        (2, 0), (2, 1), (2, 2), (2, 3),     # largest index an item is assigned to
        (3, 0), (3, 1), (3, 2), (3, 3),
        (4, 0), (4, 1), (4, 2), (4, 3) ]

# Assign a button per-gridLayout-position
for i in names:
    button = QtGui.QPushButton(i)

    if j == 2: grid.addWidget( QtGui.QLabel(''), 0, 2 )
    else:      grid.addWidget( button, pos[j][0], pos[j][1] )  # GridLayouts work a little differently here
    j=j + 1                                                    # you seem to attach controls to a position
                                                               # rather than their left/right side...

# Attach a (primary) layout to the window
self.setLayout(grid)
# More Complicated Layout
# =======================

# Create GUI Objects
title  = QtGui.QLabel('Title' )       # Left Column GUI Objects
author = QtGui.QLabel('Author')
review = QtGui.QLabel('Review')

titleEdit  = QtGui.QLineEdit()        # Right Column GUI Objects
authorEdit = QtGui.QLineEdit()
reviewEdit = QtGui.QTextEdit()



# Create GridLayout
grid = QtGui.QGridLayout()
grid.setSpacing(90)                   ## Space between GUI elements in grid.
                                      # Can also adjust vertically/horizontally
                                      # separately  ( setHorizontalSpacing )

# Add Objects to Layout
grid.addWidget( title     ,1 ,0 )     ## ( object, y,x ) Note that like curses, these are y/x
grid.addWidget( titleEdit ,1 ,1 )

grid.addWidget( author     ,2 ,0 )
grid.addWidget( authorEdit ,2 ,1 )

grid.setRowMinimumHeight( 3, 50  )    ## Create an emtpy 'spacer' column in a gridLayout.
                                      # (you can also use this to set a minimum height for a column with items)

grid.addWidget( review     ,4 ,0       )
grid.addWidget( reviewEdit ,4 ,1 ,5 ,1 )

Container Widgets

These aren't actually layouts, but their sole purpose is to contain other widgets. The above information about layouts does not apply to these widgets.

QSplitter (drag-resize)

QSplitters let you create a layout that is resizeable by mouse.

NOTE:

The most effective place to use QSplitter()setStretchFactor is immediately after you add widgets to it. It does not seem to take effect if used too early, or too late.


WARNING:

A Vanilla QT Application will not draw anything to represen a splitter (no 3 dots, mayastyle). You can create global presets for this using CSS.

Create Horizontal QSplitter

splitH = QtGui.QSplitter( QtCore.Qt.Orientation( QtCore.Qt.Horizontal ) )

# Add 3 Objects to QSplitter
for i in xrange(0, 3):
    splitH.addWidget( QtGui.QPushButton( str(i)) )

# Make QPushButton at index[1]'s split occupy 50%
# of window's room by default
splitH.setStretchFactor( 1, 50 )

# Assign Parent Layout
mainV.addWidget(splitH)
# Add Layouts to QSplitter
logVSP = QtGui.QSplitter(QtCore.Qt.Orientation(QtCore.Qt.Vertical))

log      = QtGui.QWidget()                             # Create a QWidget to contain layouts
logV     = QtGui.QVBoxLayout()                         # Add Layout
logT     = Title()                                     # Add Title
logT.new( label='test', parent=logV )
logCont  = QtGui.QTextEdit()                           # Add TextEdit
logV.addWidget( logCont )
log.setLayout( logV )                                  # Set QWidget layout

logVSP.addWidget( log )                                # Add QWidget to QSplitter

QScrollArea

WARNING:

A QScrollArea's widget's layout does not behave the same when setting alignment on it. (ex: QVBoxLayout().setAlignment( QtCore.Qt.AlignTop ). You can work around this by nesting another layout/widget/layout combo underneath that layout. The childmost widget will handle alignment as you originally expected.

# build widgets
main_layout  = QtGui.QVBoxLayout()
scroll        = QtGui.QScrollArea()
layout        = QtGui.QVBoxLayout()
layout_widget = QtGui.QWidget()

# widget properties
scroll.setWidgetResizable(1)               # allows widget to change size (ex: if adding several widgets to a layout inside widget)
layout.setAlignment(QtCore.Qt.AlignTop)    # align all widgets inside scrollArea to top

# populate layout
main_layout.addItem(scroll)
scroll.setWidget(layout_widget)
layout_widget.setLayout(layout)