I have the below piece of code which gives the below picture.
import os
import numpy as np
from PyQt5 import QtCore, QtWidgets
import sqlite3
class Ui_Form():
def __init__(self):
#Checking if the loading database is in place
if not os.path.exists("loading_database.db"):
QtWidgets.QMessageBox.information(None,'Loading database missing','Loading database has not been found. Creation of a new one will be attempted')
self.loadingDatabaseCreator()
QtWidgets.QMessageBox.information(None,'Successful','Loading database succesfully created')
#Asking the user for the input file to be parsed and the number of componenets determined
filePath, _ = QtWidgets.QFileDialog.getOpenFileName(None, "Select input model","","Input deck (*.inp)","*.inp")
filePath = str(filePath)
self.pleaseWait = waitWindow()
self.pleaseWait.show()
#If no file has been inputted the script will exit
if not filePath:
exit()
else:
#If a file has been inputted now it will be opened and a list containing all the lines will be created
readInputFile(filePath)
#Searching in the file for all the valid components. We disregards collectors containing RBE3 elements
#as they don't require fatigue analysis
self.pleaseWait.close()
for line in model_file:
if "*ELEMENT," in line and "DCOUP3D" not in line:
#If a valid collector is found it will be added to the array of type numpy.
try:
#Checks if the collector has already been recorded as different element types partaining of the same component
#will be specified in different collectors win the input deck
if not line.split("ELSET=")[1][:-1] in self.collector_array:
self.collector_array = np.concatenate((self.collector_array,np.array([line.split("ELSET=")[1][:-1]])),axis=0)
except:
self.collector_array = np.array([line.split("ELSET=")[1][:-1]])
#model_file_obj.close
#Testing to see if the array has been created indicating the presence of at least one entity
#This will be useful if the user loads a load deck instead of a model as they have the same .inp extension
try:
self.collector_array
except:
QtWidgets.QMessageBox.information(None,'Error','File contains no element collectors')
#Creating the initial Window
self.mainWidget = QtWidgets.QWidget()
self.mainWidget.resize(500, 500)
self.mainWidget.setWindowFlags(self.mainWidget.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint)
self.mainWidget.setWindowTitle("nCode analysis set-up")
#Creating the top level grid layout
self.mainGrid = QtWidgets.QGridLayout(self.mainWidget)
#Creating the boxes which will describe the analysis to be written in the .dcl file
self.analysis_type_label = QtWidgets.QLabel(self.mainWidget)
self.analysis_type_label.setText("Type of analysis")
self.mainGrid.addWidget(self.analysis_type_label,0,0)
self.analysis_type_combo = QtWidgets.QComboBox(self.mainWidget)
self.analysis_type_combo.addItems(["Fatigue","Proof plus fatigue"])
self.mainGrid.addWidget(self.analysis_type_combo,0,1,1,2)
self.load_deck_type_label = QtWidgets.QLabel(self.mainWidget)
self.load_deck_type_label.setText("Type of fatigue deck")
self.mainGrid.addWidget(self.load_deck_type_label,1,0)
self.load_deck_type_combo = QtWidgets.QComboBox(self.mainWidget)
self.load_deck_type_combo.addItems(["Regen braking","No regen braking"])
self.mainGrid.addWidget(self.load_deck_type_combo,1,1,1,2)
self.analysis_engine_type_label = QtWidgets.QLabel(self.mainWidget)
self.analysis_engine_type_label.setText("Analysis Engine")
self.mainGrid.addWidget(self.analysis_engine_type_label,2,0)
self.analysis_engine_type_combo = QtWidgets.QComboBox(self.mainWidget)
self.analysis_engine_type_combo.addItems(["EN analysis","SN analysis"])
self.mainGrid.addWidget(self.analysis_engine_type_combo,2,1,1,2)
#Creating a scrolable area to accommodate for a large number of components with possible lenghty names
self.scrollArea = QtWidgets.QScrollArea(self.mainWidget)
#The line below is absolutely required to make the scrollable area work.
self.scrollArea.setWidgetResizable(True)
self.mainGrid.addWidget(self.scrollArea,3,0,1,3)
self.secondaryWidget = QtWidgets.QWidget()
self.scrollArea.setWidget(self.secondaryWidget)
self.secondaryGrid = QtWidgets.QGridLayout(self.secondaryWidget)
#This bit creates the necessary object for every componenet that was found in the input deck.
#The globals method is used to dynamically assign objects to variables for subsequent manipulation.
for i in range(0, self.collector_array.shape[0]):
globals()["self.materialLabel"+str(i)] = QtWidgets.QLabel(self.secondaryWidget)
globals()["self.materialLabel"+str(i)].setText(self.collector_array[i]+" material")
self.secondaryGrid.addWidget(globals()["self.materialLabel"+str(i)],2+i,0)
globals()["self.materialName"+str(i)] = QtWidgets.QLineEdit(self.secondaryWidget)
globals()["self.materialName"+str(i)].setPlaceholderText("Drop material name here")
globals()["self.materialName"+str(i)].setFixedWidth(150)
self.secondaryGrid.addWidget(globals()["self.materialName"+str(i)],2+i,1)
globals()["self.materialPickingButton"+str(i)] = QtWidgets.QPushButton(self.secondaryWidget)
globals()["self.materialPickingButton"+str(i)].setText("Pick material")
globals()["self.materialPickingButton"+str(i)].clicked.connect(self.material_lookup)
self.secondaryGrid.addWidget(globals()["self.materialPickingButton"+str(i)],2+i,2)
#Creates the button that connects to the DLC_writer function
self.createDCL = QtWidgets.QPushButton(self.mainWidget)
self.createDCL.setText("Create DCL")
self.mainGrid.addWidget(self.createDCL,4,0,1,3)
self.createDCL.clicked.connect(self.DCL_guide)
self.mainWidget.show()
class waitWindow(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Info")
self.resize(600,200)
self.VLayout = QtWidgets.QVBoxLayout(self)
self.message = QtWidgets.QLabel(self)
self.message.setFixedWidth(550)
self.message.setText("Please wait while input file is being read")
self.VLayout.addWidget(self.message)
class readInputFile():
def __init__(self,filePath):
model_file_obj = open(filePath, "r")
globals()['model_file'] = model_file_obj.readlines()
model_file_obj.close
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = Ui_Form()
sys.exit(app.exec_())
The problem is my text label is missing from this window. I made it so big in case the label did not have enough space to fully display but in that case I think it would have displayed what it had space for. Hopefully someone knows why.
Edit: I have included the entire init function of Ui_Form. All my problems are caused in this bit the rest working ok.
The window you are viewing is not pleaseWait window but the mainWidget window.
The above is explained assuming that:
The file that is read is small, so the pleaseWait window will open and close instantly, so that being that synchronous action Qt will not have time to do it and for the user the window will never have been shown. For this case the solution is to give a reasonable time for the user to see the window.
The file is very large, the reading will take a long time blocking the eventloop, which will cause tasks such as displaying a window to not be performed, to avoid blocking the task must be executed in another thread.
Combining both solutions we obtain the following code:
import os
from functools import partial
from PyQt5 import QtCore, QtWidgets
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
contentChanged = QtCore.pyqtSignal(list)
#QtCore.pyqtSlot(str)
def read_file(self, fileName):
with open(fileName, "r") as model_file_obj:
model_file = model_file_obj.readlines()
print(model_file)
self.contentChanged.emit(model_file)
self.finished.emit()
class MainWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(500, 500)
self.setWindowFlags(
self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint
)
self.setWindowTitle("nCode analysis set-up")
mainGrid = QtWidgets.QGridLayout(self)
thread = QtCore.QThread(self)
thread.start()
self.m_worker = Worker()
self.m_worker.moveToThread(thread)
self.m_worker.contentChanged.connect(self.get_content)
def launch_task(self):
if not os.path.exists("loading_database.db"):
QtWidgets.QMessageBox.information(
None,
"Loading database missing",
"Loading database has not been found. Creation of a new one will be attempted",
)
# self.loadingDatabaseCreator()
QtWidgets.QMessageBox.information(
None, "Successful", "Loading database succesfully created"
)
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
None, "Select input model", "", "Input deck (*.inp)", "*.inp"
)
self.pleaseWait = WaitWindow()
self.pleaseWait.show()
self.m_worker.finished.connect(self.pleaseWait.close)
wrapper = partial(self.m_worker.read_file, fileName)
# Launch the task in a reasonable time for the window to show
QtCore.QTimer.singleShot(100, wrapper) #
#QtCore.pyqtSlot(list)
def get_content(self, lines):
print(lines)
class WaitWindow(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Info")
self.resize(600, 200)
layout = QtWidgets.QVBoxLayout(self)
self.message = QtWidgets.QLabel(self)
self.message.setFixedWidth(550)
self.message.setText("Please wait while input file is being read")
layout.addWidget(self.message)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWidget()
w.show()
w.launch_task()
sys.exit(app.exec_())
Update:
import os
from functools import partial
import numpy as np
from PyQt5 import QtCore, QtWidgets
class MainWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(500, 500)
self.setWindowFlags(
self.windowFlags() | QtCore.Qt.MSWindowsFixedSizeDialogHint
)
self.setWindowTitle("nCode analysis set-up")
self.wait_window = WaitWindow()
thread = QtCore.QThread(self)
thread.start()
self.m_worker = Worker()
self.m_worker.moveToThread(thread)
self.m_worker.new_content_signal.connect(self.get_content)
# Creating the top level grid layout
mainGrid = QtWidgets.QGridLayout(self)
self.analysis_type_label = QtWidgets.QLabel(self)
self.analysis_type_label.setText("Type of analysis")
mainGrid.addWidget(self.analysis_type_label, 0, 0)
self.analysis_type_combo = QtWidgets.QComboBox(self)
self.analysis_type_combo.addItems(["Fatigue", "Proof plus fatigue"])
mainGrid.addWidget(self.analysis_type_combo, 0, 1, 1, 2)
self.load_deck_type_label = QtWidgets.QLabel(self)
self.load_deck_type_label.setText("Type of fatigue deck")
mainGrid.addWidget(self.load_deck_type_label, 1, 0)
self.load_deck_type_combo = QtWidgets.QComboBox(self)
self.load_deck_type_combo.addItems(
["Regen braking", "No regen braking"]
)
mainGrid.addWidget(self.load_deck_type_combo, 1, 1, 1, 2)
self.analysis_engine_type_label = QtWidgets.QLabel(self)
self.analysis_engine_type_label.setText("Analysis Engine")
mainGrid.addWidget(self.analysis_engine_type_label, 2, 0)
self.analysis_engine_type_combo = QtWidgets.QComboBox(self)
self.analysis_engine_type_combo.addItems(["EN analysis", "SN analysis"])
mainGrid.addWidget(self.analysis_engine_type_combo, 2, 1, 1, 2)
# Creating a scrolable area to accommodate for a large number of components with possible lenghty names
self.scrollArea = QtWidgets.QScrollArea(self)
# The line below is absolutely required to make the scrollable area work.
self.scrollArea.setWidgetResizable(True)
mainGrid.addWidget(self.scrollArea, 3, 0, 1, 3)
self.secondaryWidget = QtWidgets.QWidget()
self.scrollArea.setWidget(self.secondaryWidget)
self.secondaryGrid = QtWidgets.QGridLayout(self.secondaryWidget)
self.createDCL = QtWidgets.QPushButton(self)
self.createDCL.setText("Create DCL")
mainGrid.addWidget(self.createDCL, 4, 0, 1, 3)
def start_task(self):
if not os.path.exists("loading_database.db"):
QtWidgets.QMessageBox.information(
None,
"Loading database missing",
"Loading database has not been found. Creation of a new one will be attempted",
)
# self.loadingDatabaseCreator()
QtWidgets.QMessageBox.information(
None, "Successful", "Loading database succesfully created"
)
filePath, _ = QtWidgets.QFileDialog.getOpenFileName(
None, "Select input model", "", "Input deck (*.inp)", "*.inp"
)
if filePath:
self.wait_window.show()
self.m_worker.finished.connect(self.wait_window.close)
wrapper = partial(self.m_worker.read_file, filePath)
# Launch the task in a reasonable time for the window to show
QtCore.QTimer.singleShot(100, wrapper)
#QtCore.pyqtSlot(int, str)
def get_content(self, i, content):
label = QtWidgets.QLabel("{} material".format(content))
linedit = QtWidgets.QLineEdit(placeholderText="Drop material name here")
linedit.setFixedWidth(150)
button = QtWidgets.QPushButton("Pick material")
self.secondaryGrid.addWidget(label, 2 + i, 0)
self.secondaryGrid.addWidget(linedit, 2 + i, 1)
self.secondaryGrid.addWidget(button, 2 + i, 2)
class WaitWindow(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Info")
self.resize(600, 200)
layout = QtWidgets.QVBoxLayout(self)
self.message = QtWidgets.QLabel()
self.message.setFixedWidth(550)
self.message.setText("Please wait while input file is being read")
layout.addWidget(self.message)
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
new_content_signal = QtCore.pyqtSignal(int, str)
#QtCore.pyqtSlot(str)
def read_file(self, fileName):
i = 0
collector_array = []
with open(fileName, "r") as model_file_obj:
for line in model_file_obj.readlines():
if "*ELEMENT," in line and "DCOUP3D" not in line:
t = line.split("ELSET=")[1][:-1]
if t not in collector_array:
self.new_content_signal.emit(i, t)
QtCore.QThread.msleep(10)
collector_array.append(t)
i += 1
self.finished.emit()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWidget()
w.show()
w.start_task()
sys.exit(app.exec_())
This code works perfectly for me:
import sys
from PyQt5 import QtWidgets
from PyQt5.Qt import QApplication
class waitWindow(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Info")
self.resize(600,200)
self.VLayout = QtWidgets.QVBoxLayout(self)
self.message = QtWidgets.QLabel(self)
self.message.setFixedWidth(550)
self.message.setText("Please wait while input file is being read")
self.VLayout.addWidget(self.message)
self.show()
def closeWindow(self):
self.close()
app = QApplication(sys.argv)
w = waitWindow()
w.exec_()
Okay took your code and gutted all the unessential stuff to analyze the actual issue I have included the gutted version so you can see what is necessary in the future to see what is wrong.
In short the issue is that you need to add the following line as the last line in your waitWindow function >> self.exec() by adding that I got your label to display (see code below)
Now with that said you do have another issue and that is the QDialog box will not allow the program to continue until it has been closed (aka it stops the process flow until you release it by closing the window or use a different means). My question is why not use your "main window" to display that message then repopulate with the rest of that data. Also curious why are you not using QMainWindow?
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Ui_Form():
def __init__(self):
self.pleaseWait = waitWindow()
self.pleaseWait.show()
self.pleaseWait.close()
sys.exit()
class waitWindow(QDialog):
def __init__(self):
super(waitWindow, self).__init__()
self.setWindowTitle("Info")
self.resize(600,200)
self.message = QLabel(self)
self.message.setFixedWidth(550)
self.message.setText("Please wait while input file is being read")
self.VLayout = QVBoxLayout(self)
self.VLayout.addWidget(self.message)
self.setLayout(self.VLayout)
self.exec_()
if __name__ == "__main__":
app = QApplication(sys.argv)
ui = Ui_Form()
sys.exit(app.exec_())
Related
So I'm creating an image gallery browser in PYQT5.
I can choose a directory and load the image file icons in a scrollable widget like so:
If there is less than 20 or so images in the directory, it works fine. However when there's more than that the image labels for some reason don't show:
If I take a few of those images that aren't showing and put them into a new folder on their own, then try to load only those images, it works, only if the application hasn't already tried to load them before hand and failed, otherwise, the empty square happens again.
So this seems to me to be some sort of framework/memory limitation? Can anyone shed some light on this?
Here is my code:
# MainWindow Set-Up : Creating Widgets and Layouts
class ClassUi(object):
def setup(self, MainW):
MainW.setObjectName("MainW")
MainW.resize(400,500)
self.mainlayout = QtWidgets.QVBoxLayout()
self.centralwidget = QtWidgets.QWidget(MainW)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
self.centralwidget.setSizePolicy(sizePolicy)
self.centralwidget.setLayout(self.mainlayout)
MainW.setCentralWidget(self.centralwidget)
#direcwidget is a container widget for the top part of the program where a directory is chosen
self.direcwidget = QtWidgets.QWidget()
self.direclayout = QtWidgets.QGridLayout()
self.direcwidget.setLayout(self.direclayout)
self.mainlayout.addWidget(self.direcwidget)
self.opdirbut = QtWidgets.QPushButton() #Button that opens directory dialog when pressed
self.opdirbut.setText("Choose")
self.opdirbut.setFixedSize(50,50)
self.linepath = QtWidgets.QLineEdit() #Line Edit that displays current directory
self.linepath.setText(os.getcwd())
self.backpath = QtWidgets.QPushButton() #Button that changes directory to parent directory of the current one
self.backpath.setFixedSize(20,20)
self.backpath.setText("^")
#Positioning of widgets inside widget container direcwidget
self.direclayout.addWidget(self.opdirbut, 0,0, 2, 1)
self.direclayout.addWidget(self.linepath, 0,2, 1, 3)
self.direclayout.addWidget(self.backpath, 1,4, 1, 1)
#Scrollwidget is the area wherein a container widget widgetforscroll holds all image icons in a grid
self.scrollwidget = QtWidgets.QScrollArea()
self.mainlayout.addWidget(self.scrollwidget)
self.scrollwidget.setWidgetResizable(True)
self.scrollwidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scrollgrid = QtWidgets.QGridLayout()
self.widgetforscroll = QtWidgets.QWidget()
self.widgetforscroll.setLayout(self.scrollgrid)
self.scrollwidget.setWidget(self.widgetforscroll)
#Contains logic of program
class MainWindow(QtWidgets.QMainWindow, ClassUi):
def __init__(self):
super().__init__()
self.setup(self)
#Counter variables for keeping track of where to layout items
self.picturerow = 0
self.picturecolumn = 0
self.howmany = 0
#Assigns class methods to directory buttons
self.opdirbut.clicked.connect(self.opdial)
self.backpath.clicked.connect(self.uppath)
# Each time this function is called, a new widget called newwidget is created containing "pic" in a pixmap label and a text label and layed out
# on the widgetforscroll widget through its scrollgrid layout. Each time the function is called, picture column increments by one at the end of the function
# when all the columns in a row are filled, picture column is reset to 0 and and picture row is incremented. Picture row and picture column are used in positioning
# the newwidgets in the scrollgrid.
def addpicture(self, pic):
if self.picturecolumn == 3:
self.picturecolumn = 0
self.picturerow += 1
self.howmany += 1
#newwidget is object of picwidg class containing pixmap and text label
newwidget = picwidg(self.howmany, pic)
#This function was not required to be created, it was only created for the purpose of the Qtimer singleshot implementation.
#The newwidget is being positioned on the scrollgrid layout here.
def addnewone(lyout,nw,rw,cl):
lyout.addWidget(nw, rw, cl)
QtCore.QTimer.singleShot(
self.howmany*500,
lambda sc=self.scrollgrid, nr = newwidget, ow = self.picturerow, mn=self.picturecolumn : addnewone(sc,nr,ow,mn)
)
#Incrementing column by 1 for the next time function is called
self.picturecolumn += 1
#This is the function connected to the choose dialog button. It opens a QFileDialog window which allows you to only choose a directory folder.
#When the folder is chosen:
# 1: The linepath text is set the to the new directory
# 2: Any previous picwidg objects are cleared from the scrollgrid layout
# 3: Picture column and picture row variables are reset for positioning
# 4: A for loop scans the new directory for files with .jpg or .png extensions
# 5: The addpicture method is called with the filename as the argument
def opdial(self):
dialogbox = dialog()
try:
os.chdir(dialogbox.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog))
self.linepath.setText(os.getcwd())
for i in reversed(range(self.scrollgrid.count())):
widgetToRemove = self.scrollgrid.itemAt(i).widget()
# remove it from the layout list
self.scrollgrid.removeWidget(widgetToRemove)
# remove it from the gui
widgetToRemove.setParent(None)
self.picturecolumn =0
self.picturerow =0
self.howmany = 0
for a, b, c in os.walk(os.getcwd()):
for i in c:
if i[-4:].lower() == ".png" or i[-4:].lower() == ".jpg":
self.addpicture(i)
except:
pass
#This is the function for reaching the parent directory. It works very similar to the above function, the only difference
#being that instead of grabbing a new directory from a QFileDialog, the directory processed is taken from the current linepath text
#and goes to the parent directory instead, then removes widgets from the scrolllayout and adds new pictures to the scrolllayout
def uppath(self):
newpath = os.path.dirname(self.linepath.text())
os.chdir(newpath)
self.linepath.setText(newpath)
for i in reversed(range(self.scrollgrid.count())):
widgetToRemove = self.scrollgrid.itemAt(i).widget()
# remove it from the layout list
self.scrollgrid.removeWidget(widgetToRemove)
# remove it from the gui
widgetToRemove.setParent(None)
self.picturecolumn = 0
self.picturerow = 0
self.howmany = 0
for a, b, c in os.walk(os.getcwd()):
for i in c:
# print(i[-4:].lower())
if i[-4:].lower() == ".png" or i[-4:].lower() == ".jpg":
self.addpicture(i)
# This is the class where newwidget instances are created
# Here 2 labels are created, one for the image, one for the text and packed in a vertical layout
class picwidg(QtWidgets.QWidget):
whoshover = None
picwidglist =[]
def __init__(self, numb, pic):
super().__init__()
self.setMouseTracking(True)
self.numb = numb
self.pic = pic
picwidg.picwidglist.append(self)
SizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
newwidgetlayout = QtWidgets.QVBoxLayout()
self.setLayout(newwidgetlayout)
self.setSizePolicy(SizePolicy)
self.setMinimumSize(QtCore.QSize(115, 140))
self.setMaximumSize(QtCore.QSize(115, 140))
#Pic Label
self.newpic = QtWidgets.QLabel()
QtCore.QTimer.singleShot(self.numb*500, self.addingnewpic)
self.newpic.setScaledContents(True)
self.newpic.setSizePolicy(SizePolicy)
self.newpic.setGeometry(0, 0, 100, 100)
self.newpic.setStyleSheet("border:1px solid gray")
#Picture text label
self.newtext = QtWidgets.QLabel()
font_metrics = QtGui.QFontMetrics(self.font())
self.newtext.setAlignment(QtCore.Qt.AlignCenter)
elided_text = font_metrics.elidedText(pic, QtCore.Qt.ElideRight, 100)
self.newtext.setText(elided_text)
newwidgetlayout.addWidget(self.newpic)
newwidgetlayout.addWidget(self.newtext)
def addingnewpic(self):
self.newpic.setPixmap(QtGui.QPixmap(self.pic))
#Class for QFileDialog for selecting only directories
class dialog(QtWidgets.QFileDialog):
def __init__(self):
super().__init__()
self.setFileMode(QtWidgets.QFileDialog.DirectoryOnly)
self.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
self.setOption(QtWidgets.QFileDialog.ShowDirsOnly, False)
if __name__ == "__main__" :
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
If you want to show a lot of QPixmap then it is not optimal to use a lot of QLabel, in that case it is better to use a QListView (or QListWidget) since it handles memory better.
In your code you add and remove QLabels but in the case of the model only items are added or removed and the view is repainted avoiding the excessive use of memory.
Considering the above I have implemented the following solution:
import os
from PyQt5 import QtCore, QtGui, QtWidgets
ICON_SIZE = 100
class StyledItemDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
option.text = option.fontMetrics.elidedText(
index.data(), QtCore.Qt.ElideRight, ICON_SIZE
)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.choose_btn = QtWidgets.QPushButton(
self.tr("Choose"), clicked=self.on_choose_btn_clicked
)
self.choose_btn.setFixedSize(50, 50)
self.path_le = QtWidgets.QLineEdit()
self.back_btn = QtWidgets.QPushButton(
self.tr("^"), clicked=self.on_back_btn_clicked
)
self.back_btn.setFixedSize(20, 20)
self.pixmap_lw = QtWidgets.QListWidget(
viewMode=QtWidgets.QListView.IconMode,
iconSize=ICON_SIZE * QtCore.QSize(1, 1),
movement=QtWidgets.QListView.Static,
resizeMode=QtWidgets.QListView.Adjust,
)
delegate = StyledItemDelegate(self.pixmap_lw)
self.pixmap_lw.setItemDelegate(delegate)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
grid_layout = QtWidgets.QGridLayout(central_widget)
grid_layout.addWidget(self.choose_btn, 0, 0, 2, 1)
grid_layout.addWidget(self.path_le, 0, 1)
grid_layout.addWidget(self.back_btn, 1, 1, alignment=QtCore.Qt.AlignRight)
grid_layout.addWidget(self.pixmap_lw, 2, 0, 1, 2)
self.resize(640, 480)
self.timer_loading = QtCore.QTimer(interval=50, timeout=self.load_image)
self.filenames_iterator = None
#QtCore.pyqtSlot()
def on_choose_btn_clicked(self):
directory = QtWidgets.QFileDialog.getExistingDirectory(
options=QtWidgets.QFileDialog.DontUseNativeDialog
)
if directory:
self.start_loading(directory)
#QtCore.pyqtSlot()
def on_back_btn_clicked(self):
directory = os.path.dirname(self.path_le.text())
self.start_loading(directory)
def start_loading(self, directory):
if self.timer_loading.isActive():
self.timer_loading.stop()
self.path_le.setText(directory)
self.filenames_iterator = self.load_images(directory)
self.pixmap_lw.clear()
self.timer_loading.start()
#QtCore.pyqtSlot()
def load_image(self):
try:
filename = next(self.filenames_iterator)
except StopIteration:
self.timer_loading.stop()
else:
name = os.path.basename(filename)
it = QtWidgets.QListWidgetItem(name)
it.setIcon(QtGui.QIcon(filename))
self.pixmap_lw.addItem(it)
def load_images(self, directory):
it = QtCore.QDirIterator(
directory,
["*.jpg", "*.png"],
QtCore.QDir.Files,
QtCore.QDirIterator.Subdirectories,
)
while it.hasNext():
filename = it.next()
yield filename
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I have a mainwindow which has a button which opens a second window when clicked. This second window contains a mayavi plot. Every time I click this button an error is shown
QCoreApplication::exec: The event loop is already running
I have looked at multiple similar questions on SO, but I cannot fix my problem.
The code of the mainwindow maingui.py:
# Standard library imports
import re
import sys
import string
import inspect
import platform
import importlib
import ctypes
from os.path import abspath, join, dirname, realpath
from functools import wraps
import inspect
import matplotlib as mpl
# Local imports
from loadhistory.stresscorrection import DnvThicknessStressCorrection
# PyQt5 imports
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QListView, QWidget, QGridLayout
from PyQt5.QtGui import QPixmap
# Gui package import
import gui.design
import gui.resources
from gui.inputs import Inputs
from gui.filemanager import Filemanager
from gui.datawrapper import RunThread
from gui.mayaviwindow import MayaviWindow
class MainWindow(QMainWindow, gui.design.Ui_MainWindow):
'''main window of the gui'''
def __init__(self):
QMainWindow.__init__(self)
super().__init__()
# Bypass needed to set taskbar icon if on windows
if "Windows" in platform.system():
myappid = 'LaboSoete'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
self.inputs = Inputs(self)
self.filemanager = Filemanager(self)
self.mayaviwindow = MayaviWindow()
self.setupUi(self)
self.setWindowTitle("Endurance framework SafeLife")
# Apply css stylesheet
sshFile = "gui/stylesheet.css"
with open(sshFile, "r") as fh:
self.setStyleSheet(fh.read())
# Initialize combobox design codes
self.designcodes = self.inputs.code_dict()
codes = list(self.designcodes.keys())
self.design_code_combo.addItems(codes)
self.design_cat_combo.addItems(self.designcodes[codes[0]])
self.scrollbar_QComboBox(self.design_code_combo)
self.scrollbar_QComboBox(self.design_cat_combo)
# Initialize combobox Damagemodels
module = importlib.import_module('damagemodels.damagemodels')
for m in module.__dict__.values():
if inspect.isclass(m):
if 'damagemodels.damagemodels.' in str(m):
if not inspect.isabstract(m):
self.dmgBox.addItem(m.__name__)
###### Menubar ######
self.actionSave.triggered.connect(self.filemanager.save)
self.actionSave_as.triggered.connect(self.filemanager.save_as)
self.actionLoad.triggered.connect(self.filemanager.load)
self.actionOutput.triggered.connect(self.filemanager.change_output_dir)
self.actionQuit.triggered.connect(self.close)
###### Button handeling ######
self.loadhistory_btn.clicked.connect(self.inputs.lh_browse_file)
self.odb_btn.clicked.connect(self.inputs.odb_browse_file)
self.design_code_combo.currentIndexChanged.connect(
self.inputs.design_combo_currentIndexChanged)
self.btn_snCustom.clicked.connect(self.inputs.sn_browse_file)
self.run_btn.clicked.connect(self.run_analysis)
self.newBtn.clicked.connect(self.filemanager.new)
self.abaqus_results_btn.clicked.connect(self.filemanager.open_odb_results)
self.damageNnumber.textChanged.connect(lambda: self.check_numeric_entry(self.damageNnumber))
self.hss_visualization_btn.clicked.connect(self.mayaviwindow.open_new_window)
# DNV thickness correction initialization
self.thicknessFrame.hide()
self.design_cat_combo.currentIndexChanged.connect(self.show_thickness_correction)
self.loadhistory_txt.textChanged.connect(self.loadhistory_txt_changed)
self.radioBtn_snBasquin.toggled.connect(self.show_thickness_correction)
self.radioBtn_snCustom.toggled.connect(self.show_thickness_correction)
self.radioBtn_snDesign.toggled.connect(self.show_thickness_correction)
self.radioBtn_snLog.toggled.connect(self.show_thickness_correction)
###### Tab/plot handeling ######
# Loadhistory plot
self.tabWidget.setCurrentIndex(0)
self.loadhistory_txt.textChanged.connect(lambda: self.LhWidget.run(self))
self.thicknessCorrCheckbox.toggled.connect(lambda: self.LhWidget.run(self))
self.t_txt.textChanged.connect(lambda: self.LhWidget.run(self))
self.tref_txt.textChanged.connect(lambda: self.LhWidget.run(self))
self.k_txt.textChanged.connect(lambda: self.LhWidget.run(self))
self.mscCheckBox.toggled.connect(lambda: self.LhWidget.run(self))
self.mscSelectionBox.currentIndexChanged.connect(lambda: self.LhWidget.run(self))
# Sncurve plot
self.design_cat_combo.currentIndexChanged.connect(self.inputs.design_cat_combo_change)
self.design_code_combo.currentIndexChanged.connect(self.inputs.design_combo_currentIndexChanged)
self.sncurve_txt.textChanged.connect(lambda: self.SnWidget.run(self))
self.radioBtn_snCustom.clicked.connect(lambda: self.SnWidget.run(self))
self.radioBtn_snDesign.clicked.connect(lambda: self.SnWidget.run(self))
self.radioBtn_snBasquin.clicked.connect(self.clear_sntxt_fields)
self.radioBtn_snLog.clicked.connect(self.clear_sntxt_fields)
self.Nf_txt.textChanged.connect(lambda: self.SnWidget.run(self))
self.uts_txt.textChanged.connect(lambda: self.SnWidget.run(self))
self.m1_txt.textChanged.connect(lambda: self.SnWidget.run(self))
self.B1_txt.textChanged.connect(lambda: self.SnWidget.run(self))
self.m2_txt.textChanged.connect(lambda: self.SnWidget.run(self))
self.B2_txt.textChanged.connect(lambda: self.SnWidget.run(self))
# HSS handeling
self.hss_checkbox.toggled.connect(self.show_hss_image)
self.hss_combobox.currentIndexChanged.connect(self.show_hss_image)
self.hss_combobox.currentIndexChanged.connect(self.show_hide_hss_spinbox)
###### Output related ######
self.hss_visualization_btn.hide()
self.hideTab(3)
self.abaqus_results_btn.hide()
self.newBtn.hide()
self.progressBar.hide()
#staticmethod
def eval_eq(equation):
'''
Check if the string 'equation' only contains allowable characters
'''
regex = re.compile(r'(\d+([.]|\*))?\d+(e|[.]|\*)?\d*(e|[.]|\*)?\d*?$')
if (equation.endswith('e') or equation.endswith('.')) and re.match(regex, equation):
return eval(equation[:-1])
elif re.match(regex, equation) and equation.count('e') < 2:
return eval(equation)
else:
raise ValueError(
'Illegal expression used to define the S-N curve, only e^*'+string.digits+' allowed')
def check_numeric_entry(self, myQlineEdit):
'''
For QlineEdit which can only contain numbers, check whether
no non-numeric characters are entered
'''
if myQlineEdit.text().isdigit() or len(myQlineEdit.text()) < 1:
return
else:
reply = QMessageBox.warning(self, 'TypeError', 'This field only allows for numeric entries')
if reply == QMessageBox.Ok:
myQlineEdit.setText(myQlineEdit.text()[:-1])
return
def loadhistory_txt_changed(self):
'''
If a loadhistory is selected the meanstresscorrection option
becomes enabled
'''
if len(self.loadhistory_txt.text())>0:
self.mscCheckBox.setEnabled(True)
else:
self.mscCheckBox.setDisabled(True)
def show_thickness_correction(self):
'''
Determine whether or not the thickness_correction option should be shown and enabled.
This is only the case when a DNV SN curve is selected
'''
if 'DNV' in self.design_code_combo.currentText() and self.radioBtn_snDesign.isChecked() and len(self.loadhistory_txt.text())>0:
self.thicknessFrame.show()
self.thicknessCorrCheckbox.setEnabled(True)
else:
self.thicknessFrame.hide()
self.thicknessCorrCheckbox.setChecked(False)
self.thicknessCorrCheckbox.setDisabled(True)
def show_hss_image(self):
if self.hss_checkbox.isChecked():
filename = self.hss_combobox.currentText()
filepath = abspath(join(dirname(realpath(__file__)), 'gui', 'icons', filename))
pixmap = QPixmap(filepath)
self.hss_imagelabel.setPixmap(pixmap)
self.hss_imagelabel.show()
self.tabWidget.setCurrentIndex(2)
if not self.hss_checkbox.isChecked():
self.hss_imagelabel.hide()
def get_thickness_correction(self):
'''
Return a thickness correction object using the values from the
gui to initialize the object
'''
if "DNV" in self.design_code_combo.currentText():
tref = self.eval_eq(self.tref_txt.text())
t = self.eval_eq(self.t_txt.text())
k = self.eval_eq(self.k_txt.text())
if k >= 1:
reply = QMessageBox.warning(
self, 'ValueError', 'k needs to be less than one, please consult DNV-GL-RP203 for the correct values')
if reply == QMessageBox.Ok or reply == QMessageBox.Cancel:
self.k_txt.clear()
return
else: return
return DnvThicknessStressCorrection(tref, t, k)
def show_hide_hss_spinbox(self):
'''
hide the hss_spinbox and hss_t_label widgets if a hss method, that does not
use the plate thickness to determine the extrapolation coords, is selected
'''
test = self.hss_combobox.currentText()
lst = ['1', '2', '3']
if any(x in test for x in lst):
self.hss_spinbox.show()
self.hss_t_label.show()
else:
self.hss_t_label.hide()
self.hss_t_label.show()
def scrollbar_QComboBox(self, combo):
'''
Add a scrollbar to QComboBoxes if the selected text is wider than
the combobox
'''
view = QListView(combo)
combo.setView(view)
view.setTextElideMode(Qt.ElideNone)
view.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
def closeEvent(self, event):
'''
Handle the event of the user wanting to close the application
'''
msgBox = QMessageBox()
msgBox.setWindowTitle('Exit application?')
msgBox.setText('Do you want to exit the application?')
exit_btn = msgBox.addButton('Exit', QMessageBox.YesRole)
save_exit_btn = msgBox.addButton('Save and Exit', QMessageBox.YesRole)
cancel_btn = msgBox.addButton('Cancel', QMessageBox.RejectRole)
msgBox.setEscapeButton(cancel_btn)
msgBox.exec_()
if msgBox.clickedButton() == exit_btn:
event.accept()
elif msgBox.clickedButton() == save_exit_btn:
self.filemanager.save()
event.accept()
elif msgBox.clickedButton() == cancel_btn:
event.ignore()
def start_thread(self, thread):
'''Pass all MainWindow attributes to the new thread and start the thread'''
thread.progressBar = self.progressBar
thread.loadhistory_txt = self.loadhistory_txt
thread.loadhistory_btn = self.loadhistory_btn
thread.odb_txt = self.odb_txt
thread.odb_btn = self.odb_btn
thread.dmgBox = self.dmgBox
thread.dmax_box = self.dmax_box
thread.radioBtn_snCustom = self.radioBtn_snCustom
thread.radioBtn_snBasquin = self.radioBtn_snBasquin
thread.radioBtn_snDesign = self.radioBtn_snDesign
thread.sncurve_txt = self.sncurve_txt
thread.btn_snCustom = self.btn_snCustom
thread.design_cat_combo = self.design_cat_combo
thread.design_code_combo = self.design_code_combo
thread.mscCheckBox = self.mscCheckBox
thread.mscSelectionBox = self.mscSelectionBox
thread.calcLifetimeBox = self.calcLifetimeBox
thread.calcLifetimeCheckBox = self.calcLifetimeCheckBox
thread.damageNcheckBox = self.damageNcheckBox
thread.damageNnumber = self.damageNnumber
thread.newBtn = self.newBtn
thread.m1_txt = self.m1_txt
thread.B1_txt = self.B1_txt
thread.m2_txt = self.m2_txt
thread.B2_txt = self.B2_txt
thread.uts_txt = self.uts_txt
thread.Nf_txt = self.Nf_txt
thread.SnWidget = self.SnWidget
thread.LhWidget = self.LhWidget
thread.Inputs = self.inputs
thread.thicknessCorrCheckbox = self.thicknessCorrCheckbox
thread.k_txt = self.k_txt
thread.t_txt = self.t_txt
thread.tref_txt = self.tref_txt
thread.hss_spinbox = self.hss_spinbox
thread.hss_combobox = self.hss_combobox
thread.hss_checkbox = self.hss_combobox
thread.start()
def clear_sntxt_fields(self):
'''Clear all text fields related to SN curve equations'''
self.Nf_txt.clear()
self.uts_txt.clear()
self.m1_txt.clear()
self.m2_txt.clear()
self.B1_txt.clear()
self.B2_txt.clear()
def initialize(self):
self.hss_visualization_btn.hide()
self.run_btn.setDisabled(True)
self.newBtn.hide()
self.abaqus_results_btn.hide()
self.hideTab(3)
self.output_txt.clear()
def finished(self):
if self.hss_checkbox.isChecked():
self.hss_visualization_btn.show()
self.run_btn.setEnabled(True)
self.newBtn.show()
self.abaqus_results_btn.show()
self.hide_progressbar()
self.showTab(3)
def show_progressbar(self):
self.progressBar.show()
def hide_progressbar(self):
self.progressBar.hide()
def clear_lhtxt(self):
self.loadhistory_txt.clear()
self.loadhistory_txt.setFocus()
def clear_odbtxt(self):
self.odb_txt.clear()
self.odb_txt.setFocus()
def clear_sncustomtxt(self):
self.sncurve_txt.clear()
self.sncurve_txt.setFocus()
def show_messagebox(self, errortype, msg, methodname):
reply = QMessageBox.warning(self, errortype, msg)
if methodname is not "None":
handle_error = getattr(self, methodname)
if reply == QMessageBox.Ok:
if 'handle_error' in locals():
handle_error()
return
def dmg_curve_plot(self, Dplot, nNplot):
self.DmgWidget.run(self, Dplot, nNplot)
def hideTab(self, tabIndex):
self.tabWidget.setTabEnabled(tabIndex, False)
def showTab(self, tabIndex):
self.tabWidget.setTabEnabled(tabIndex, True)
def check_required_inputs(self):
if "".__eq__(self.loadhistory_txt.text()) or "".__eq__(self.odb_txt.text()) \
or not self.odb_txt.text().endswith('odb'):
return False
if self.radioBtn_snCustom.isChecked() and "".__eq__(self.sncurve_txt.text()):
return False
if self.radioBtn_snBasquin.isChecked() or self.radioBtn_snLog.isChecked():
if ("".__eq__(self.m1_txt.text()) or "".__eq__(self.Nf_txt.text()) or \
"".__eq__(self.uts_txt.text()) or "".__eq__(self.B1_txt.text())):
return False
return True
def run_analysis(self):
mpl.pyplot.close(fig="all")
if self.check_required_inputs() is False:
self.show_messagebox('IOError', 'Please check all required fields', "None")
return
self.run_thread = RunThread(self)
self.run_thread.initialize.connect(self.initialize)
self.run_thread.clear_lhtxt.connect(self.clear_lhtxt)
self.run_thread.clear_odbtxt.connect(self.clear_odbtxt)
self.run_thread.clear_odbtxt.connect(self.clear_sncustomtxt)
self.run_thread.show_messagebox.connect(self.show_messagebox)
self.run_thread.show_progressbar.connect(self.show_progressbar)
self.run_thread.hide_progressbar.connect(self.hide_progressbar)
self.run_thread.finished.connect(self.finished)
self.run_thread.output.connect(self.filemanager.to_output)
self.run_thread.dmg_curve_plot.connect(self.dmg_curve_plot)
self.start_thread(self.run_thread)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
The code of the second window mayaviwindow.py:
import os
import sys
import mayavi.mlab as mlab
os.environ['ETS_TOOLKIT'] = 'qt4'
from pyface.qt import QtGui, QtCore
from traits.api import HasTraits, Instance, on_trait_change
from traitsui.api import View, Item
from mayavi.core.ui.api import MayaviScene, MlabSceneModel, SceneEditor
class MayaviWindow(QtGui.QMainWindow):
def open_new_window(self):
container = QtGui.QWidget()
container.setWindowTitle("Hot spot stress")
layout = QtGui.QGridLayout(container)
mayavi_widget = MayaviQWidget(container)
layout.addWidget(mayavi_widget, 1, 1)
container.show()
self.window = QtGui.QMainWindow()
self.window.setCentralWidget(container)
self.window.show()
class Visualization(HasTraits):
scene = Instance(MlabSceneModel, ())
#on_trait_change('scene.activated')
def update_plot(self):
self.scene.mlab.test_points3d()
# the layout of the dialog screated
view = View(Item('scene', editor=SceneEditor(scene_class=MayaviScene),
height=250, width=300, show_label=False),
resizable=True # We need this to resize with the parent widget
)
class MayaviQWidget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
layout = QtGui.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.visualization = Visualization()
# The edit_traits call will generate the widget to embed.
self.ui = self.visualization.edit_traits(parent=self,
kind='subpanel').control
layout.addWidget(self.ui)
self.ui.setParent(self)
Could someone explain to me what the problem is?
Edit
changing the class MayaviWindow to inherit from QWidget instead of QMainwindow did not solve the problem
class MayaviWindow(QtGui.QWidget):
def open_new_window(self):
self.container = QtGui.QWidget()
self.container.setWindowTitle("Hot spot stress")
layout = QtGui.QGridLayout(self.container)
mayavi_widget = MayaviQWidget(self.container)
layout.addWidget(mayavi_widget, 1, 1)
self.container.show()
I'm new to PyQt and I'm trying to create a videoplayer. When the user screenshots a certain frame, I should be able to retrieve 5 frames prior to the current frame and another 5 frames after the current frame. So far I'm only able to retrieve the current frame and I'm having a hard time figuring out which part of the code to alter to store the previous frames and the after frames. Here's the complete code:
# PyQt5 Video player
#!/usr/bin/env python
from PyQt5.QtCore import QDir, Qt, QUrl, pyqtSignal, QPoint, QRect, QObject
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer, QVideoFrame, QAbstractVideoSurface, QAbstractVideoBuffer, QVideoSurfaceFormat
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtWidgets import (QApplication, QFileDialog, QHBoxLayout, QLabel,
QPushButton, QSizePolicy, QSlider, QStyle, QVBoxLayout, QWidget)
from PyQt5.QtWidgets import QMainWindow,QWidget, QPushButton, QAction
from PyQt5.QtGui import QIcon, QPainter, QImage
import sys
import os
import os.path as osp
class VideoFrameGrabber(QAbstractVideoSurface):
frameAvailable = pyqtSignal(QImage)
def __init__(self, widget: QWidget, parent: QObject):
super().__init__(parent)
self.widget = widget
def supportedPixelFormats(self, handleType):
return [QVideoFrame.Format_ARGB32, QVideoFrame.Format_ARGB32_Premultiplied,
QVideoFrame.Format_RGB32, QVideoFrame.Format_RGB24, QVideoFrame.Format_RGB565,
QVideoFrame.Format_RGB555, QVideoFrame.Format_ARGB8565_Premultiplied,
QVideoFrame.Format_BGRA32, QVideoFrame.Format_BGRA32_Premultiplied, QVideoFrame.Format_BGR32,
QVideoFrame.Format_BGR24, QVideoFrame.Format_BGR565, QVideoFrame.Format_BGR555,
QVideoFrame.Format_BGRA5658_Premultiplied, QVideoFrame.Format_AYUV444,
QVideoFrame.Format_AYUV444_Premultiplied, QVideoFrame.Format_YUV444,
QVideoFrame.Format_YUV420P, QVideoFrame.Format_YV12, QVideoFrame.Format_UYVY,
QVideoFrame.Format_YUYV, QVideoFrame.Format_NV12, QVideoFrame.Format_NV21,
QVideoFrame.Format_IMC1, QVideoFrame.Format_IMC2, QVideoFrame.Format_IMC3,
QVideoFrame.Format_IMC4, QVideoFrame.Format_Y8, QVideoFrame.Format_Y16,
QVideoFrame.Format_Jpeg, QVideoFrame.Format_CameraRaw, QVideoFrame.Format_AdobeDng]
def isFormatSupported(self, format):
imageFormat = QVideoFrame.imageFormatFromPixelFormat(format.pixelFormat())
size = format.frameSize()
return imageFormat != QImage.Format_Invalid and not size.isEmpty() and \
format.handleType() == QAbstractVideoBuffer.NoHandle
def start(self, format: QVideoSurfaceFormat):
imageFormat = QVideoFrame.imageFormatFromPixelFormat(format.pixelFormat())
size = format.frameSize()
if imageFormat != QImage.Format_Invalid and not size.isEmpty():
self.imageFormat = imageFormat
self.imageSize = size
self.sourceRect = format.viewport()
super().start(format)
self.widget.updateGeometry()
self.updateVideoRect()
return True
else:
return False
def stop(self):
self.currentFrame = QVideoFrame()
self.targetRect = QRect()
super().stop()
self.widget.update()
def present(self, frame):
if frame.isValid():
cloneFrame = QVideoFrame(frame)
cloneFrame.map(QAbstractVideoBuffer.ReadOnly)
image = QImage(cloneFrame.bits(), cloneFrame.width(), cloneFrame.height(),
QVideoFrame.imageFormatFromPixelFormat(cloneFrame.pixelFormat()))
self.frameAvailable.emit(image) # this is very important
cloneFrame.unmap()
if self.surfaceFormat().pixelFormat() != frame.pixelFormat() or \
self.surfaceFormat().frameSize() != frame.size():
self.setError(QAbstractVideoSurface.IncorrectFormatError)
self.stop()
return False
else:
self.currentFrame = frame
self.widget.repaint(self.targetRect)
return True
def updateVideoRect(self):
size = self.surfaceFormat().sizeHint()
size.scale(self.widget.size().boundedTo(size), Qt.KeepAspectRatio)
self.targetRect = QRect(QPoint(0, 0), size)
self.targetRect.moveCenter(self.widget.rect().center())
def paint(self, painter):
if self.currentFrame.map(QAbstractVideoBuffer.ReadOnly):
oldTransform = self.painter.transform()
if self.surfaceFormat().scanLineDirection() == QVideoSurfaceFormat.BottomToTop:
self.painter.scale(1, -1)
self.painter.translate(0, -self.widget.height())
image = QImage(self.currentFrame.bits(), self.currentFrame.width(), self.currentFrame.height(),
self.currentFrame.bytesPerLine(), self.imageFormat)
self.painter.drawImage(self.targetRect, image, self.sourceRect)
self.painter.setTransform(oldTransform)
self.currentFrame.unmap()
class VideoWindow(QMainWindow):
def __init__(self, parent=None):
super(VideoWindow, self).__init__(parent)
self.setWindowTitle("PyQt Video Player Widget")
self.counter = 0
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.videoWidget = QVideoWidget()
self.videoFrame = QVideoFrame()
self.playButton = QPushButton()
self.playButton.setEnabled(False)
self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playButton.clicked.connect(self.play)
self.positionSlider = QSlider(Qt.Horizontal)
self.positionSlider.setRange(0, 0)
self.positionSlider.sliderMoved.connect(self.setPosition)
self.errorLabel = QLabel()
self.errorLabel.setSizePolicy(QSizePolicy.Preferred,
QSizePolicy.Maximum)
# Create new action
openAction = QAction(QIcon('open.png'), '&Open', self)
openAction.setShortcut('Ctrl+O')
openAction.setStatusTip('Open video')
openAction.triggered.connect(self.openFile)
# Create exit action
exitAction = QAction(QIcon('quit.png'), '&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.exitCall)
screenshotAction = QAction(QIcon('screenshot.png'), '&Screenshot', self)
screenshotAction.setShortcut('Ctrl+S')
screenshotAction.setStatusTip('Screenshot scenes')
screenshotAction.triggered.connect(self.screenshotCall)
# Create menu bar and add action
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
#fileMenu.addAction(newAction)
fileMenu.addAction(openAction)
fileMenu.addAction(screenshotAction)
fileMenu.addAction(exitAction)
# Create a widget for window contents
wid = QWidget(self)
self.setCentralWidget(wid)
# Create layouts to place inside widget
controlLayout = QHBoxLayout()
controlLayout.setContentsMargins(0, 0, 0, 0)
controlLayout.addWidget(self.playButton)
controlLayout.addWidget(self.positionSlider)
layout = QVBoxLayout()
layout.addWidget(self.videoWidget)
layout.addLayout(controlLayout)
layout.addWidget(self.errorLabel)
# Set widget to contain window contents
wid.setLayout(layout)
self.mediaPlayer.setVideoOutput(self.videoWidget)
self.mediaPlayer.stateChanged.connect(self.mediaStateChanged)
self.mediaPlayer.positionChanged.connect(self.positionChanged)
self.mediaPlayer.durationChanged.connect(self.durationChanged)
self.mediaPlayer.error.connect(self.handleError)
def openFile(self):
fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie",
QDir.homePath())
self.path = osp.dirname(str(fileName))
if fileName != '':
self.mediaPlayer.setMedia(
QMediaContent(QUrl.fromLocalFile(fileName)))
self.playButton.setEnabled(True)
def exitCall(self):
sys.exit(app.exec_())
def screenshotCall(self):
#Call video frame grabber
self.grabber = VideoFrameGrabber(self.videoWidget, self)
self.mediaPlayer.setVideoOutput(self.grabber)
self.mediaPlayer.pause()
self.grabber.frameAvailable.connect(self.process_frame)
self.errorLabel.setText("Taking a screenshot of image "+str(self.counter)+" ....")
self.mediaPlayer.play()
self.mediaPlayer.setVideoOutput(self.videoWidget)
def play(self):
if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
self.mediaPlayer.pause()
else:
self.mediaPlayer.play()
def mediaStateChanged(self, state):
if self.mediaPlayer.state() == QMediaPlayer.PlayingState:
self.playButton.setIcon(
self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.playButton.setIcon(
self.style().standardIcon(QStyle.SP_MediaPlay))
def positionChanged(self, position):
self.positionSlider.setValue(position)
def durationChanged(self, duration):
self.positionSlider.setRange(0, duration)
def setPosition(self, position):
self.mediaPlayer.setPosition(position)
def handleError(self):
self.playButton.setEnabled(False)
self.errorLabel.setText("Error: " + self.mediaPlayer.errorString())
def process_frame(self, image):
# Save image here
filename = "screenshot" + str(self.counter).zfill(6)
self.path = 'C:/Desktop/temp'
image.save(self.path+'/{}.png'.format(str(filename)))
self.counter = self.counter+1
if __name__ == '__main__':
app = QApplication(sys.argv)
player = VideoWindow()
player.resize(720, 480)
player.show()
sys.exit(app.exec_())
I'm thinking of retrieving the current frame number and creating loops to retrieve the before and after frames and then send them to process_frame for saving, but I'm not sure how to implement that because as of now, I'm still having a hard time grasping the principles behind the video frame grabber.
You have a few options.
First is to seek the video to the frames you need using QMediaPlayer.setPosition, then use the grabber you have already implemented to grab the current frame. Then you'd restore the position.
In reality, this is difficult to implement, since you need to know precise frame times, which is something that PyQT doesn't offer as part of their API.
To obtain frame times, you could use FFMS2 library and its Python bindings:
import ffms
source_file = "test/x264.mkv"
vsource = ffms.VideoSource(source_file)
print("Frames:", vsource.properties.NumFrames)
print("Times:", vsource.track.timecodes)
This is where the other solution kicks in. If you use FFMS2 anyway, why even use the media player to obtain the frames? You can just use FFMS2 directly:
>>> frame = vsource.get_frame(0)
>>> frame.EncodedWidth, frame.EncodedHeight
(128, 72)
>>> frame.planes[0]
array([16, 16, 16, ..., 16, 16, 16], dtype=uint8)
You input the frame number and receive a numpy array that you can convert into an actual image with library such as PIL or scikit-image. So you only need to convert current stream position to the frame number. To do it, you can query the vsource.track.timecodes variable and find the index of the closest frame number. To do it fast, you can use divide-and-conquer technique (the builtin Python bisect module).
Note that you should load the ffms.VideoSource once, when you select the video, since it may take a bit of time to index the time and frame information and you don't want the UI to lag when the user chooses to do a screenshot. To further speed up things, you could also cache the indexing information, if you need to reopen the file often. Manual indexing is covered in FFMS2 documentation.
UPDATE I made a function to get the text from QLineEdit in the logingui file, and it works. I have no idea why the same function wouldn't work if imported from a different file.
I am trying to get text from QLineEdit on a button click, I have 2 files, logingui and loginlogic. Both the files import each other. Here's the logingui's code
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import login.loginlogic
class LoginForm(QWidget):
def __init__(self):
QWidget.__init__(self, parent=None)
object = login.loginlogic
formLayout = QFormLayout()
# vboxlayout = QVBoxLayout()
serifFont = QFont("Arial", 10)
unamelbl = QLabel("Username")
unamelbl.setFont(serifFont)
pwdlbl = QLabel("Password")
pwdlbl.setFont(serifFont)
self.unamele = QLineEdit()
self.pwdle = QLineEdit()
self.unamele.setPlaceholderText("Username")
self.pwdle.setPlaceholderText("Password")
self.loginButton = QPushButton("Login")
self.loginButton.setStyleSheet("background-color: #99ff99; font-size: 15px;")
self.loginButton.setFont(serifFont)
self.loginButton.clicked.connect(object.handleLogin)
formLayout.addRow(unamelbl)
formLayout.addRow(self.unamele)
formLayout.addRow(pwdlbl)
formLayout.addRow(self.pwdle)
formLayout.addRow(self.loginButton)
formLayout.setSpacing(20)
self.setLayout(formLayout)
self.setStyleSheet("background-color: rgb(255,255,255)")
self.setMinimumSize(420, 320)
self.setMaximumSize(420, 320)
self.setContentsMargins(60, 50, 60, 50)
self.setWindowIcon(QIcon("favicon.ico"))
self.show()
def handlelogin(self):
u = self.unamele.text()
print(u)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = LoginForm()
app.exec_()
here's the code from loginlogic
import login.logingui
class handleLogin():
def __init__(self):
self.obj = login.logingui.LoginForm()
self.username = self.obj.unamele.text()
print("the code did reach here my man ")
print(self.username)
if __name__ == '__main__':
a = handleLogin()
now the output that I get is
your code reached here
*blank line*
I know that's a blank line cause when I click the button again it just leaves an empty line and the reached here text again. Also when I run the logingui, I do write some text in it. Not leaving it blank.
So the below code is part of a bigger project but in general, I have a QTableWidget which is populated from a database. One of the items is a combobox which when the user selects a milestone option from the combobox I want to be able to know which row and column the combo box that was select is in so I can then apply a fixed rate to the value of a cell in the same row. All I need help on is how to track which cell (row, column) the combo box that was selected is in.
Please note im using other tabs that are not shown and is why my code is setup the way it is. I found some other help but am not a very experience python programmer so I got stuck.
#!/usr/local/bin/python
# -*- coding: latin9 -*-
import sys, os , random
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSlot,SIGNAL,SLOT
import time
import json
import openpyxl
import Lists
class CashflowTab(QtGui.QDialog):
'''
Parent for all tab widgets of the REPO GUI
'''
def __init__(self, parent = None):
super(CashflowTab, self).__init__()
if parent != None:
self.setParent(parent)
self.initUI()
self.loadSettings()
def loadSettings(self):
'''
read settings and read db
'''
fh = open('settings.json')
self.settings = json.load(fh)
fh.close()
#load db
dbpath = self.settings['dbpath']
self.db = Lists.SQLiteHandler(dbpath)
self.repo = Lists.WCNList('ROSQL')
try:
self.db.read(self.repo)
except:
pass
class WCNSelectTab(CashflowTab):
'''
Window for WCN selection
'''
def __init__(self, parent = None):
super(WCNSelectTab, self).__init__(parent)
if parent != None:
self.setParent(parent)
def initUI(self):
global wbsitem, WCNSelectTab_object, linequerycheck
linequerycheck = 'False'
wbsitem = 'null'
QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("cleanlooks"))
gbox = QtGui.QGridLayout(self)
self.projectlist = QtGui.QTableWidget()
self.projectlist.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
#self.projectlist.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.projectlist.setColumnCount(3)
self.projectlist.setHorizontalHeaderLabels(QtCore.QStringList(['WBS','Service','Milestone']))
self.projectlist.setColumnWidth(0, 100)
self.projectlist.setColumnWidth(1, 100)
self.projectlist.setColumnWidth(2, 150)
gbox.addWidget(self.projectlist,5,0,3,6)
self.getAwardBudget()
def getAwardBudget(self):
global wbs_details
wbs_details = []
wbs_details.append(["123", "Service 1"])
wbs_details.append(["456", "Service 2"])
print wbs_details
self.projectlist.setRowCount(len(wbs_details))
for n,item in enumerate(wbs_details):
qitem = QtGui.QTableWidgetItem(item[0])
self.projectlist.setItem(n, 0, qitem)
qitem = QtGui.QTableWidgetItem(item[1])
self.projectlist.setItem(n, 1, qitem)
milestone_options = ["Award","Mobilization","Survey"]
milestonecombo = QtGui.QComboBox()
for t in milestone_options:
milestonecombo.addItem(t)
milestonecombo.setFixedWidth(150)
self.projectlist.setCellWidget(n, 2, milestonecombo)
class RepoGUI(QtGui.QMainWindow):
'''
Main Widget for REPO helper
'''
def __init__(self):
super(RepoGUI, self).__init__()
#self.mode = mode
self.initUI()
def initUI(self):
global approval, approval_names, username, approval_names
self.tabs = QtGui.QTabWidget()
self.setCentralWidget(self.tabs)
self.tabs.setAutoFillBackground(1)
fh = open('settings.json')
settings = json.load(fh)
fh.close()
if settings['WCNsubmit'] == 1:
self.tabs.addTab(WCNSelectTab(), 'WCN Creation')
self.setWindowTitle('Work Completion Notification')
self.setGeometry(300, 150, 1400, 800)
self.setStyleSheet('font-size: %ipt' %settings['fontsize'])
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = RepoGUI()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
A possible solution is to use indexAt() since the position of the QComboBox is relative to the viewport(), but to obtain the QComboBox that was selected we use sender().
for n,item in enumerate(wbs_details):
qitem = QtGui.QTableWidgetItem(item[0])
self.projectlist.setItem(n, 0, qitem)
qitem = QtGui.QTableWidgetItem(item[1])
self.projectlist.setItem(n, 1, qitem)
milestone_options = ["Award","Mobilization","Survey"]
milestonecombo = QtGui.QComboBox()
milestonecombo.addItems(milestone_options)
milestonecombo.setFixedWidth(150)
milestonecombo.currentIndexChanged[str].connect(self.onCurrentIndexChanged)
self.projectlist.setCellWidget(n, 2, milestonecombo)
def onCurrentIndexChanged(self, text):
combobox = self.sender()
ix = self.projectlist.indexAt(combobox.pos())
print(ix.row(), ix.column(), text)
Another possible solution is to use the property:
for n,item in enumerate(wbs_details):
qitem = QtGui.QTableWidgetItem(item[0])
self.projectlist.setItem(n, 0, qitem)
qitem = QtGui.QTableWidgetItem(item[1])
self.projectlist.setItem(n, 1, qitem)
milestone_options = ["Award","Mobilization","Survey"]
milestonecombo = QtGui.QComboBox()
milestonecombo.addItems(milestone_options)
milestonecombo.setFixedWidth(150)
milestonecombo.setProperty("row", n)
milestonecombo.setProperty("column", 1)
milestonecombo.currentIndexChanged[str].connect(self.onCurrentIndexChanged)
self.projectlist.setCellWidget(n, 2, milestonecombo)
def onCurrentIndexChanged(self, text):
combobox = self.sender()
r = combobox.property("row").toPyObject()
c = combobox.property("column").toPyObject()
print(r, c, text)