I'm trying to build myself a simple graphics calculator, as a way of teaching myself PyQt. I'd like the calculator to have a pane which lists all of the equations plotted and shows the line style used.
I began with a QListWidget to achieve these ends, the pane contains a scrollable list of equations, but does not show the line style because the QListWidget only allows strings or icons.
So I tried using a QAbstractScrollArea widget instead. For each equation I use QHBoxLayout to produce two Widgets, a label containing the equation string, and a QWidget within which I draw the line style. Then I stack all of the equation QHBoxLayouts within the ScrollArea using QVBoxLayout.
The problem is that QVBoxLayout uses all of the space available to it. So if I have only three equations, they are spread throughout the pane and not listed at the top as I'd like them to be, while if I have too many equations to fit in the pane they are stacked on top of one another rather than causing the area to be scrollable.
This is how the calculator appears with too many equations...
And this is how it looks with too few...
Does anyone have any suggestions of better ways to get around these issues? One idea is to generate icons programmatically to have the properties of the lines and to use these in the listwidget, is that possible?
It sounds like what you want is a QListWidget with multiple columns - and a QTreeWidget can be adapted to do exactly that.
Here's a simple demo:
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.list = QtGui.QTreeWidget(self)
self.list.setRootIsDecorated(False)
self.list.setUniformRowHeights(True)
self.list.setAllColumnsShowFocus(True)
self.list.setItemsExpandable(False)
self.list.header().hide()
self.list.setColumnCount(2)
for label, color in (
('v=x**2', 'red'),
('v=x/2', 'blue'),
('v=2*x', 'green'),
('v=3*2', 'orange'),
('v=5-x', 'purple'),
):
item = QtGui.QTreeWidgetItem([label, '------------'])
item.setForeground(1, QtGui.QColor(color))
self.list.addTopLevelItem(item)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.list)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 200)
window.show()
sys.exit(app.exec_())
Related
A long time ago, I wanted to make a logo appear on top of the text in a QPushButton stacked on top of each other, but I couldn't find anyway
I read some stylesheets (couldn't find a single doc to read it all about all styles I can apply to a button)
tried the setLayoutDirection (RightToLeft and LeftToRight were there, but no UpToDown direction)
In my (I wish) last attempt I tried to inherit a QAbstractButton (I didn't find QAbstractPushButton, so I guess QAbstractButton is the answer) and change its paintEvent/paintEngine to draw an image or maybe add a vbox inside it as a layout to draw to components, but I can't find anything in python (specially PySide) which has an example in any possible way close to that. The best thing I found was the analogue clock example which was not very helpful because it was trying to work a QWidget and not a QAbstractButton and I want to keep the feel of a Native looking button.
I like my final product to be something like this.
source of the implemention of that
Python Enaml toolkit supported this feature out of the box (in one of its widgets), and I know it is QT based, so I really wish to know how it is possible?
p.s.: Also, is there a market for qt widgets? e.g.: a plugin system. Because rewriting an android like switch doesn't seem like the correct thing that I should do! even a good tutorial or doc would be appreicated (excluding official doc)
It is easier than you think, you can use QToolButton() like this:
import sys
from PySide6.QtCore import Qt, QSize
from PySide6.QtWidgets import QApplication, QVBoxLayout,QStyle, QWidget,
QToolButton
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
button = QToolButton()
# here you choose the position of the icon and its text
button.setToolButtonStyle(
Qt.ToolButtonStyle.ToolButtonTextUnderIcon)
# here I just use built-in icon by PySide6 for this example
name = 'SP_DialogSaveButton'
pixmapi = getattr(QStyle, name)
icon = self.style().standardIcon(pixmapi)
# here we set text and icon of size 32x32 to the button
button.setIcon(icon)
button.setText("Sample text")
button.setIconSize(QSize(32, 32))
# finally we add our button to the layout
lay = QVBoxLayout(self)
lay.addWidget(button, alignment=Qt.AlignCenter)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())
Consider this example. I want to make a program where the main window is divided into three parts which can be resized. In the middle I want to have two widgets placed vertially, the bottom one is QTabWidget, where users can change certain properties. Currently I have only one tab and one property there can be more.
I saw similar questions (here and here) but I can't seem to fathom how all the different parts related to size and layout even work together in the first place + they were C++ questions.
Please help me resize QTabWidget to its minimum necessary size to show the contents of the current tab.
As side note you can point me to some understandable docs for a beginner in GUI and PyQt5.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QLineEdit, QLabel, QSplitter, QWidget, QListWidget, QApplication, QTabWidget, QGroupBox, \
QFormLayout, QSizePolicy, QLayout
from PyQt5.QtCore import Qt
class Example(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.init_tabs()
self.main_splitter = QSplitter(Qt.Horizontal)
some_left_widget = QWidget()
some_right_widget = QWidget()
mid = QSplitter(Qt.Vertical)
mid.addWidget(QListWidget())
mid.addWidget(self.tabs)
self.main_splitter.addWidget(some_left_widget)
self.main_splitter.addWidget(mid)
self.main_splitter.addWidget(some_right_widget)
self.setCentralWidget(self.main_splitter)
self.showMaximized()
def init_tabs(self):
self.properties_dict = {}
self.properties_dict['Text'] = QLineEdit()
self.tabs = QTabWidget()
self.properties_groupbox = QGroupBox("Overview")
layout = QFormLayout()
for k, v in self.properties_dict.items():
layout.addRow(QLabel(k + ':'), v)
self.properties_groupbox.setLayout(layout)
self.tabs.addTab(self.properties_groupbox, 'Properties')
# I have no idea how these work
self.properties_groupbox.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
self.properties_groupbox.resize(self.properties_groupbox.minimumSizeHint())
self.properties_groupbox.adjustSize()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Left one is now, right one is desired
A QSplitter uses complex computation to evaluate the sizes it assigns to each of its child widgets, especially when resizing (which is something that also happens as soon as it's first shown, like any other widget).
The most important aspects it takes into account are the widgets size hints (what the widget suggests it would be the preferable size) and size policy (how the widget can be resized and how it will behave if there's more or less available space).
To achieve what you want, you'll need to set the size policy stretch (which is the proportion of available space in the layout the widget will try to use).
Just add the following lines after adding the widgets to the splitter:
mid.setStretchFactor(0, 1)
mid.setStretchFactor(1, 0)
The first line indicates that the first widget (the list) will use a stretch factor of 1, while the second (the tab widget) will be 0. The stretch factor is computed based on the sum of all the stretch factors of the widgets.
In this way the list will try to uccupy the maximum available space (since 1 is the maximum of 1 + 0), while the tab the least.
Remember that stretch factor also consider the size hints of the widget, so if you set 2 to the list and 1 to the tab, you will not get a list with a height twice than that of the tab.
Also, as soon as the splitter is resized, the new proportions will be used when the splitter is resized, ignoring the previously set stretch factors.
I have a list of pyqt4 push button and want to move the position. Since it is troublesome it make lots of buttons variable I create them through a list. The code below
import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.buttons = []
for i in range(3):
self.buttons.append(QPushButton('',self))
self.buttons[-1].setFixedWidth(50)
self.buttons[-1].setFixedHeight(50)
self.buttons[-1].move(70*i+50,300)
layout.addWidget(self.buttons[-1])
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.resize(500,500)
window.show()
sys.exit(app.exec_())
don't work for specify the position
but
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.button1 = QPushButton('',self)
self.button1.setFixedWidth(50)
self.button1.setFixedHeight(50)
self.button1.move(50,300)
self.button2 = QPushButton('',self)
self.button2.setFixedWidth(50)
self.button2.setFixedHeight(50)
self.button2.move(120,300)
self.button3 = QPushButton('',self)
self.button3.setFixedWidth(50)
self.button3.setFixedHeight(50)
self.button3.move(190,300)
layout = QVBoxLayout(self)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
layout.addWidget(self.button3)
works fine.
What's the reason behind?
If you want to manually specify the geometry (position and size) of widgets, you should not add them to a layout.
Your second example "works" just because you already created and set a layout to the top-level widget (the Window class), and since a layout already exists the second one is not "installed". Running it from the console will shows this error:
StdErr: QLayout: Attempting to add QLayout "" to Window "", which already has a layout
When a widget is added to a layout, the ownership of the "layout item" (an abstract item used by layouts to manage its widgets) is transferred to the layout, and the widget is reparented to the widget that uses that layout.
Since the second layout cannot be set, it will not manage the geometries of the items you tried to add, and the result is that they will keep the geometries you set before.
The same result can be obtained if you remove all the last lines referencing the other layout, which is exactly what you need.
Also, note that in order to add a widget that is not managed by a layout, a parent is required. Your last example also works because you specified the window as the parent while instantiating them; if you don't do that the buttons will not be shown, but if you do show them (with show() or setVisible(True)) they will each appear in separate windows, as they will become their own top level windows.
If you don't have other widgets that should be managed by a layout, you can also avoid creating the first layout at all (but, still, the parent is required).
Finally, let me tell you that using manual geometries is generally discouraged, and there are very few and specific cases for which it's a good idea to go with.
The main reason behind this is that widgets tend to show very differently from device to device, and that depends on various aspects:
different OS and OS versions draw widgets differently (sometimes dramatically), and this involves varying the widget size and behavior; while this might not be a major issue for simple widgets, it can be a problem for things like item views, spinboxes, etc;
different OS and systems use different styles, which also includes things like internal widget content margins and minimum size;
specific system settings (most importantly, the default font) could make widgets mostly unreadable, specifically with text being clipped within the widget margins if the font size is too big;
depending on the OS, you might face issues if the system uses High DPI displays, and you could end up with very tiny widgets that are almost impossible to interact with;
fixed geometries force you (and the user) to have a fixed widget/window size, and (along with the DPI issue above) this can be a problem: the same widget could look too big or too small;
Because of their high customizability I've been relying on using multiple GroupBoxes while building app GUIs. But it appears QGroupBoxes make a certain impact on how fast an interface builds.
Now with layout's .insertLayout() method I can build an entire graphics interface placing the widgets any where I want. The dialogs feel very lightweight and extremely fast to re-draw. Unfortunately I can't find a way to control their appearance. I would appreciate if you would give me some clue on how to control the layout visual properties. I am particularly interested in knowing:
How to draw layout border, how to control a border line width,
How to place a layout title (similar to what QGroupBox's .setTitle() does)
How to control the layout outside and inside margins.
How to make layout minimizable/size-restorable (So the user could click some minus/arrow icon to fold/unfold layout when they need or don't need certain widgets belonging to the same layout.
The example with three nested layouts is posted below. As it is seen on dialog screenshot there is no way to visually differentiate one layout from another since there are no border, no titles, no dividers and etc.
from PyQt4 import QtGui, QtCore
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
tabWidget = QtGui.QTabWidget()
tabGroupBox = QtGui.QGroupBox()
tabLayout = QtGui.QVBoxLayout()
tabLayout.setContentsMargins(0, 0, 0, 0)
tabLayout.setSpacing(0)
subLayoutA=QtGui.QVBoxLayout()
tabLayout.insertLayout(0, subLayoutA)
tabGroupBox.setLayout(tabLayout)
tabWidget.addTab(tabGroupBox,' Tab A ')
listWidgetA = QtGui.QListWidget()
for i in range(3):
QtGui.QListWidgetItem( 'Item '+str(i), listWidgetA )
subLayoutA.addWidget(listWidgetA)
subLayoutB=QtGui.QHBoxLayout()
tabLayout.insertLayout(1, subLayoutB)
subLayoutB.addWidget(QtGui.QLineEdit('LineEdit 1'))
subLayoutB.addWidget(QtGui.QLineEdit('LineEdit 2'))
subLayoutC=QtGui.QVBoxLayout()
tabLayout.insertLayout(2, subLayoutC)
subLayoutC.addWidget(QtGui.QPushButton('PushButton 1'))
subLayoutC.addWidget(QtGui.QPushButton('PushButton 2'))
self.setCentralWidget(tabWidget)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())
EDITED LATER
I inserted two lines into an example code to implement one of the suggestions made by sebastian. A Spacing-Margins method combos can be effectively used to get some additional tweaks done. Here is a screenshot (still could not get rid of the spacing around pushButtons):
QLayout sublcasses don't have a visual representation, which becomes clear by the fact that QLayout classes do not inherit QWidget. They only calculate the positions of the widgets they are responsible for in the context of their "parent" widget.
So the answer to questions 1,2 and 4 is basically: You can't.
You'll always have to have a QWidget in combination with a QLayout.
E.g. to group your two buttons into a frame with a box use a QFrame:
subLayoutC=QtGui.QVBoxLayout()
buttonFrame = QtGui.QFrame()
buttonFrame.setFrameStyle(QtGui.QFrame.Plain |QtGui.QFrame.Box)
buttonFrame.setLayout(subLayoutC)
subLayoutC.addWidget(QtGui.QPushButton('PushButton 1'))
subLayoutC.addWidget(QtGui.QPushButton('PushButton 2'))
# now we add the QFrame widget - not subLayoutC to the tabLayout
tabLayout.addWidget(buttonFrame) # I think your suggested edit was correct here
self.setCentralWidget(tabWidget)
Concerning question 3, check the docs:
http://qt-project.org/doc/qt-4.8/qlayout.html#setContentsMargins
http://qt-project.org/doc/qt-4.8/qboxlayout.html#setSpacing
I'm writing a little Qt application with Python. I've created QMainWindow, which have a QGridLayout. In each grid I'm adding QTextBrowser Widget. I want left side of my grid to be not bigger than 25% of window. So I'll have two QTextBrowsers: one is 25% of window's width, and another is 75% of window's width. How can I do it? Thanks!
You can specify relative width by giving each cell a stretch with setStretch(). They will get sizes proportional to the given stretches. Here is a simple example that makes the right widget 3 times greater than the left widget.
import sys
from PyQt4 import QtGui
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
Q = QtGui.QWidget()
H = QtGui.QHBoxLayout()
H.addWidget(QtGui.QTextBrowser())
H.setStretch(0,1)
H.addWidget(QtGui.QTextBrowser())
H.setStretch(1,3)
Q.setLayout(H)
Q.show()
app.exec_()
But, bear in mind that widgets have minimum sizes by default. So they can shrink below that. If you want to change that behavior also, consider setting minimum sizes to your liking.