python pyside6 qtextedit synchronize error - python

I have a pyside6 app which has 2 TextEdit widgets and I want the 2 TextEdits to scroll in sync.
The following code is used for this function:
self.scrollconnect1 = self.TextEdit_1.verticalScrollBar().valueChanged.connect(
self.TextEdit_2.verticalScrollBar().setValue)
self.scrollconnect2 = self.TextEdit_2.verticalScrollBar().valueChanged.connect(
self.TextEdit_1.verticalScrollBar().setValue)
It works fine when there are few lines in TextEdit, but if there are too many lines (about 50,000 lines) in both TextEdit, it will not precisely synchronize in same line. (Especially when I scroll the widget by both mouse wheel and scroll bar).
How to keep them synchronize precisely? Thank you!
Following code is for test:
from PySide6 import QtCore, QtGui, QtWidgets
class ScrollBar(QtWidgets.QScrollBar):
valueUpdated = QtCore.Signal(int)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTracking(True)
self.actionTriggered.connect(self.handleAction)
def handleAction(self, action):
action = QtWidgets.QAbstractSlider.SliderAction(action)
print(self.objectName(), action, (self.value(), self.sliderPosition()))
if self.value() != self.sliderPosition():
self.valueUpdated.emit(self.sliderPosition())
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.editTop = QtWidgets.QPlainTextEdit()
self.editBtm = QtWidgets.QPlainTextEdit()
self.editTop.setVerticalScrollBar(
ScrollBar(self, objectName='EDIT-TOP'))
self.editBtm.setVerticalScrollBar(
ScrollBar(self, objectName='EDIT-BTM'))
self.editTop.verticalScrollBar().actionTriggered.connect(
lambda a: self.editBtm.verticalScrollBar().triggerAction(
QtWidgets.QAbstractSlider.SliderAction(a)))
self.editTop.verticalScrollBar().valueUpdated.connect(
self.editBtm.verticalScrollBar().setValue)
self.editBtm.verticalScrollBar().valueUpdated.connect(
self.editTop.verticalScrollBar().setValue)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.editTop)
layout.addWidget(self.editBtm)
line = ' text' * 40
# for index in range(50000):
for index in range(500):
self.editTop.insertPlainText("\n"+str(index) + line)
self.editBtm.insertPlainText("\n"+str(index) + line)
if __name__ == '__main__':
app = QtWidgets.QApplication(['Test'])
window = Window()
window.setGeometry(800, 100, 600, 400)
window.show()
app.exec()

Test Script:
from PySide6 import QtCore, QtGui, QtWidgets
class ScrollBar(QtWidgets.QScrollBar):
valueUpdated = QtCore.Signal(int)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTracking(True)
self.actionTriggered.connect(self.handleAction)
def handleAction(self, action):
action = QtWidgets.QAbstractSlider.SliderAction(action)
print(self.objectName(), action, (self.value(), self.sliderPosition()))
if self.value() != self.sliderPosition():
self.valueUpdated.emit(self.sliderPosition())
def sliderChange(self, change):
change = QtWidgets.QAbstractSlider.SliderChange(change)
if (change == QtWidgets.QAbstractSlider.SliderValueChange and
self.signalsBlocked()):
self.blockSignals(False)
print(self.objectName(), change, self.value())
self.valueUpdated.emit(self.value())
super().sliderChange(change)
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
# self.editTop = QtWidgets.QTextEdit()
# self.editBtm = QtWidgets.QTextEdit()
self.editTop = QtWidgets.QPlainTextEdit()
self.editBtm = QtWidgets.QPlainTextEdit()
self.editTop.setVerticalScrollBar(
ScrollBar(self, objectName='EDIT-TOP'))
self.editBtm.setVerticalScrollBar(
ScrollBar(self, objectName='EDIT-BTM'))
self.editTop.verticalScrollBar().valueUpdated.connect(
self.editBtm.verticalScrollBar().setValue)
self.editBtm.verticalScrollBar().valueUpdated.connect(
self.editTop.verticalScrollBar().setValue)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.editTop)
layout.addWidget(self.editBtm)
text = ' text' * 20
# for index in range(50000):
for index in range(500):
line = f'{index}{text}\n'
self.editTop.insertPlainText(line)
self.editBtm.insertPlainText(line)
if __name__ == '__main__':
app = QtWidgets.QApplication(['Test'])
window = Window()
window.setGeometry(800, 100, 600, 400)
window.show()
app.exec()

