How do I improve the efficiency of the PyQt4 QListWidget - python

I have the following code and am wondering if there is a way to make this more efficient. The setCurrentItem() and scrollToItem() functions seem to slow the process down considerable. Also, I would like to see the items show up in the list as they are added instead of all at once after the loop has completed. Any help or discussion would be greatly appreaciated.
import sys
from math import *
#from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import (Qt, SIGNAL, QSize)
from PyQt4.QtGui import (QApplication, QDialog, QLabel, QListWidget, QListWidgetItem, QPushButton, QScrollArea, QTextDocument, QVBoxLayout)
from time import localtime, strftime
import time
class LogDlg(QDialog):
def __init__(self, parent=None):
super(LogDlg, self).__init__(parent)
self.resize(450, 380)
self.log1 = QListWidget()
self.log2 = QListWidget()
lbl = QLabel()
lbl_2 = QLabel()
lbl.setText("Communications Log")
lbl_2.setText("Command/Data Log")
self.pushButton = QPushButton()
self.pushButton.setMaximumSize(QSize(110, 24))
self.pushButton.setObjectName("pushButton")
self.pushbutton = QPushButton
self.pushButton.setText("Start Log Loop")
layout = QVBoxLayout()
layout.addWidget(self.pushButton)
layout.addWidget(lbl)
layout.addWidget(self.log1)
layout.addWidget(lbl_2)
layout.addWidget(self.log2)
self.setLayout(layout)
self.setWindowTitle("Transaction Logs")
self.connect(self.pushButton,SIGNAL("clicked()"),self.logLoop)
self.time = time.time()
def logLoop(self):
for i in range(1000):
print i
self.addLog("This is a test","c",True)
def timeStamp(self):
now = time.time()
localtime = time.localtime(now)
milliseconds = '%02d' % int((now - int(now)) * 100)
val = time.strftime('%H:%M:%S.', localtime) + milliseconds
return val
def clearUi(self):
self.log1.clear()
self.log2.clear()
def addLog(self, data, type="c", ts=False):
# pass
t = self.timeStamp()
if ts == True:
data = t + " " + data
if type == "c":
self.listItem1 = QListWidgetItem()
self.listItem1.setText(data)
self.log1.addItem(self.listItem1)
# self.log1.scrollToItem(self.listItem1)
self.log1.setCurrentItem(self.listItem1)
elif type == "d":
self.listItem2 = QListWidgetItem()
self.listItem2.setText(data)
self.log2.addItem(self.listItem2)
# self.log2.scrollToItem(self.listItem2)
self.log2.setCurrentItem(self.listItem2)
app = QApplication(sys.argv)
form = LogDlg()
form.open()
app.exec_()

Your issue doesn't have anything to do with .scrollToItem or .setCurrentItem. The loop in logLoop method doesn't give any chance for the Qt event loop to update things. One way to solve it is, give the Qt a chance to update the GUI with QApplication.processEvents(). So if you modify the logLoop as following, you should see the items as they are added:
def logLoop(self):
for i in range(1000):
print i
self.addLog("This is a test","c",True)
QApplication.processEvents()
This is useful to some degree. If the time between processEvents is small enough you'll get a responsive UI. But once things get more complicated and the time to complete tasks for each segment increases, you'll need to delegate that piece of code to a separate thread (QThread) in order to keep the GUI responsive.
One other issue is, things will get slower once you have a large number of items in the QListWidget. You may notice that adding the 25th item is faster than 925th item. That's because QListWidget (or QTableWidget/QTreeWidget) doesn't scale well. If you are going to have large number of items, model/view framework (QListView/QTableView/QTreeView) should be your choice.

Related

Python, first window crash until another function its executed

