QScroller on QListWidget in QTabWidget is not work - python

In nutshell, I have QTabWidget which contain tab1 and tab2. Each of tab1 and tab2 contains a QListWidget. I would like to use QScroller on each of the QListWidget.
Here is the code (The simplest version).:
import sys
from PyQt5.QtWidgets import (QWidget, QHBoxLayout, QAbstractItemView, QScrollerProperties, QScroller, QVBoxLayout, QListWidget,
QTabWidget, QApplication, QLabel, QListWidgetItem)
from PyQt5.QtCore import Qt
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.resize(700, 300)
mainLayout = QVBoxLayout()
self.tabWidget = QTabWidget()
self.tabWidget.setStyleSheet("QTabBar::tab { height: 50px; width: 250px; }")
mainLayout.addWidget(self.tabWidget)
myBoxLayout = QHBoxLayout()
self.tabWidget.setLayout(myBoxLayout)
self.tab1 = WidgetTab1()
self.tab2 = WidgetTab2()
self.tabWidget.addTab(self.tab1, 'Tab1')
self.tabWidget.addTab(self.tab2, 'Tab2')
self.setLayout(mainLayout)
class WidgetTab1(QWidget):
def __init__(self):
super().__init__()
self.hbox = QHBoxLayout()
# Create the list
self.mylist = QListWidget()
self.mylist.setStyleSheet("QListWidget::item { border-bottom: 1px solid gray; }")
self.mylist.setFocusPolicy(Qt.NoFocus)
self.mylist.setSelectionMode(QAbstractItemView.NoSelection)
self.mylist.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.mylist.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
for i in range(20):
item = QListWidgetItem(self.mylist)
self.mylist.addItem(item)
self.mylist.setItemWidget(item, QLabel(str(i)))
self.sp = QScrollerProperties()
self.sp.setScrollMetric(QScrollerProperties.DragVelocitySmoothingFactor, 0.6)
self.sp.setScrollMetric(QScrollerProperties.MinimumVelocity, 0.0)
self.sp.setScrollMetric(QScrollerProperties.MaximumVelocity, 0.2)
self.sp.setScrollMetric(QScrollerProperties.AcceleratingFlickMaximumTime, 0.1)
self.sp.setScrollMetric(QScrollerProperties.AcceleratingFlickSpeedupFactor, 1.2)
self.sp.setScrollMetric(QScrollerProperties.SnapPositionRatio, 0.2)
self.sp.setScrollMetric(QScrollerProperties.MaximumClickThroughVelocity, 1)
self.sp.setScrollMetric(QScrollerProperties.DragStartDistance, 0.001)
self.sp.setScrollMetric(QScrollerProperties.MousePressEventDelay, 0.5)
self.scroller = QScroller.scroller(self.mylist.viewport())
self.scroller.setScrollerProperties(self.sp)
self.scroller.grabGesture(self.mylist.viewport(), QScroller.LeftMouseButtonGesture)
self.mylist.show()
self.hbox.addWidget(self.mylist)
self.setLayout(self.hbox)
class WidgetTab2(QWidget):
def __init__(self):
super().__init__()
self.hbox = QHBoxLayout()
# Create the list
self.mylist = QListWidget()
self.mylist.setStyleSheet("QListWidget::item { border-bottom: 1px solid gray; }")
self.mylist.setFocusPolicy(Qt.NoFocus)
self.mylist.setSelectionMode(QAbstractItemView.NoSelection)
self.mylist.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.mylist.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
for i in range(19, 0, -1):
item = QListWidgetItem(self.mylist)
self.mylist.addItem(item)
self.mylist.setItemWidget(item, QLabel(str(i)))
self.sp = QScrollerProperties()
self.sp.setScrollMetric(QScrollerProperties.DragVelocitySmoothingFactor, 0.6)
self.sp.setScrollMetric(QScrollerProperties.MinimumVelocity, 0.0)
self.sp.setScrollMetric(QScrollerProperties.MaximumVelocity, 0.2)
self.sp.setScrollMetric(QScrollerProperties.AcceleratingFlickMaximumTime, 0.1)
self.sp.setScrollMetric(QScrollerProperties.AcceleratingFlickSpeedupFactor, 1.2)
self.sp.setScrollMetric(QScrollerProperties.SnapPositionRatio, 0.2)
self.sp.setScrollMetric(QScrollerProperties.MaximumClickThroughVelocity, 1)
self.sp.setScrollMetric(QScrollerProperties.DragStartDistance, 0.001)
self.sp.setScrollMetric(QScrollerProperties.MousePressEventDelay, 0.5)
self.scroller = QScroller.scroller(self.mylist.viewport())
self.scroller.setScrollerProperties(self.sp)
self.scroller.grabGesture(self.mylist.viewport(), QScroller.LeftMouseButtonGesture)
self.mylist.show()
self.hbox.addWidget(self.mylist)
self.setLayout(self.hbox)
if __name__ == '__main__':
qApplication = QApplication(sys.argv)
window = MainWindow()
window.show()
qApplication.exec_()
The problem is, the QScroller only work in the first tab. For example, If I choose tab1 at first and scroll on it. Then I switch to tab2, the scroll on tab2 will not work. If I choose tab2 first and scroll on it. Then I switch to tab1, the scroll on tab1 will not work.
I also have tried to ungrabGesture every I switch the tab, but it does not work.
Am I implement the QScroller in the wrong way?