Related

PyQt5 MainWindow resize() call not working

I have a PyQt5 GUI application mainwindow that sets geometry based on the screen size. When I call the toogleLogWindow() function, the visibility property of hLayoutWidget_error changes, but window resize does not happen. When I restore the mainwindow manually by clicking the restore button on the right top corner, the resize function works. Can anyone help me understand this behavior? actionToggleLogWindow status is not checked by default.
import sys, os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUI()
def setupUI(self):
# Set screen size parameters
for i in range(QApplication.desktop().screenCount()):
self.window_size = QApplication.desktop().availableGeometry(i).size()
self.resize(self.window_size)
self.move(QPoint(0, 0))
self._button = QtWidgets.QPushButton(self)
self._button.setText('Test Me')
self._editText = QtWidgets.QComboBox(self)
self._editText.setEditable(True)
self._editText.addItem("")
self._editText.setGeometry(QtCore.QRect(240, 40, 113, 21))
# Connect signal to slot
self._button.clicked.connect(self.toogleLogWindow)
def toogleLogWindow(self):
if self._editText.currentText() == "0":
h = self.window_size.height()
w = int(self.window_size.width()/2)
self.resize(w,h)
elif self._editText.currentText() == "1":
h = self.window_size.height()
w = int(self.window_size.width())
self.resize(w,h)
else:
pass
def get_main_app(argv=[]):
app = QApplication(argv)
win = MainWindow()
win.show()
return app, win
def main():
app, _win = get_main_app(sys.argv)
return app.exec_()
if __name__ == '__main__':
sys.exit(main())
It should be noted that:
It seems that if setting the maximum size of a window before being shown and then displaying it is equivalent to maximizing the window.
When a window is maximized you cannot change its size unless you return it to the previous state, for example if you change the size of the window manually until it is in the normal state then you can just change the size.
So there are several alternatives for this case:
Do not set the full size of the screen:
self.window_size = QApplication.desktop().availableGeometry(i).size() - QSize(10, 10)
Set the size after displaying:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUI()
def setupUI(self):
# Set screen size parameters
for i in range(QApplication.desktop().screenCount()):
self.window_size = QApplication.desktop().availableGeometry(i).size()
self._button = QPushButton(self)
self._button.setText("Test Me")
self._editText = QComboBox(self)
self._editText.setEditable(True)
self._editText.addItem("")
self._editText.setGeometry(QRect(240, 40, 113, 21))
# Connect signal to slot
self._button.clicked.connect(self.toogleLogWindow)
def init_geometry(self):
self.resize(self.window_size)
self.move(QPoint(0, 0))
def toogleLogWindow(self):
if self._editText.currentText() == "0":
h = self.window_size.height()
w = int(self.window_size.width() / 2)
self.resize(w, h)
elif self._editText.currentText() == "1":
h = self.window_size.height()
w = int(self.window_size.width())
self.resize(w, h)
else:
pass
def get_main_app(argv=[]):
app = QApplication(argv)
win = MainWindow()
win.show()
win.init_geometry()
return app, win

Center subwindows in qmdiarea

