I'm trying to add custom RadioWidgets as QListWidgetItem to a QListWidget. The Mainwindow is shown and it seems radiowidget items are added but the labels are not shown. This is the code I'm using:
from typing import Optional
from PyQt5.QtWidgets import QLabel, QWidget
class RadioWidget(QWidget):
def __init__(self, parent: Optional[QWidget], radioTitle: str) -> None:
super().__init__(parent=parent)
self.radioTitleLbl = QLabel(radioTitle)
import sys
from typing import Optional
from PyQt5.QtWidgets import (
QApplication,
QListWidget,
QListWidgetItem,
QMainWindow,
QWidget,
)
from vagh.radiowidget import RadioWidget
class Vagh(QMainWindow):
def __init__(self, parent: Optional[QWidget] = None) -> None:
super().__init__(parent=parent)
self.radiosList = QListWidget(self)
self.radios = ("radio1", "radio2", "radio3")
self.loadRadios()
self.setCentralWidget(self.radiosList)
self.show()
def loadRadios(self):
for radio in self.radios:
radioItem = QListWidgetItem(self.radiosList)
radioWidget = RadioWidget(self.radiosList, radio)
radioItem.setSizeHint(radioWidget.sizeHint())
self.radiosList.addItem(radioItem)
self.radiosList.setItemWidget(radioItem, radioWidget)
if __name__ == "__main__":
app = QApplication(sys.argv)
v = Vagh()
app.exec_()
A widget that is created without any parent is considered a top level window (until it's reparented, for example when added to a layout).
Theoretically, the QLabel could be correctly added as a child by adding the RadioWidget instance as a parent in the constructor:
self.radioTitleLbl = QLabel(radioTitle, self)
But this won't work well: the label will be created on the top left of the radio widget, but that widget will know nothing about the label.
The sizeHint of a widget is returned based on its contents only when a layout manager is set for the widget (or when reimplemented in some way).
Since no layout manager is set for that widget, the result is that RadioWidget considers itself empty, returning an invalid hint (QSize(-1, -1)), and you will not see the label (nor the item) because the item is resized to a null size.
As usual, layout managers should always be used.
Not only: adding the label to a layout ensures that it correctly resizes itself whenever its text changes.
With the following modification you don't even need to set the sizeHint on the QListWidgetItem, as it will automatically use the hint returned by the widget:
class RadioWidget(QWidget):
def __init__(self, parent: Optional[QWidget], radioTitle: str) -> None:
super().__init__(parent=parent)
layout = QVBoxLayout(self)
self.radioTitleLbl = QLabel(radioTitle)
layout.addWidget(self.radioTitleLbl)
Related
I'm trying to learn pyqt5 in python by creating a small application. For one of the windows, I need to add a vertical scroll bar to the window. Now, this window has a table made using QLabel and QLineEdit. Check the picture to get exactly how it looks like.
As you can see there are a lot of chemicals, which goes below the window screen. I have tried numerous approaches but somehow couldn't get the result. If I am able to get the scroll, all the elements get aligned one under another (QVBoxLayout) which is not the way I want the elements to be aligned.
Here's the code I'm using
class ChemicalWindow(QWidget):
def __init__(self,chemicals,data):
super().__init__()
self.layout = QVBoxLayout()
self.setWindowTitle("Chemicals")
self.setMinimumSize(QSize(600,600))
self.setStyleSheet("background-color:#eaf4f4;")
self.chemicals = chemicals
self.data = data
self.createBody()
self.createButtons()
def createBody(self):
headerLabel = QLabel('Chemicals',scroll_widget)
headerLabel.move(265,10)
headerLabel.resize(70,40)
headerLabel.setStyleSheet("color:#000;")
tcLabel = QLabel('Tc',scroll_widget)
tcLabel.move(200,50)
tcLabel.resize(60,30)
tcLabel.setStyleSheet("color:#000;")
pcLabel = QLabel('Pc',scroll_widget)
pcLabel.move(280,50)
pcLabel.resize(60,30)
pcLabel.setStyleSheet("color:#000;")
cpLabel = QLabel('Cp',scroll_widget)
cpLabel.move(360,50)
cpLabel.resize(60,30)
cpLabel.setStyleSheet("color:#000;")
self.chemical_names = self.chemicals.keys()
y_position = 90
# List for keeping chemical inputs variables in form of dict of list -> {A:[chemical_a_tc,chemical_a_pc,chemical_a_cp],
# B:[chemical_b_tc,chemical_b_pc,...],...}
self.chemical_inputs = dict()
# Creating labels for the chemical names
for name in self.chemical_names:
chemicalLabel = QLabel(name,scroll_widget)
chemicalLabel.move(70,y_position)
chemicalLabel.resize(75,30)
chemicalLabel.setStyleSheet("color:#000;")
chemicalLabel.setToolTip(name)
y_position += 40
current_chemical_inputs = dict()
for chemical_input in self.chemicals[name]:
current_chemical_inputs[chemical_input] = QLineEdit(scroll_widget)
self.chemical_inputs[name] = current_chemical_inputs
position_y = 90
for individual_chemical in self.chemical_inputs:
position_x = 160
for chemical_input in self.chemical_inputs[individual_chemical]:
self.chemical_inputs[individual_chemical][chemical_input].setText(str(self.data['chemicals'][individual_chemical][chemical_input]))
self.chemical_inputs[individual_chemical][chemical_input].move(position_x,position_y)
self.chemical_inputs[individual_chemical][chemical_input].resize(80,30)
self.chemical_inputs[individual_chemical][chemical_input].setStyleSheet("color:#000;background-color:#a9d6e5;padding:2px;")
position_x += 90
position_y += 40
def createButtons(self):
close_button = QPushButton('Close',self)
close_button.move(510,550)
close_button.resize(70,30)
close_button.setStyleSheet("background-color:#00509d;color:#fff;")
close_button.clicked.connect(self.closeButton)
def closeButton(self):
self.close()
What am I doing wrong?
Firstly, instead of using .move() to manually place your widgets, you should be using a QLayout (ex. QHBoxLayout or QVBoxLayout). This will automatically space your labels, and you can modify it by adjusting stretch and adding spacers (QSpacerItem). For more complex layouts, you can either nest multiple box layouts, or use a QGridLayout.
Now to address the scrolling:
First, you want to create your scroll area. Make this widget the central widget. Remember to set setWidgetResizable to True.
scroller = QScrollArea()
scroller.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroller.resize(self.width(),self.height())
scroller.setWidgetResizable(True)
self.setCentralWidget(scroller)
Next, create your container and add it to the scroll area. All your layout elements (labels, buttons, etc.) should be placed in this container.
self.container = QWidget()
scroller.setWidget(self.container)
Here's the full sample program I created:
import sys
from PyQt5.QtWidgets import QMainWindow, QWidget, QScrollArea, QVBoxLayout, QLabel, QApplication
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(1100, 800)
scroller = QScrollArea()
scroller.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.container = QWidget()
scroller.setWidget(self.container)
scroller.resize(self.width(),self.height())
scroller.setWidgetResizable(True)
self.setCentralWidget(scroller)
self.holderColumn=QVBoxLayout()
txtList=["apple","banana","orange","triangle","circle","square","moon","star","sun","delta"]
objs=list()
for i in txtList:
tempLabel=QLabel()
tempLabel.setText(i)
tempLabel.setFixedSize(300,300)
objs.append(tempLabel)
self.holderColumn.addWidget(tempLabel)
self.container.setLayout(self.holderColumn)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
i have two views (first and second) vertical aligment
import sys
from PyQt5.QtWidgets import QMainWindow,QSplitter,QGroupBox, QApplication, QPushButton, QWidget, QAction, QTabWidget,QVBoxLayout
from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5.QtCore import Qt
from .First import First
from .Second import Second
class LateralMenu(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.plotview = QGroupBox("Group of views")
self.text = "Menu Lateral"
self.layout = QSplitter(Qt.Vertical)
self.layout_plotview = QVBoxLayout()
self.First = First()
self.Second = Second()
self.layout.addWidget(self.First)
self.layout.addWidget(self.Second)
self.layout_plotview.addWidget(self.layout)
self.plotview.setLayout(self.layout_plotview)
self.setLayout(self.plotview)
i want to put those views in a QGroupBox, but i am getting this error: self.setLayout(self.plotview)
TypeError: setLayout(self, QLayout): argument 1 has unexpected type 'QGroupBox'
what is the problem?
The problem is exactly what the error reports: you're trying to set a layout (which has to be a subclass of QLayout, such as QGridLayout, etc), but the provided argument is a QGroupBox, which is a subclass of QWidget.
If you want to add the QGroupBox to the current widget, you should set a layout for that widget and then add the groupbox to that layout:
def initUI(self):
self.main_layout = QVBoxLayout()
self.setLayout(self.main_layout)
# ...
self.main_layout.addWidget(self.plotview)
If the groupbox is going to be the only "main" widget, there's no need for that, though: you can just subclass from QGroupBox (instead of QWidget):
class LateralMenu(QGroupBox):
# ...
def initUi(self):
self.setTitle("Group of views")
self.text = "Menu Lateral"
self.layout_plotview = QVBoxLayout()
self.setLayout(self.layout_plotview)
self.splitter = QSplitter(Qt.Vertical)
self.First = First()
self.Second = Second()
self.splitter.addWidget(self.First)
self.splitter.addWidget(self.Second)
self.layout_plotview.addWidget(self.splitter)
Note that I've changed some names (most importantly, self.layout has become self.splitter), and that's for clarity: while QSplitter behaves similarly to a layout, it is not a layout, but a QWidget; naming it "layout" might create a lot of confusion.
Also, you should not overwrite existing class properties (such as, indeed, self.layout).
Finally, avoid using capitalized names for variables and attributes: both First and Second instances should be lower case (read more about this in the Style Guide for Python Code).
As the title says I'm trying to make a scrollArea that uses QScroller with grabgesture so I can scroll by dragging on the widget. I found some good examples and got it working. Now I want to remove the overshoot that happens when you drag further than there is items in the widget.
But when I try to tweak the Qscroller, I can't seem to figure out how to apply the QScrollerProperties to the QScroller. Which is how I assume you remove the overshoot.
Here is an example of the code:
import sys
from PyQt5.QtWidgets import (
QApplication,
QFormLayout,
QGridLayout,
QLabel,
QScrollArea,
QScroller,
QScrollerProperties,
QWidget,
)
class MainWindow(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
scroll_area = QScrollArea()
layout = QGridLayout(self)
layout.addWidget(scroll_area)
scroll_widget = QWidget()
scroll_layout = QFormLayout(scroll_widget)
for i in range(200):
scroll_layout.addRow(QLabel('Label #{}'.format(i)))
scroll_area.setWidget(scroll_widget)
scroll = QScroller.scroller(scroll_area.viewport())
scroll.grabGesture(scroll_area.viewport(), QScroller.LeftMouseButtonGesture)
scroll.scrollerPropertiesChanged.connect(self.PropsChanged) #Just to see if I could registre a change
props = scroll.scrollerProperties()
props.setScrollMetric(QScrollerProperties.VerticalOvershootPolicy,QScrollerProperties.OvershootAlwaysOff)
props.setScrollMetric(QScrollerProperties.DragStartDistance, 0.01)
#Apply Qscroller properties here somehow?
print(scroll.scrollerProperties().scrollMetric(QScrollerProperties.DragStartDistance))
scroll.scrollerProperties = props #Maybe? Doesn't seem to change the overshoot?
def PropsChanged(self):
print("Something is being changed??")
if __name__ == '__main__':
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
I'm not sure how to proceed from here.
Any help would be appriciated :)
Just call scroll.setScrollerProperties(props) once you've set the new properties.
When you call scrollerProperties() you get "copy" of the current properties: it is not a pointer to the actual properties, so nothing changes unless you apply them back to the scroller.
It's almost like calling self.font():
font = self.font()
font.setPointSize(20)
# at this point, the widget font is still the same...
# unless you do this:
self.setFont(font)
The same applies to almost any property, like text()/setText() for labels, palette()/setPalette(), etc.
To prevent the vertical overshoot, you have to use setScrollMetric with VerticalOvershootPolicy, and set the value to OvershootAlwaysOff:
props.setScrollMetric(QScrollerProperties.VerticalOvershootPolicy,
QScrollerProperties.OvershootAlwaysOff)
scroll.setScrollerProperties(props)
I am trying to add QAction object to QLabel object using QLabel.addAction() method, but it doesn't seem to work. Is it not supposed to work or am I doing something wrong?
I am trying to make an accordion using QStackedWidget.
For this I need a section title which will either hide or show the title's section when user presses on that title. I could use mouseReleasedEvent, but I would prefer proper QAction toggle() implementation. Maybe I could use something else than QLabel for this matter?
The addAction functionality of QWidget is used for providing context menus and does not directly relate to an action that is triggered when the mouse is clicked on the label.
You therefore must use some kind of mousexxxevent.
If you prefer signals instead, this is also quite easy:
from PySide.QtGui import *
from PySide.QtCore import *
class ClickableLabel(QLabel):
"""
A Label that emits a signal when clicked.
"""
clicked = Signal()
def __init__(self, *args):
super().__init__(*args)
def mousePressEvent(self, event):
self.clicked.emit()
# example
app = QApplication([])
window = QWidget()
layout = QVBoxLayout(window)
labelA = ClickableLabel('Click on me for more.')
layout.addWidget(labelA)
labelB = QLabel('Here I am.')
layout.addWidget(labelB)
labelB.hide()
labelA.clicked.connect(labelB.show)
window.show()
app.exec_()
Or if you want an action instead, make it like this:
from PySide.QtGui import *
from PySide.QtCore import *
class ClickableLabel(QLabel):
"""
A Label that emits a signal when clicked.
"""
def __init__(self, *args):
super().__init__(*args)
def mousePressEvent(self, event):
self.action.triggered.emit()
# example
app = QApplication([])
window = QWidget()
layout = QVBoxLayout(window)
labelA = ClickableLabel('Click on me for more.')
layout.addWidget(labelA)
labelB = QLabel('Here I am.')
layout.addWidget(labelB)
labelB.hide()
action = QAction('Action', labelA)
labelA.action = action
action.triggered.connect(labelB.show)
window.show()
app.exec_()
The example are in Python 3.X notation and for PySide but translation to Python 2.X or PyQt is probably quite simple.
This fairly minimal code creates a systray item with three right click options. One is an instance of QDialog, another QMainWindow, and also Quit. It's for a systray-driven app that will have some qdialogs and also a qmainwindow containing a table widget (or, is it possible to create a table into a qdialog?). I've been stuck on this for a week or so, read a lot of related materials and do not understand how to resolve it.
From the systray icon menu, clicking on QDialog, the dialog opens, waits for user action, and clicking the Ok or Cancel buttons will print which one was clicked. It would seem this works because QDialog has its own exec_(). Great so far.
However, clicking on the QMainWindow menu option, the main window dialog appears briefly and disappears with no chance for user input, of course. Maybe instead, the qmainwindow dialog would need to rely on the QApplication's exec_() where the object would instead be initialized just before app.exec_() perhaps? If that would work, I'm not clear on how def qmainwindow() would retrieve the information back from the user.
Hopefully a simple matter for someone who knows, a few changes, bingo.
Current environment: Windows 7 or XP, Python 2.7, Pyside
If you run this, there will be a blank place-holder in the systray that is clickable (right click), or you can also give it an actual image in place of 'sample.png'.
#!python
from PySide import QtGui, QtCore
from PySide.QtGui import QApplication, QDialog, QMainWindow
def qdialog():
qdialog_class_obj = TestClassQDialog()
qdialog_class_obj.show()
qdialog_class_obj.exec_() # wait for user
print "qdialog_user_action: ", qdialog_class_obj.qdialog_user_action
def qmainwindow():
qmainwindow_class_obj = TestClassQMainWindow()
qmainwindow_class_obj.show()
#qmainwindow_class_obj.exec_() # 'TestClassQMainWindow' object has no attribute 'exec_'
class TestClassQDialog(QDialog):
def __init__(self, parent=None):
super(TestClassQDialog, self).__init__(parent)
self.ok_cancel = QtGui.QDialogButtonBox(self)
self.ok_cancel.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
QtCore.QObject.connect(self.ok_cancel, QtCore.SIGNAL("accepted()"), self.button_ok)
QtCore.QObject.connect(self.ok_cancel, QtCore.SIGNAL("rejected()"), self.button_cancel)
def button_ok(self):
self.qdialog_user_action = 'ok'
self.hide()
def button_cancel(self):
self.qdialog_user_action = 'cancel'
self.hide()
class TestClassQMainWindow(QMainWindow):
def __init__(self, parent=None):
super(TestClassQMainWindow, self).__init__(parent)
self.ok_cancel = QtGui.QDialogButtonBox(self)
self.ok_cancel.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
QtCore.QObject.connect(self.ok_cancel, QtCore.SIGNAL("accepted()"), self.button_ok)
QtCore.QObject.connect(self.ok_cancel, QtCore.SIGNAL("rejected()"), self.button_cancel)
def button_ok(self):
self.hide()
def button_cancel(self):
self.hide()
class SysTrayIcon(QMainWindow):
def __init__(self, parent=None):
super(SysTrayIcon, self).__init__(parent)
self.qdialog_action = QtGui.QAction("QDialog", self, triggered=qdialog)
self.qmainwindow_action = QtGui.QAction("QMainWindow", self, triggered=qmainwindow)
self.quit_action = QtGui.QAction("Quit", self, triggered=QtGui.qApp.quit)
self.createSystrayIcon()
self.systrayIcon.show()
def createSystrayIcon(self):
self.systrayIconMenu = QtGui.QMenu(self)
self.systrayIconMenu.addAction(self.qdialog_action)
self.systrayIconMenu.addAction(self.qmainwindow_action)
self.systrayIconMenu.addSeparator()
self.systrayIconMenu.addAction(self.quit_action)
self.systrayIcon = QtGui.QSystemTrayIcon(self)
self.systrayIcon.setContextMenu(self.systrayIconMenu)
self.systrayIcon.setIcon(QtGui.QIcon('sample.png')) # point to a valid image if you want.
self.systrayIcon.setVisible(True)
if __name__ == '__main__':
app = QtGui.QApplication([])
systrayicon = SysTrayIcon()
app.exec_()
I've got it working. What I did was move your external method qmainwindow inside of your
SysTrayIcon and created the class parameter self.qmainwindow_class_obj = TestClassQMainWindow(). I've attached the working code below. Also, you're using the old style signal slot method, I take it you're coming from old school PyQt. The new method if very nice, clean and pythonic. I've also put the new style methods in the below code. Another thing I would do is move your qdialog method inside the SysTrayIcon class. I don't really understand why you have it outside the class but maybe I'm missing something. Hope this helps.
#!python
from PySide import QtGui, QtCore
from PySide.QtGui import QApplication, QDialog, QMainWindow
def qdialog():
qdialog_class_obj = TestClassQDialog()
qdialog_class_obj.show()
qdialog_class_obj.exec_() # wait for user
print "qdialog_user_action: ", qdialog_class_obj.qdialog_user_action
class TestClassQDialog(QDialog):
def __init__(self, parent=None):
super(TestClassQDialog, self).__init__(parent)
self.ok_cancel = QtGui.QDialogButtonBox(self)
self.ok_cancel.setStandardButtons(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
self.ok_cancel.accepted.connect(self.button_ok)
self.ok_cancel.rejected.connect(self.button_cancel)
def button_ok(self):
self.qdialog_user_action = 'ok'
self.hide()
def button_cancel(self):
self.qdialog_user_action = 'cancel'
self.hide()
class TestClassQMainWindow(QMainWindow):
def __init__(self, parent=None):
super(TestClassQMainWindow, self).__init__(parent)
self.ok_cancel = QtGui.QDialogButtonBox(self)
self.ok_cancel.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.ok_cancel.accepted.connect(self.button_ok)
self.ok_cancel.rejected.connect(self.button_cancel)
def button_ok(self):
self.hide()
def button_cancel(self):
self.hide()
class SysTrayIcon(QMainWindow):
def __init__(self, parent=None):
super(SysTrayIcon, self).__init__(parent)
self.qmainwindow_class_obj = TestClassQMainWindow()
self.qdialog_action = QtGui.QAction("QDialog", self, triggered=qdialog)
self.qmainwindow_action = QtGui.QAction("QMainWindow", self, triggered=self.qmainwindow)
self.quit_action = QtGui.QAction("Quit", self, triggered=QtGui.qApp.quit)
self.createSystrayIcon()
self.systrayIcon.show()
def createSystrayIcon(self):
self.systrayIconMenu = QtGui.QMenu(self)
self.systrayIconMenu.addAction(self.qdialog_action)
self.systrayIconMenu.addAction(self.qmainwindow_action)
self.systrayIconMenu.addSeparator()
self.systrayIconMenu.addAction(self.quit_action)
self.systrayIcon = QtGui.QSystemTrayIcon(self)
self.systrayIcon.setContextMenu(self.systrayIconMenu)
self.systrayIcon.setIcon(QtGui.QIcon('linux.jpeg')) # point to a valid image if you want.
self.systrayIcon.setVisible(True)
def qmainwindow(self):
self.qmainwindow_class_obj.show()
if __name__ == '__main__':
app = QtGui.QApplication([])
systrayicon = SysTrayIcon()
app.exec_()