How to fill QStandardItemModel with bigdata if setItem works slow? - python

My task is to fill QStandardItemModel with some rect-shaped bigdata. The Python code is
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
import time
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(540, 220)
self.table_view = QTableView(self)
self.table_view.setGeometry(10, 10, 400, 200)
self.button = QPushButton(self)
self.button.move(420, 20)
self.button.setText('Generate!')
self.table_model = QStandardItemModel(parent=self)
self.button.clicked.connect(self.button_clicked)
def button_clicked(self):
start_time = time.time()
start_cell = [2, 1]
for i in range(100):
for j in range(1000):
item = QStandardItem('X')
self.table_model.setItem(start_cell[0] + i, start_cell[1] + j, item)
self.table_view.setModel(self.table_model)
print(time.time() - start_time)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
For the first time it works pretty fast (0.2 s at my computer) but for the second time it takes 10s because QTableView is connected with QStandardItemModel self.table_view.setModel(self.table_model)
Could you advice how to disconnect QTableView and QStandardItemModel and if the more optimal way to fill table with bigdata?
===
Thanks to #musicamante the following code was realized. Now table is filled with insertRow() and now it takes ~0.4 sec.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
import time
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(540, 220)
self.table_view = QTableView(self)
self.table_view.setGeometry(10, 10, 400, 200)
self.button = QPushButton(self)
self.button.move(420, 20)
self.button.setText('Paste bigdata')
self.table_model = QStandardItemModel(parent=self)
self.table_view.setModel(self.table_model)
self.button.clicked.connect(self.button_clicked)
for i in range(10):
for j in range(10):
self.table_model.setItem(i, j, QStandardItem(str(10 * i + j)))
def button_clicked(self):
start_time = time.time()
start_row = 2 # coordinates of bigdata array top-left corner
start_col = 2
for i in range(100): # number of rows in bigdata array
row = []
# Adding cells before array
for j in range(start_col):
if self.table_model.item(start_row + i, j) is not None:
row.append(QStandardItem(self.table_model.item(start_row + i, j).text()))
else:
row.append(QStandardItem(''))
# Adding array cells
for j in range(1000): # number of columns in bigdata array
row.append(QStandardItem('X'))
# Adding cells after array
for j in range(start_col + 1000, self.table_model.columnCount()-1, 1):
if self.table_model.item(start_row + i, j) is not None:
row.append(QStandardItem(self.table_model.item(start_row + i, j).text()))
else:
row.append(QStandardItem(''))
self.table_model.insertRow(start_row + i, row)
self.table_model.removeRows(start_row + i + 1, 1)
print(time.time() - start_time)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())

Related

Pyside/Pyqt5 dynamically adding and sorting items