Is there an attribute to position subwindows in qmdiarea? I’m trying to center subwindow in middle of mainwindow on startup (mdiarea)
I’m working on a mcve but haven’t finished it, wanted to see if anyone has tried doing this, and how they did it
Subwindows are randomly placed on startup when initialized
class App(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent=parent)
self.setupUi(self)
self.screenShape = QDesktopWidget().screenGeometry()
self.width = self.screenShape.width()
self.height = self.screenShape.height()
self.resize(self.width * .6, self.height * .6)
self.new = []
#calls GUI's in other modules
self.lw = Login()
self.vs = VS()
self.ms = MS()
self.hw = HomeWindow()
self.mw = MainWindow()
self.ga = GA()
self.sGUI = Settings()
# shows subwindow
self.CreateLogin()
self.CreateVS()
self.CreateMS()
self.CreateGA()
self.CreateSettings()
def CreateLogin(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.lw)
self.subwindow.setAttribute(Qt.WA_DeleteOnClose, True)
self.mdiArea.addSubWindow(self.subwindow)
self.subwindow.setMaximumSize(520, 300)
self.subwindow.setMinimumSize(520, 300)
self.lw.showNormal()
def CreateVS(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.vs)
self.mdiArea.addSubWindow(self.subwindow)
self.vs.showMinimized()
def CreateMS(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.ms)
self.mdiArea.addSubWindow(self.subwindow)
self.ms.showMinimized()
self.ms.tabWidget.setCurrentIndex(0)
def CreateGA(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.ga)
self.mdiArea.addSubWindow(self.subwindow)
self.ga.showMinimized()
self.subwindow.setMaximumSize(820, 650)
def CreateSettings(self):
self.subwindow = QMdiSubWindow()
self.subwindow.setWidget(self.sGUI)
self.mdiArea.addSubWindow(self.subwindow)
self.sGUI.showMinimized()
def CreateWindow(self):
self.hw.pushButton.clicked.connect(self.vs.showNormal)
self.hw.pushButton_2.clicked.connect(self.Moduleprogram)
self.hw.pushButton_3.clicked.connect(self.ms.showNormal)
self.hw.pushButton_4.clicked.connect(self.ga.showNormal)
self.subwindow = QMdiSubWindow()
self.subwindow.setWindowFlags(Qt.CustomizeWindowHint | Qt.Tool)
self.subwindow.setWidget(self.hw)
self.subwindow.setMaximumSize(258, 264)
self.subwindow.move(self.newwidth*.35, self.newheight*.25)
self.mdiArea.addSubWindow(self.subwindow)
In Qt the geometry is only effective when the window is visible so if you want to center something it must be in the showEvent method. On the other hand to center the QMdiSubWindow you must first get the center of the viewport of the QMdiArea, and according to that modify the geometry of the QMdiSubWindow.
Because the code you provide is complicated to execute, I have created my own code
from PyQt5 import QtCore, QtGui, QtWidgets
import random
def create_widget():
widget = QtWidgets.QLabel(
str(random.randint(0, 100)), alignment=QtCore.Qt.AlignCenter
)
widget.setStyleSheet(
"""background-color: {};""".format(
QtGui.QColor(*random.sample(range(255), 3)).name()
)
)
widget.setMinimumSize(*random.sample(range(100, 300), 2))
return widget
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
add_button = QtWidgets.QPushButton(
"Add subwindow", clicked=self.add_subwindow
)
self._mdiarea = QtWidgets.QMdiArea()
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QVBoxLayout(central_widget)
lay.addWidget(add_button)
lay.addWidget(self._mdiarea)
self._is_first_time = True
for _ in range(4):
self.add_subwindow()
#QtCore.pyqtSlot()
def add_subwindow(self):
widget = create_widget()
subwindow = QtWidgets.QMdiSubWindow(self._mdiarea)
subwindow.setWidget(widget)
subwindow.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
subwindow.show()
self._mdiarea.addSubWindow(subwindow)
# self.center_subwindow(subwindow)
def showEvent(self, event):
if self.isVisible() and self._is_first_time:
for subwindow in self._mdiarea.subWindowList():
self.center_subwindow(subwindow)
self._is_first_time = False
def center_subwindow(self, subwindow):
center = self._mdiarea.viewport().rect().center()
geo = subwindow.geometry()
geo.moveCenter(center)
subwindow.setGeometry(geo)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Update:
If you want the subwindow to be centered then with the following code you have to create a property center to True:
def add_subwindow(self):
widget = create_widget()
subwindow = QtWidgets.QMdiSubWindow(self._mdiarea)
subwindow.setWidget(widget)
subwindow.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
subwindow.show()
subwindow.setProperty("center", True) # <----
self._mdiarea.addSubWindow(subwindow)
def showEvent(self, event):
if self.isVisible() and self._is_first_time:
for subwindow in self._mdiarea.subWindowList():
if subwindow.property("center"): # <---
self.center_subwindow(subwindow)
self._is_first_time = False

how i can make thread for progress bar with pafy