I want my program to show another window where it says that it's the break time when the determined requirements are met (in this case the exact same hour) but when I run it, the first window (not the login one) crashes or simply doesn't show up until it is the hour.
(horatime and min can be changed for testing it)
main and main2 are windows programed in PyCharm they are clocks.
I want to show main window until the time set in the code ("timecheck") is reached then "timecheck" will hide main and show main2
this is my code:
'''
from PyQt5 import QtWidgets, uic
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QTimer, QTime
from PyQt5.uic import loadUi
import datetime
app = QtWidgets.QApplication([])
login = uic.loadUi("ventana1.ui")
time = uic.loadUi("ventana2.ui")
error = uic.loadUi("ventana3.ui")
def gui_login():
name = login.lineEdit.text()
password = login.lineEdit_2.text()
if len(name)==0 or len(password)==0:
login.label_5.setText("Ingrese todos los datos")
elif name == "Pedro" and password == "1234":
gui_clock()
timecheck()
else:
gui_error()
def gui_clock():
login.hide()
main.show()
def timecheck():
horatime = int(19)
min = int(21)
while True:
if horatime == datetime.datetime.now().hour and min == datetime.datetime.now().minute:
main.hide()
main2.show()
break
#this is the clock1, it shows the time in red
class VentanaPrincipal(QMainWindow):
def __init__(self):
super(VentanaPrincipal, self).__init__()
loadUi('ventana2.ui', self)
timer = QTimer(self)
timer.timeout.connect(self.displayTime)
timer.start(1000)
def displayTime(self):
currentTime = QTime.currentTime()
displayText = currentTime.toString('hh:mm:ss')
self.reloj.setText(displayText)
self.lcdNumber.display(displayText)
#this is the clock2, it shows the time in green
class VentanaPrincipal2(QMainWindow):
def __init__(self2):
super(VentanaPrincipal2, self2).__init__()
loadUi('ventana4.ui', self2)
timer2 = QTimer(self2)
timer2.timeout.connect(self2.displayTime)
timer2.start(1000)
def displayTime(self):
currentTime2 = QTime.currentTime()
displayText2 = currentTime2.toString('hh:mm:ss')
self.reloj.setText(displayText2)
self.lcdNumber.display(displayText2)
def gui_error():
login.hide()
error.show()
def back_error():
error.hide()
login.show()
login.pushButton.clicked.connect(gui_login)
error.pushButton.clicked.connect(regresar_error)
login.show()
main = VentanaPrincipal()
main2 = VentanaPrincipal2()
app.exec()
i tried a lot of things to change the loop to show what i want, but im just an amateur on programming so i cant find a way to make my programm to do what i want

PyQt5 chang Label text using Loop

code it's clean from bugs but when is running program will freezing
the program its countdown using button to start the countdown
the picture below discripe the layout
enter image description here
the probelm is im using loop to changing label text and that make program freezed
from time import sleep
import PyQt5.QtWidgets as Qtw
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
pushButton = Qtw.QPushButton("start",clicked = lambda: setting_label())
self.layout().addWidget(pushButton)
my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(my_label)
self.show()
def setting_label():
t = 1200
while t:
h = t // 3600
m = t // 60
s = t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
my_label.setText(timer)
sleep(1)
t -= 1
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
The way the code is written in the OP, it doesn't really get stuck or frozen. But rather, the display fails to update. You can get around this by trying to force Qt to update the GUI with app.processEvents(). Put it in your setting_label function after the setText():
self.my_label.setText(timer)
app.processEvents()
The Preferred Way
Using app.processEvents() a lot is usually discouraged. Another way to make a countdown timer is to use a QTimer. There is a little more overhead in setting up all of the signals and slots. But it can be very powerful. Here is an example
from time import sleep
import PyQt5.QtWidgets as Qtw
from PyQt5.QtCore import QTimer
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
pushButton = Qtw.QPushButton("start",clicked = self.start_countdown)
self.layout().addWidget(pushButton)
self.my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(self.my_label)
self.timer = QTimer() # create a new QTimer instance
self.timer.setInterval(1000) # make it fire every 1000 msec
self.t = 1200
self.timer.timeout.connect(self.setting_label) # connect the timeout signal to self.setting_label
self.show()
def start_countdown(self):
self.t = 1200
self.timer.start()
def setting_label(self):
if self.t == 0:
self.timer.stop()
print('timer stopped')
h = self.t // 3600
m = self.t // 60
s = self.t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
self.my_label.setText(timer)
self.t -= 1
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
Edit: This solution works for the OP, but others have pointed out that doing this using threading can cause unexpected behavior, crashes, etc. so it's better to do this using other methods (for example: this answer)
The interface freezes because the while loop you use to change the label blocks the event loop of the app (i.e. receiving inputs). You can fix this by moving the function to a thread like this:
import threading
from time import sleep
import PyQt5.QtWidgets as Qtw
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
def setting_label():
t = 1200
while t:
h = t // 3600
m = t // 60
s = t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
my_label.setText(timer)
sleep(1)
t -= 1
pushButton = Qtw.QPushButton(
"start",
clicked=lambda: thread.Thread(target=setting_label).start()
)
self.layout().addWidget(pushButton)
my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(my_label)
self.show()
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()

Why doesn't QTreeView.scrollTo() work initially

The code below just displays a tree view of computer drives. Each time a new file/folder is selected, the view scrolls to make this new selection visible.
Question 1: While this works, the initial selection after the application is launched doesn't trigger the scroll. Why?
Question 2: If the instructions:
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
are inverted:
self.my_view.resizeColumnToContents(0)
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
the first column size is not adjusted either on the initial display, only after. Why?
import sys
from PyQt5.QtCore import Qt, QModelIndex, QDir
from PyQt5.QtWidgets import QApplication, QTreeView, QMainWindow, QFileSystemModel, QAbstractItemView
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
# Instance variables
self.my_view = QTreeView()
self.my_model = QFileSystemModel()
# Init FS model to show all computer drives
model_root_path = str(self.my_model.myComputer())
self.my_model.setRootPath(model_root_path)
# Init tree view
self.my_view.setModel(self.my_model)
self.my_view.setRootIndex(self.my_model.index(model_root_path))
self.my_view.setSelectionMode(QAbstractItemView.SingleSelection)
self.my_view.setSelectionBehavior(QAbstractItemView.SelectRows)
# Connect selection change events to custom slot
select_model = self.my_view.selectionModel()
select_model.currentRowChanged.connect(self.current_row_changed)
# Main window
self.setCentralWidget(self.my_view)
self.setGeometry(200, 200, 800, 600)
# Select initial row on view
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
def current_row_changed(self):
"""Current row of the model has changed"""
# Scroll view to new row
index = self.my_view.selectionModel().currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
# Show path of current row in window title
absolute_path = self.my_model.filePath(index)
self.setWindowTitle(absolute_path)
def main():
a = QApplication(sys.argv)
mw = MyWindow()
mw.show()
sys.exit(a.exec_())
if __name__ == '__main__':
main()
`
Edit: After using the good solution provided by #ekhumoro, my sample code above worked. However this other piece of code still didn't:
import os
import sys
from PyQt5.QtCore import pyqtSignal, QTimer, QDir, Qt
from PyQt5.QtWidgets import QMainWindow, QGridLayout, QWidget, QTreeView, QAbstractItemView, QFileSystemModel, \
QApplication
class AppWindow(QMainWindow):
default_folder_path = "."
def __init__(self):
super().__init__()
self.folder_view = FolderTreeView()
self.folder_view.folder_has_changed.connect(self.folder_changed)
self.build_ui()
self.show()
# Select initial folder
self.select_initial_folder()
def build_ui(self):
main_widget = QWidget()
layout = QGridLayout(main_widget)
layout.addWidget(self.folder_view)
self.setCentralWidget(main_widget)
self.setGeometry(200, 100, 800, 600)
def select_initial_folder(self):
folder_index = self.folder_view.get_index(AppWindow.default_folder_path)
if folder_index.isValid():
self.folder_view.select_folder(folder_index)
def folder_changed(self, folder_path):
if not os.path.isdir(folder_path):
print("Non existing folder:", folder_path)
return
class FolderTreeView(QTreeView):
folder_has_changed = pyqtSignal(str)
def __init__(self):
super().__init__()
self.folder_tree_model = FolderTreeModel()
self.setModel(self.folder_tree_model)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
def select_folder(self, folder_index):
self.setCurrentIndex(folder_index)
def currentChanged(self, current, previous):
super(FolderTreeView, self).currentChanged(current, previous)
# Scroll the view to current item and resize folder name column
QTimer.singleShot(50, lambda: self.delayed_scroll(current))
# Emit signal for other uses
self.folder_has_changed.emit(self.folder_tree_model.filePath(current))
def delayed_scroll(self, index):
self.scrollTo(index, QAbstractItemView.EnsureVisible)
self.resizeColumnToContents(0)
def get_index(self, folder_path):
return self.folder_tree_model.index(folder_path)
class FolderTreeModel(QFileSystemModel):
def __init__(self):
super().__init__()
self.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
self.setRootPath("")
def main():
app = QApplication(sys.argv)
window = AppWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The first problem may be caused if, by default, the model initialises its current index to the current directory. This would mean that if you set it again to the same index, the row-change signal will not be emitted (because nothing changed). This can be fixed by calling the row-change handler directly:
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
...
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
self.current_row_changed()
def current_row_changed(self):
index = self.my_view.currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
...
As to the second problem: when you call scrollTo, it may have to expand several directories in order to select the required index. This could obviously change the width of the first column, so you should always call resizeColumnToContents afterwards in order to get the correct width.
UPDATE:
I think there is also another problem caused by timing issues. The QFileSystemModel must work asynchronously to some extent, because it has to request resources from the operating system and then wait for the response. Also, before it gets the response, it cannot know in advance exactly how much data it is going to receive, because the file-system may have been updated while it was waiting. Potentially, the response could include data from a huge directory containing thousands of files. So in order to keep the GUI responsive, the data is processed in batches which are of a sufficient size to fill the current view. If the current index is set before the window has been shown and all its widgets fully laid out, there is no guarantee that the view will be able to resize its columns correctly.
This can be fixed by explicitly re-calling the row-change handler via a one-shot timer with a small delay. This should allow the view to recalculate its column widths correctly:
...
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
QTimer.singleShot(50, self.current_row_changed)
def current_row_changed(self):
index = self.my_view.currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)

PySide QWidget immediate update

In my application, I have a call to an external module which spawns some threads, does some stuff, then returns a value. I'm trying to get a QMessageBox to show before and a QLabel to update after this is complete, but I'm stumped. The code goes something like this (called from QObject.connect on a button):
def _process(self):
self._message_box.show()
for i in range(3):
rv = external_module_function_with_threads() // blocking function call
label = getattr(self, "label%d" % (i + 1))
label.setText(rv)
When I click the button and the function is called, the message box only shows after the loop completes. The labels only update after the loop completes as well. I tried calling label.repaint() in the loop, but all that seems to do is make the message box show up earlier (but with no text in it).
I know I'm not violating the "GUI operations from outside the main thread" rule (...right?), so is there a way to force an update?
For your message box use self._message_box.exec_(). From my understanding of your question, I think this will do what you want.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QVBoxLayout(self)
button = QPushButton("Press me")
self.label = QLabel("Run #")
map(layout.addWidget, [button, self.label])
button.pressed.connect(self.buttonPressed)
self.messageBox = QMessageBox()
def buttonPressed(self):
self.messageBox.exec_()
Thread().run(self.label)
class Thread(QThread):
def run(self, label):
for x in range(5):
self.updateLabel(label)
app.processEvents()
time.sleep(.5)
def updateLabel(self, label):
try:
number = int(label.text().split(" ")[-1])
number += 1
except ValueError:
number = 0
label.setText("Run %i" % number)
app = QApplication([])
main = Main()
main.show()
sys.exit(app.exec_())

Python PyQt4 progressbar Freezing GUI

the following code counts a bunch of PyQt4 progressbar's up tp 99%, I would like for my GUI NOT to freeze as they count up to 99%. I would LOVE to do this without custom classes or functions if it is possible.
I know it is good to use classes but for this tiny snippet of code I don't want to create a class.
From what I've read there may be a update() function that could accomplish this ... please advise If I'm on the right track
import sys
import time
from PyQt4 import QtGui
from PyQt4 import QtCore
app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
widget.resize(400, 200)
widget.setWindowTitle('simple')
widget.show()
shift = 0
cntControl = 5
barra = [0] * cntControl
for i in range(cntControl):
shift = shift + 10
barra[i] = QtGui.QProgressBar(widget)
barra[i].show()
barra[i].setMinimum(0)
barra[i].setMaximum(10000)
barra[i].setGeometry(QtCore.QRect(10, shift, 200, 10))
for a in range(10000):
for i in range(cntControl):
barra[i].setValue(a)
sys.exit(app.exec_())
try changing your for loop with:
while True:
for a in range(10000):
time.sleep(0.0001)
for i in range(cntControl):
barra[i].setValue(a)
if works for me.
The while loop continues endlessly moving the bar. If you are looking only to clean the bar after it reaches the end you should use reset:
PySide.QtGui.QProgressBar.reset()
Reset the progress bar. The progress bar “rewinds” and shows no
progress
Update after OP comments: If you want your gui to be responsive when entering a long loop or other operation you should use either python thread module or QThreads.
I really could not get threads to work at all ... I could post my thread attempt which looks flawless to my (now tired) eyes ...
I have however been able to tweek http://zetcode.com/tutorials/pyqt4/widgets/ 's progressbar example and came out with the following code ... which solves the problem of freezing in the GUI:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.setWindowTitle('ProgressBar')
self.setGeometry(300, 300, 250, 150)
self.pbar = [0] * 3
self.timer = [0] * 3
self.step = [0] * 3
self.shift = 0
for i in range(3):
self.shift = self.shift + 30
self.pbar[i] = QtGui.QProgressBar(self)
self.pbar[i].setGeometry(30, self.shift, 200, 25)
self.timer[i] = QtCore.QBasicTimer()
self.step[i] = 0
self.timer[i].start(100, self)
def timerEvent(self, event):
for i in range(3):
if self.step[i] >= 100:
self.timer[i].stop()
return
self.step[i] = self.step[i] + 1
self.pbar[i].setValue(self.step[i])
app = QtGui.QApplication(sys.argv) ex
= Example() ex.show() app.exec_()
I have NO IDEA why it works which is probably not a good thing. I'm guessing it might have something to do with super(Example, self).__init__() and the custom timer pyqt4 uses. I was really hoping to do this without functions or classes, but not sure that is possible. If you think it is, feel free to post!

Categories

Resources