Thanks #G.M. for your comment.
I solve the problem by upgrading the PyQt version from 5.11.3 to 5.13.0

Related

PYQT5: How to remove margins from QGroupBox

I made three group boxes in a vertical layout. What I want is that they are right next to each other, no space in between. I set window layout margins (.setContentsMargins) and it removes all margins except the one in between them. I tried setting gbox style sheet, padding and margins to 0 but nothing happens. Any ideas?
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QGroupBox
class mainWindow(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(200, 200, 800, 600)
self.setFixedSize(800, 600)
self.winLayout = QVBoxLayout()
# ==================================================================
self.gboxFirst, self.gboxFirstLayout = self.create_v_layout()
self.gboxSecond, self.gboxSecondLayout = self.create_v_layout()
self.gboxThird, self.gboxThirdLayout = self.create_v_layout()
self.winLayout.addWidget(self.gboxFirst)
self.winLayout.addWidget(self.gboxSecond)
self.winLayout.addWidget(self.gboxThird)
self.winLayout.setContentsMargins(0,0,0,0)
# ==================================================================
# Coloring gbox-es
self.gboxFirst.setStyleSheet(""" QGroupBox {
background-color: red;
}""")
self.gboxSecond.setStyleSheet(""" QGroupBox {
background-color: green;
}""")
self.gboxThird.setStyleSheet(""" QGroupBox {
background-color: blue;
}""")
self.setLayout(self.winLayout)
self.show()
def create_v_layout(self):
gbox = QGroupBox()
gboxLayout = QVBoxLayout()
gbox.setLayout(gboxLayout)
return gbox, gboxLayout
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = mainWindow()
app.exec_()

How do I modify spacing in nested PyQt layouts?

Currently, I have a nested QVBoxLayout in the first column of a QHBoxLayout, but no matter my changes to .setContentMargins or .setSpacing nothing changes in that first column.
import sys
import io
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import QWebEngineView
class MyApp(QWidget):
def __init__(self):
super().__init__()
# Main widget/window
self.setWindowTitle('Test')
self.window_width, self.window_height = 1600, 900
self.setMinimumSize(self.window_width, self.window_height)
layout = QHBoxLayout()
self.setLayout(layout)
leftside = QWidget()
leftlayout = QVBoxLayout()
# Creating textbox and adding to leftside GUI
lineEdit = QLineEdit()
leftlayout.addWidget(lineEdit)
leftlayout.addWidget(QPushButton('Placeholder'))
leftside.setLayout(leftlayout)
# Adding both widgets to main layout
testWidget = QWidget()
testWidget.setStyleSheet("background-color: blue")
layout.addWidget(leftside, 2)
layout.addWidget(testWidget, 8)
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setStyleSheet('''
QWidget {
font-size: 20px;
}
''')
myApp = MyApp()
myApp.show()
try:
sys.exit(app.exec_())
except SystemExit:
print('Closing Window...')
Leaves me with this result:
What I want:
Use addStretch() method:
class MyApp(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Test")
self.window_width, self.window_height = 1600, 900
self.setMinimumSize(self.window_width, self.window_height)
leftside = QWidget()
leftlayout = QVBoxLayout(leftside)
lineEdit = QLineEdit()
leftlayout.addWidget(lineEdit)
leftlayout.addWidget(QPushButton("Placeholder"))
leftlayout.addStretch()
testWidget = QWidget()
testWidget.setStyleSheet("background-color: blue")
layout = QHBoxLayout(self)
layout.addWidget(leftside)
layout.addWidget(testWidget, stretch=1)

How to remove black space when deleting widgets in PyQt5

I have a code to generate a GUI with PyQt5 that enables a user to create multiple buttons (QPushButton) based on an entry (QLineEdit), and to delete these buttons when pressing an "X" button (deleteLater()).
My problem is that when deleting some of these buttons by pressing the associated X-button, this leaves a small empty space where the buttons were initially, and I therefore wonder how to remove these spaces?
Image of the empty spaces
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLineEdit, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QGroupBox, QScrollArea, QLabel
from PyQt5.QtCore import Qt
import sys
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setWindowTitle("My Program")
self.setGeometry(100, 100, 1500, 1500)
self.initUI()
def initUI(self):
widgets = MainWidgets()
self.setCentralWidget(widgets)
class MainWidgets(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.grid = QGridLayout()
self.grid.setColumnStretch(0, 1)
self.grid.setColumnStretch(1, 1)
self.grid.setColumnStretch(2, 1)
self.grid.setColumnStretch(3, 1)
self.grid.setColumnStretch(4, 1)
self.groupBox = QGroupBox("Labels")
self.groupBox.setStyleSheet('''
QGroupBox::title {
subcontrol-position: top center;
}
''')
right_column_layout = QVBoxLayout(self.groupBox)
scrollArea = QScrollArea()
scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scrollArea.setWidgetResizable(True)
right_column_layout.addWidget(scrollArea)
scrollArea.setWidget(RightColWidgets())
self.grid.addWidget(self.groupBox, 0, 5, 1, 5)
self.setLayout(self.grid)
class RightColWidgets(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.layout = QVBoxLayout(self)
self.labelEntry = QLineEdit(self)
self.addLabelButton = QPushButton(self)
self.addLabelButton.setText("Add Label")
self.addLabelButton.clicked.connect(self.addNewLabel)
self.emptyspace = QLabel(self)
self.layout.addWidget(self.labelEntry, stretch=0)
self.layout.addWidget(self.addLabelButton, stretch=0)
self.layout.addWidget(self.emptyspace, stretch=1)
def addNewLabel(self):
labelname = self.labelEntry.text()
newLabelItems = Labels(self, labelname)
self.layout.insertWidget(2, newLabelItems)
class Labels(QWidget):
def __init__(self, parent, labelname, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.mylabelname = labelname
self.initUI()
def initUI(self):
self.labelButton = QPushButton(self)
self.labelButton.setText(str(self.mylabelname))
self.labelButton.setStyleSheet("""
QPushButton {border: 1px solid back; background: rgba(103, 186, 181, 0.5); padding-top: 10px; padding-bottom: 10px}
""")
self.labelButton.clicked.connect(self.printbutton)
self.buttonErase = QPushButton(self)
self.buttonErase.setText("X")
self.buttonErase.setStyleSheet("""
QPushButton {border: 1px solid back; padding-right: 5 px; padding-left: 5 px; padding-top: 10px; padding-bottom: 10px}
""")
self.buttonErase.clicked.connect(self.erasebutton)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.labelButton, stretch=1)
layout.addWidget(self.buttonErase, stretch=0)
def printbutton(self):
print('clicked:', self.labelButton.text())
def erasebutton(self):
self.labelButton.deleteLater()
self.buttonErase.deleteLater()
if __name__ == '__main__':
app = QApplication(sys.argv)
# app.setStyle('Fusion')
window = MyWindow()
window.showMaximized()
sys.exit(app.exec_())
Deleting the children does not delete the container, so what you see is the empty Labels widget with the spacing of its layout contentsMargins().
A simple solution could be to directly connect the button with its own deleteLeter(), which automatically deletes its children:
self.buttonErase.clicked.connect(self.deleteLater)
A better solution would be to connect the signal to the parent and let it do everything necessary in a cleaner way, as you might need to keep track of the existing widgets (for instance, to remove them from the list of currently existing labels):
class RightColWidgets(QWidget):
# ...
def addNewLabel(self):
labelname = self.labelEntry.text()
newLabelItems = Labels(self, labelname)
self.layout.insertWidget(2, newLabelItems)
newLabelItems.buttonErase.clicked.connect(
lambda: self.deleteLabel(newLabelItems))
def deleteLabel(self, widget):
self.layout.removeWidget(widget)
widget.deleteLater()
Obviously, in this case you don't need to connect the clicked signal in the initUi of the Label class anymore.
Note that layout() is an existing (and dynamic) property of any QWidget, so you should not overwrite it.

remove margin between QLabel and QLineEdit

I'm trying to create Login Window, which has QLineEdit below QLabel, but the current QLabel taking too many space in Window, i don't know why, here is the picture :
QLabel { background-color: red; }
My Code :
self.text = QLabel("LOGIN")
# self.text.setStyleSheet("QLabel { background-color: red; color : white;margin-top: 50px;margin-bottom: 0px; }")
self.text.setAlignment(Qt.AlignCenter)
# self.text.setContentsMargins(0, 0, 0, 0)
# self.text.setGeometry(QRect(10,10,30,80))
font = QFont("Sans-Serif", 30)
self.text.setFont(font)
form = QLineEdit("Write my name here..")
form.setAlignment(Qt.AlignCenter)
# form.setAlignment(Qt.AlignHCenter)
self.layout = QVBoxLayout()
# self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.layout.addWidget(self.text)
self.layout.addWidget(form)
self.layout.addWidget(self.button)
self.setLayout(self.layout)
I do not see the relationship between the code provided by the OP and the LOGIN window from which you want to copy the structure, so create the following code from scratch.
from PySide2 import QtCore, QtGui, QtWidgets
class LoginPage(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.image_label = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)
self.login_label = QtWidgets.QLabel(
text=self.tr("LOG IN"), alignment=QtCore.Qt.AlignCenter
)
self.email_username_lineedit = QtWidgets.QLineEdit(
placeholderText=self.tr("Email or username")
)
self.password_lineedit = QtWidgets.QLineEdit(
placeholderText=self.tr("Password"), echoMode=QtWidgets.QLineEdit.Password
)
self.enter_button = QtWidgets.QPushButton(self.tr("Enter"))
self.forgot_password_label = QtWidgets.QLabel(
self.tr("Forgot password?"), alignment=QtCore.Qt.AlignCenter
)
self.image_label.setPixmap(QtGui.QPixmap("so-icon.png"))
# font = self.
font = self.login_label.font()
font.setPointSize(30)
self.login_label.setFont(font)
self.login_label.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
self.forgot_password_label.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
)
lay = QtWidgets.QVBoxLayout(self)
lay.setSpacing(0)
lay.addWidget(self.image_label)
lay.addWidget(self.login_label)
lay.addWidget(self.email_username_lineedit)
lay.addWidget(self.password_lineedit)
lay.addWidget(self.enter_button)
lay.addWidget(self.forgot_password_label)
self.resize(320, 480)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = LoginPage()
w.show()
sys.exit(app.exec_())

How to make border of QLabel look like border of other widgets like QTreeWidget?

I noticed different border styles for QTreeWidget and QLabel - even if I try to adjust the stylesheet. Of course, I could change the stylesheet for both, but ideally I'd like to keep the QTreeWidget's border style. How can I make the border of QLabel look like the border of QTreeWidget?
MCVE snippet:
import sys
from PyQt5.QtWidgets import *
class widget(QWidget):
def __init__(self):
super().__init__()
treewidget = QTreeWidget(self)
label = QLabel(self)
label.setStyleSheet("background-color: white; border: 1px inset grey; min-height: 200px;")
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(treewidget, 1, 0)
grid.addWidget(label, 2, 0)
self.setLayout(grid)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
f = widget()
sys.exit(app.exec_())
Without stylesheet:
Screenshot of resulting window for MCVE snippet:
What I want the window to look like:
You may have found the answer to this already but just in case...
The following should provide you with what you want...
label = QLabel(self)
label.setFrameShape(QFrame.Panel)
label.setFrameShadow(QFrame.Sunken)
label.setLineWidth(3)
Where I've just hardwired the line width. Note that the "border: 1px" specifier must be removed from the style sheet otherwise it will be used in preference to the specified line width. The complete code would be...
import sys
from PyQt5.QtWidgets import *
class widget(QWidget):
def __init__(self):
super().__init__()
treewidget = QTreeWidget(self)
label = QLabel(self)
label.setStyleSheet("background-color: white; inset grey; min-height: 200px;")
label.setFrameShape(QFrame.Panel)
label.setFrameShadow(QFrame.Sunken)
label.setLineWidth(3)
grid = QGridLayout()
grid.setSpacing(10)
grid.addWidget(treewidget, 1, 0)
grid.addWidget(label, 2, 0)
self.setLayout(grid)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
f = widget()
sys.exit(app.exec_())

Categories