i'm trying to fix problem in my program and this problem is when i start download video the program not responding and i can't see also progress bar move so i tried used threading module but i can't fix problem so how i can fix problem
From this code I can download the video and send the data to another function to retrieve information that I use to connect it to the progress bar
def video(self):
video_url = self.lineEdit_4.text()
video_save = self.lineEdit_3.text()
pafy_video = pafy.new(video_url)
type_video = pafy_video.videostreams
quality = self.comboBox.currentIndex()
start_download = type_video[quality].download(filepath=video_save,callback=self.video_progressbar)
This code is received information from video function to connect with progress bar
def video_progressbar(self,total, recvd, ratio, rate, eta):
self.progressBar_2.setValue(ratio * 100)
I use;python3.5 pyqt5 pafy
One way to move to another thread is to create a QObject that lives in another thread and execute that task in a slot. And that slot must be invoked through QMetaObject::invokeMethod or a signal.
with QThread and QMetaObject::invokeMethod:
import pafy
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
self.le_url = QtWidgets.QLineEdit("https://www.youtube.com/watch?v=bMt47wvK6u0")
path = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
self.le_output = QtWidgets.QLineEdit(path)
self.btn_quality = QtWidgets.QPushButton("Get qualities")
self.combo_quality = QtWidgets.QComboBox()
self.btn_download = QtWidgets.QPushButton("Download")
self.progressbar = QtWidgets.QProgressBar(maximum=100)
self.downloader = DownLoader()
thread = QtCore.QThread(self)
thread.start()
self.downloader.moveToThread(thread)
self.btn_quality.clicked.connect(self.on_clicked_quality)
self.btn_download.clicked.connect(self.download)
self.btn_download.setDisabled(True)
self.downloader.progressChanged.connect(self.progressbar.setValue)
self.downloader.qualitiesChanged.connect(self.update_qualityes)
self.downloader.finished.connect(self.on_finished)
form_lay = QtWidgets.QFormLayout(central_widget)
form_lay.addRow("Url: ", self.le_url)
form_lay.addRow(self.btn_quality)
form_lay.addRow("qualities: ", self.combo_quality)
form_lay.addRow("Output: ", self.le_output)
form_lay.addRow(self.btn_download)
form_lay.addRow(self.progressbar)
#QtCore.pyqtSlot()
def on_finished(self):
self.update_disables(False)
#QtCore.pyqtSlot()
def on_clicked_quality(self):
video_url = self.le_url.text()
QtCore.QMetaObject.invokeMethod(self.downloader, "get_qualities",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, video_url))
#QtCore.pyqtSlot(list)
def update_qualityes(self, types_of_video):
for t in types_of_video:
self.combo_quality.addItem(str(t), t)
self.btn_download.setDisabled(False)
#QtCore.pyqtSlot()
def download(self):
video_save = self.le_output.text()
d = self.combo_quality.currentData()
QtCore.QMetaObject.invokeMethod(self.downloader, "start_download",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(object, d),
QtCore.Q_ARG(str, video_save))
self.update_disables(True)
def update_disables(self, state):
self.combo_quality.setDisabled(state)
self.btn_quality.setDisabled(state)
self.le_output.setDisabled(state)
self.le_url.setDisabled(state)
self.btn_download.setDisabled(not state)
class DownLoader(QtCore.QObject):
progressChanged = QtCore.pyqtSignal(int)
qualitiesChanged = QtCore.pyqtSignal(list)
finished = QtCore.pyqtSignal()
#QtCore.pyqtSlot(str)
def get_qualities(self, video_url):
pafy_video = pafy.new(video_url)
types_of_video = pafy_video.allstreams # videostreams
self.qualitiesChanged.emit(types_of_video)
#QtCore.pyqtSlot(object, str)
def start_download(self, d, filepath):
d.download(filepath=filepath, callback=self.callback)
def callback(self, total, recvd, ratio, rate, eta):
val = int(ratio * 100)
self.progressChanged.emit(val)
if val == 100:
self.finished.emit()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(320, 480)
w.show()
sys.exit(app.exec_())
with threading.Thread:
import pafy
import threading
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
qualitiesChanged = QtCore.pyqtSignal(list)
progressChanged = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
self.le_url = QtWidgets.QLineEdit("https://www.youtube.com/watch?v=bMt47wvK6u0")
path = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.DownloadLocation)
self.le_output = QtWidgets.QLineEdit(path)
self.btn_quality = QtWidgets.QPushButton("Get qualities")
self.combo_quality = QtWidgets.QComboBox()
self.btn_download = QtWidgets.QPushButton("Download")
self.progressbar = QtWidgets.QProgressBar(maximum=100)
self.btn_quality.clicked.connect(self.on_clicked_quality)
self.btn_download.clicked.connect(self.download)
self.btn_download.setDisabled(True)
self.progressChanged.connect(self.progressbar.setValue)
self.qualitiesChanged.connect(self.update_qualityes)
self.finished.connect(self.on_finished)
form_lay = QtWidgets.QFormLayout(central_widget)
form_lay.addRow("Url: ", self.le_url)
form_lay.addRow(self.btn_quality)
form_lay.addRow("qualities: ", self.combo_quality)
form_lay.addRow("Output: ", self.le_output)
form_lay.addRow(self.btn_download)
form_lay.addRow(self.progressbar)
#QtCore.pyqtSlot()
def on_finished(self):
self.update_disables(False)
#QtCore.pyqtSlot()
def on_clicked_quality(self):
video_url = self.le_url.text()
threading.Thread(target=self.get_qualities, args=(video_url,)).start()
def get_qualities(self, video_url):
pafy_video = pafy.new(video_url)
types_of_video = pafy_video.allstreams # videostreams
self.qualitiesChanged.emit(types_of_video)
#QtCore.pyqtSlot(list)
def update_qualityes(self, types_of_video):
for t in types_of_video:
self.combo_quality.addItem(str(t), t)
self.btn_download.setDisabled(False)
#QtCore.pyqtSlot()
def download(self):
video_save = self.le_output.text()
d = self.combo_quality.currentData()
threading.Thread(target=d.download, kwargs={'filepath': video_save, 'callback': self.callback}, daemon=True).start()
def callback(self, total, recvd, ratio, rate, eta):
print(ratio)
val = int(ratio * 100)
self.progressChanged.emit(val)
if val == 100:
self.finished.emit()
def update_disables(self, state):
self.combo_quality.setDisabled(state)
self.btn_quality.setDisabled(state)
self.le_output.setDisabled(state)
self.le_url.setDisabled(state)
self.btn_download.setDisabled(not state)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(320, 480)
w.show()
sys.exit(app.exec_())