I have a simple user interface where I want to dynamically add frames and labels in a widget (as I will use these labels to transmit a video feed from my webcams).
In the following code I set a function where the user selects an integer which represents the number of labels(webcams) they want to see and then dynamically adds these labels& frames to the widget:
def loopCamFeed(self,n):
if (n % 2) == 0:
dividnd = n / 2
for i in range(2):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.horizontalLayout_14 = QHBoxLayout(self.frame_12)
self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
for i in range(int(dividnd)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.horizontalLayout_14.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
Which displays the labels as in the image below:
--By adding a value of 2:
--By adding a value of 4):
By adding a value of 8:
The challenge that I am facing is how to handle an odd number selection. For example, if a user selects 3 or 7 webcams/labels.
If a user selects 3 labels/webcams, I'd want to show one on the top frame and two at the bottom.
MAIN.PY (Where this piece of code was written):
from ui_interface import *
import sys
from Custom_Widgets.Widgets import *
import cv2
import numpy as np
from PyQt5.QtCore import pyqtSignal, QObject, QThread
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
loadJsonStyle(self, self.ui)
self.show()
#Expand Center Menu Widget
self.ui.settingsBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
self.ui.infoBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
self.ui.helpBtn.clicked.connect(lambda: self.ui.centerMenuContainer.expandMenu())
#Close Center Menu Widget
self.ui.closeCenterMenuButton.clicked.connect(lambda: self.ui.centerMenuContainer.collapseMenu())
#Close Notification Menu Widget
self.ui.closeNotificationBtn.clicked.connect(lambda: self.ui.popUpNotificationContainer.collapseMenu())
self.loopCamFeed(4)
def ImageUpdateSlot(self, Image):
self.ui.label_5.setPixmap(QPixmap.fromImage(Image))
def CancelFeed(self):
self.worker1.stop()
def startVideo(self):
self.worker1 = Worker1()
self.worker1.start()
self.worker1.ImageUpdate.connect(self.ImageUpdateSlot)
def loopCamFeed(self,n):
if (n % 2) == 0:
dividnd = n / 2
for i in range(2):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.horizontalLayout_14 = QHBoxLayout(self.frame_12)
self.horizontalLayout_14.setObjectName(u"horizontalLayout_14")
for i in range(int(dividnd)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.horizontalLayout_14.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
class Worker1(QThread):
ImageUpdate = pyqtSignal(QImage)
def __init__(self):
super().__init__()
def run(self):
self.ThreadActive = True
Capture = cv2.VideoCapture(0)
while self.ThreadActive:
ret, frame = Capture.read()
if ret:
Image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
FlippedImage = cv2.flip(Image, 1)
ConvertToQtFormat = QImage(FlippedImage.data, FlippedImage.shape[1], FlippedImage.shape[0], QImage.Format_RGB888)
Pic = ConvertToQtFormat.scaled(1200, 900, Qt.KeepAspectRatio)
self.ImageUpdate.emit(Pic)
def stop(self):
self.ThreadActive = False
self.quit()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Use a grid layout instead of a horizontal layout:
def loopCamFeed(self,n):
self.frame_12 = QFrame(self.ui.webcamWidget)
self.frame_12.setObjectName(u"frame_12")
self.frame_12.setFrameShape(QFrame.StyledPanel)
self.frame_12.setFrameShadow(QFrame.Raised)
self.grid_layout = QGridLayout(self.frame_12)
self.grid_layout.setObjectName(u"grid_layout")
for i in range(int(n)):
self.label_5 = QLabel("hello",self.frame_12)
self.label_5.setObjectName(u"label_5")
self.grid_layout.addWidget(self.label_5, 0, Qt.AlignHCenter)
self.ui.verticalLayout_15.addWidget(self.frame_12)
As Medhat mentioned, applying the GridLayout was the best solution.
I applied the following code:
def loopCamFeed(self,n):
w = 0
if n > 0:
# if ( n % 2) == 0:
for i in range(int(n)):
if (i%2) == 0:
w +=1
print(int(w / 2), (i%2))
self.label = QtWidgets.QLabel()
self.label.setText("Screen " +str(i))
self.label.setStyleSheet("background-color: black5;")
self.label.setObjectName(u"label")
self.gridLayout.addWidget(self.label, (i%2) ,int(w),Qt.AlignHCenter )
This works perfectly! Thanks #Medhat

How to size QMainWindow to fit a QTableWidget that has setVerticalHeaderLabels

Here is the sample code:
from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, \
QMainWindow
from PyQt5.QtCore import QSize
import sys
DATA = {
f'col{i}': [f'{i * j}' for j in range(1, 10)] for i in range(1, 10)
}
class Table(QTableWidget):
def __init__(self, d):
m = len(d[next(iter(d))])
n = len(DATA)
super().__init__(m, n)
hor_headers = []
for n, (key, values) in enumerate(DATA.items()):
hor_headers.append(key)
for m, item in enumerate(values):
qtitem = QTableWidgetItem(item)
self.setItem(m, n, qtitem)
self.setHorizontalHeaderLabels(hor_headers)
# the sizeHint works fine if I disable this line
self.setVerticalHeaderLabels(f'row{i}' for i in range(1, m + 2))
self.resizeColumnsToContents()
self.resizeRowsToContents()
# improves the situation but still the window is smaller than the table
def sizeHint(self):
hh = self.horizontalHeader()
vh = self.verticalHeader()
fw = self.frameWidth() * 2
return QSize(
hh.length() + vh.width() + fw,
vh.length() + hh.height() + fw)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('<TITLE>')
table = Table(DATA)
self.setCentralWidget(table)
# did not work
# self.setFixedSize(self.layout().sizeHint())
def main(args):
app = QApplication(args)
main_win = MainWindow()
main_win.show()
raise SystemExit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
Here is the result:
The 9th row and the 9th column are not shown and there are scroll bars.
If I comment out the self.setVerticalHeaderLabels(f'row{i}' for i in range(1, m + 2)) line then it will work:
How can I perfectly fit the main window to the table widget while having vertical header labels?
As you can see in code comments, I have tried the solutions suggested at python qt : automatically resizing main window to fit content but they are not are not working.
The problem is that when a complex widget like an item view is not yet "mapped", the actual size of its children (headers and scroll bars) is not yet updated. Only when the view is finally shown and possibly added to a layout, then it will resize itself again in order to properly resize its children using updateGeometries.
This means that, until that point, the size of each header is based on its default basic contents (the row number for a vertical header).
The solution is simple: don't use the header size, but their hints, which are computed using the actual text that is going to be displayed:
def sizeHint(self):
hh = self.horizontalHeader()
vh = self.verticalHeader()
fw = self.frameWidth() * 2
return QSize(
hh.length() + vh.sizeHint().width() + fw,
vh.length() + hh.sizeHint().height() + fw)

Grouping about 100 Radio Buttons

I'm trying to create a multiple-choice test with approximately 100 questions in it. In this example, I give you a group of radio buttons. I solved creating multiple radio buttons with this code. However, I want to group these selections.
I found a soultion in this link : https://www.delftstack.com/tutorial/pyqt5/pyqt5-radiobutton/#:~:text=setChecked(True)-,PyQt5%20QRadiobutton%20Group,are%20connected%20to%20different%20functions.
However, they didn't create radio buttons with 'for' loop. What should I do about this?
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtWidgets
class Ornek(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.toggles = []
self.lay = QVBoxLayout()
self.h_box = QGridLayout()
for i in range (4):
self.btngroup = QButtonGroup()
for j in range (4):
if j % 4 == 0:
self.btn = QRadioButton("A", self)
elif j % 4 == 1:
self.btn = QRadioButton("B", self)
elif j % 4 == 2:
self.btn = QRadioButton("C", self)
else:
self.btn = QRadioButton("D", self)
text = self.btn.text()
self.btn.clicked.connect(lambda ch, text=text: print("\nclicked--> {}".format(text)))
self.h_box.addWidget(self.btn,i,j,1,1)
self.lay.addLayout(self.h_box)
self.setLayout(self.lay)
self.setGeometry(300,300,250,250)
self.setWindowTitle("Çıkış Projesi")
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
pencere = Ornek()
sys.exit(app.exec_())
You have created a QButtonGroup but you don't use it, your code can be rewritten as:
def initUI(self):
self.toggles = []
lay = QVBoxLayout(self)
h_box = QGridLayout()
lay.addLayout(h_box)
for i in range(4):
btngroup = QButtonGroup(self)
btngroup.buttonClicked.connect(lambda btn: print(btn.text()))
for j in range(4):
btn = QRadioButton()
btngroup.addButton(btn)
h_box.addWidget(btn, i, j, 1, 1)
if j % 4 == 0:
btn.setText("A")
elif j % 4 == 1:
btn.setText("B")
elif j % 4 == 2:
btn.setText("C")
else:
btn.setText("D")
self.setGeometry(300, 300, 250, 250)
self.setWindowTitle("Çıkış Projesi")
self.show()

How to create a grid of splitters

What I'm trying to do is add splitter to a QGridLayout in order to resize the layout with the mouse. So for instance with this :
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.setFixedWidth(300)
self.setFixedHeight(100)
self.wid = QWidget()
self.setCentralWidget(self.wid)
self.grid = QGridLayout()
l_a = QLabel('A')
l_b = QLabel('B')
l_c = QLabel('C')
l_d = QLabel('D')
l_e = QLabel('E')
l_f = QLabel('F')
l_g = QLabel('G')
l_h = QLabel('H')
l_i = QLabel('I')
self.grid.addWidget(l_a, 0, 0)
self.grid.addWidget(l_b, 0, 1)
self.grid.addWidget(l_c, 0, 2)
self.grid.addWidget(l_d, 1, 0)
self.grid.addWidget(l_e, 1, 1)
self.grid.addWidget(l_f, 1, 2)
self.grid.addWidget(l_g, 2, 0)
self.grid.addWidget(l_h, 2, 1)
self.grid.addWidget(l_i, 2, 2)
self.wid.setLayout(self.grid)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
I get this:
What I would like is instead of the colored line, have the possibility to click and drag vertically (for green lines) and horizontally (for red lines) the grid borders.
I tried something with QSplitter directly, but I end up with:
The Horizontal splits are okay, but the vertical ones are not aligned any more:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.setFixedWidth(300)
self.setFixedHeight(100)
self.wid = QWidget()
self.setCentralWidget(self.wid)
# self.grid = QGridLayout()
self.globallayout = QVBoxLayout()
self.split_V = QSplitter(Qt.Vertical)
l_a = QLabel('A')
l_b = QLabel('B')
l_c = QLabel('C')
l_d = QLabel('D')
l_e = QLabel('E')
l_f = QLabel('F')
l_g = QLabel('G')
l_h = QLabel('H')
l_i = QLabel('I')
split_H = QSplitter(Qt.Horizontal)
split_H.addWidget(l_a)
split_H.addWidget(l_b)
split_H.addWidget(l_c)
self.split_V.addWidget(split_H)
split_H = QSplitter(Qt.Horizontal)
split_H.addWidget(l_d)
split_H.addWidget(l_e)
split_H.addWidget(l_f)
self.split_V.addWidget(split_H)
split_H = QSplitter(Qt.Horizontal)
split_H.addWidget(l_g)
split_H.addWidget(l_h)
split_H.addWidget(l_i)
self.split_V.addWidget(split_H)
self.globallayout.addWidget(self.split_V)
self.wid.setLayout(self.globallayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
Update
I think I almost found a solution where a function is used so that whenever the vertical splits are changed, it re-aligns them:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.setFixedWidth(300)
self.setFixedHeight(100)
self.wid = QWidget()
self.setCentralWidget(self.wid)
# self.grid = QGridLayout()
self.globallayout = QVBoxLayout()
self.split_V = QSplitter(Qt.Vertical)
l_a = QLabel('A')
l_b = QLabel('B')
l_c = QLabel('C')
l_d = QLabel('D')
l_e = QLabel('E')
l_f = QLabel('F')
l_g = QLabel('G')
l_h = QLabel('H')
l_i = QLabel('I')
self.split_H1 = QSplitter(Qt.Horizontal)
self.split_H1.addWidget(l_a)
self.split_H1.addWidget(l_b)
self.split_H1.addWidget(l_c)
self.split_V.addWidget(self.split_H1)
self.split_H2 = QSplitter(Qt.Horizontal)
self.split_H2.addWidget(l_d)
self.split_H2.addWidget(l_e)
self.split_H2.addWidget(l_f)
self.split_V.addWidget(self.split_H2)
self.split_H3 = QSplitter(Qt.Horizontal)
self.split_H3.addWidget(l_g)
self.split_H3.addWidget(l_h)
self.split_H3.addWidget(l_i)
self.split_V.addWidget(self.split_H3)
self.globallayout.addWidget(self.split_V)
self.wid.setLayout(self.globallayout)
self.split_H1.splitterMoved.connect(self.moveSplitter)
self.split_H2.splitterMoved.connect(self.moveSplitter)
self.split_H3.splitterMoved.connect(self.moveSplitter)
# self.split_H1.splitterMoved
# self.moveSplitter(0,self.split_H1.at )
def moveSplitter( self, index, pos ):
# splt = self._spltA if self.sender() == self._spltB else self._spltB
self.split_H1.blockSignals(True)
self.split_H2.blockSignals(True)
self.split_H3.blockSignals(True)
self.split_H1.moveSplitter(index, pos)
self.split_H2.moveSplitter(index, pos)
self.split_H3.moveSplitter(index, pos)
self.split_H1.blockSignals(False)
self.split_H2.blockSignals(False)
self.split_H3.blockSignals(False)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
However, I still have an issue at the begining - the alignment is not correct :
I don't know How call the function moveSplitter in the __init__
It seems that directly calling moveSplitter (which is a protected method) may be problematic. Using Qt-5.10.1 with PyQt-5.10.1 on Linux, I found that it can often result in a core dump when called during __init__. There is probably a good reason why Qt provides setSizes as a public method for changing the position of the splitters, so it may be wise to prefer it over moveSplitter.
With that in mind, I arrived at the following implementation:
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
...
self.split_H1.splitterMoved.connect(self.moveSplitter)
self.split_H2.splitterMoved.connect(self.moveSplitter)
self.split_H3.splitterMoved.connect(self.moveSplitter)
QTimer.singleShot(0, lambda: self.split_H1.splitterMoved.emit(0, 0))
def moveSplitter(self, index, pos):
sizes = self.sender().sizes()
for index in range(self.split_V.count()):
self.split_V.widget(index).setSizes(sizes)
The single-shot timer is needed because on some platforms the geometry of the window may not be fully initialized before it is shown on screen. And note that setSizes does not trigger splitterMoved, so there is no need to block signals when using it.

Showing Consecutive images on a QLabel PyQt4

I'm trying to show consecutive images on a QLabel
the images are numbered from 0000000 to 0000199
the problem is that the num variable prints empty string
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, time
class Animation(QMainWindow):
def __init__(self, parent=None):
super(Animation, self).__init__(parent)
self.resize(QSize(720, 480))
self.imageViewer = QLabel(self)
self.setCentralWidget(self.imageViewer)
startBtn = QPushButton("start", self)
startBtn.clicked.connect(self.start)
self.statusBar().addWidget(startBtn)
def start(self):
i = 0
while 1:
num = ("0" * (len(str(i)) - 7)) + str(i)
name = "frame" + num + ".png"
print ("0" * (len(str(i)) - 7))
self.imageViewer.setPixmap(QPixmap(name))
if i == 199:
break
i += 1
time.sleep(1)
app = QApplication(sys.argv)
test = Animation()
test.show()
app.exec_()
Please help
len(str(i)) - 7 returns a negative number. You need to swap it around:
num = '0' * (7-len(str(i))) + str(i)

Categories