Two QListView box one showing files in a folder and one shows selected files from the first QListview

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
hlay = QHBoxLayout(self)
self.listview = QListView()
self.listview2 = QListView()
hlay.addWidget(self.listview)
hlay.addWidget(self.listview2)
path = r'C:\Users\Desktop\Project'
self.fileModel = QFileSystemModel()
self.fileModel.setFilter(QDir.NoDotAndDotDot | QDir.Files)
self.listview.setRootIndex(self.fileModel.index(path))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I want to display the files in my listview from my folder with path described in the code and able to
select them, the files I selected will be displayed in my listview2, However, the listview doesn't show
the files in this path. Can anyone help me with it?
The files are not displayed because you have not set a rootPath in the QFileSystemModel.
On the other hand the second QListView must have a model where items are added or removed as they are selected or deselected, for this you must use the selectionChanged signal of selectionModel() of the first QListView, that signal transports the information of the selected and deselected items.
To change the color you must obtain the QStandardItem and use the setData() method with the Qt::BackgroundRole role. In the example in each second the color is changed randomly
import sys
import random
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(Widget, self).__init__(*args, **kwargs)
self.listview = QtWidgets.QListView()
self.listview2 = QtWidgets.QListView()
path = r'C:\Users\Desktop\Project'
self.fileModel = QtWidgets.QFileSystemModel(self)
self.fileModel.setRootPath(path)
self.fileModel.setFilter(QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Files)
self.listview.setModel(self.fileModel)
self.listview.setRootIndex(self.fileModel.index(path))
self.listview.selectionModel().selectionChanged.connect(self.on_selectionChanged)
self.listview.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.model = QtGui.QStandardItemModel(self)
self.listview2.setModel(self.model)
hlay = QtWidgets.QHBoxLayout(self)
hlay.addWidget(self.listview)
hlay.addWidget(self.listview2)
timer = QtCore.QTimer(self, interval=1000, timeout=self.test_color)
timer.start()
def on_selectionChanged(self, selected, deselected):
roles = (QtCore.Qt.DisplayRole,
QtWidgets.QFileSystemModel.FilePathRole,
QtWidgets.QFileSystemModel.FileNameRole,
QtCore.Qt.DecorationRole)
for ix in selected.indexes():
it = QtGui.QStandardItem(ix.data())
for role in roles:
it.setData(ix.data(role), role)
it.setData(QtGui.QColor("green"), QtCore.Qt.BackgroundRole)
self.model.appendRow(it)
filter_role = QtWidgets.QFileSystemModel.FilePathRole
for ix in deselected.indexes():
for index in self.model.match(ix.parent(), filter_role, ix.data(filter_role), -1, QtCore.Qt.MatchExactly):
self.model.removeRow(index.row())
def test_color(self):
if self.model.rowCount() > 0:
n_e = random.randint(0, self.model.rowCount())
rows_red = random.sample(range(self.model.rowCount()), n_e)
for row in range(self.model.rowCount()):
it = self.model.item(row)
if row in rows_red:
it.setData(QtGui.QColor("red"), QtCore.Qt.BackgroundRole)
else:
it.setData(QtGui.QColor("green"), QtCore.Qt.BackgroundRole)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

PyQt5 - How to add a scrollbar to a QMessageBox

I have a list which is generated based on user-input.
I am trying to display this list in a QMessageBox. But, I have no way of knowing the length of this list. The list could be long.
Thus, I need to add a scrollbar to the QMessageBox.
Interestingly, I looked everywhere, but I haven’t found any solutions for this.
Below is, what I hope to be a “Minimal, Complete and Verifiable Example”, of course without the user input; I just created a list as an example.
I appreciate any advice.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class W(QWidget):
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
self.btn = QPushButton('Show Message', self)
self.btn.setGeometry(10, 10, 100, 100)
self.btn.clicked.connect(self.buttonClicked)
self.lst = list(range(2000))
self.show()
def buttonClicked(self):
result = QMessageBox(self)
result.setText('%s' % self.lst)
result.exec_()
if __name__ == "__main__":
app = QApplication(sys.argv)
gui = W()
sys.exit(app.exec_())
You can not add a scrollbar directly since the widget in charge of displaying the text is a QLabel. The solution is to add a QScrollArea. The size may be inadequate so a stylesheet has to be used to set minimum values.
class ScrollMessageBox(QMessageBox):
def __init__(self, l, *args, **kwargs):
QMessageBox.__init__(self, *args, **kwargs)
scroll = QScrollArea(self)
scroll.setWidgetResizable(True)
self.content = QWidget()
scroll.setWidget(self.content)
lay = QVBoxLayout(self.content)
for item in l:
lay.addWidget(QLabel(item, self))
self.layout().addWidget(scroll, 0, 0, 1, self.layout().columnCount())
self.setStyleSheet("QScrollArea{min-width:300 px; min-height: 400px}")
class W(QWidget):
def __init__(self):
super().__init__()
self.btn = QPushButton('Show Message', self)
self.btn.setGeometry(10, 10, 100, 100)
self.btn.clicked.connect(self.buttonClicked)
self.lst = [str(i) for i in range(2000)]
self.show()
def buttonClicked(self):
result = ScrollMessageBox(self.lst, None)
result.exec_()
if __name__ == "__main__":
app = QApplication(sys.argv)
gui = W()
sys.exit(app.exec_())
Output:
Here is another way to override the widgets behavior.
You can get references to the children of the widget by using 'children()'.
Then you can manipulate them like any other widget.
Here we add a QScrollArea and QLabel to the original widget's QGridLayout. We get the text from the original widget's label and copy it to our new label, finally we clear the text from the original label so it is not shown (because it is beside our new label).
Our new label is scrollable. We must set the minimum size of the scrollArea or it will be hard to read.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class ScrollMessageBox(QMessageBox):
def __init__(self, *args, **kwargs):
QMessageBox.__init__(self, *args, **kwargs)
chldn = self.children()
scrll = QScrollArea(self)
scrll.setWidgetResizable(True)
grd = self.findChild(QGridLayout)
lbl = QLabel(chldn[1].text(), self)
lbl.setWordWrap(True)
scrll.setWidget(lbl)
scrll.setMinimumSize (400,200)
grd.addWidget(scrll,0,1)
chldn[1].setText('')
self.exec_()
class W(QWidget):
def __init__(self):
super(W,self).__init__()
self.btn = QPushButton('Show Message', self)
self.btn.setGeometry(10, 10, 100, 100)
self.btn.clicked.connect(self.buttonClicked)
self.message = ("""We have encountered an error.
The following information may be useful in troubleshooting:
1
2
3
4
5
6
7
8
9
10
Here is the bottom.
""")
self.show()
def buttonClicked(self):
result = ScrollMessageBox(QMessageBox.Critical,"Error!",self.message)
if __name__ == "__main__":
app = QApplication(sys.argv)
gui = W()
sys.exit(app.exec_())

Categories