Creating a non-overlapping QVideoPlayer and PlotWidget in PyQt5 - python

I am trying to create an app in PyQt5 that has a media (video) player, a graph, and a few buttons. Currently, whenever I try to add the video and graph widgets, they compete for space (lay on top of one another), even when using QGridLayout.
Here is the entire file so far. The important parts are under 'Create video object', 'Create plot', and 'Create GUI Layout'.
Any suggestions for getting the video player to occupy the top portion of the window and the graph to occupy a region underneath?
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QGridLayout, QFileDialog, QVBoxLayout, QHBoxLayout
from pyqtgraph import PlotWidget
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtCore import QUrl
import sys
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(300, 100, 900, 600)
self.init_gui()
self.show()
def init_gui(self):
# Create media object
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
# Create video object
self.videoWidget = QVideoWidget()
# Create plot (can't get to size properly)
self.plotWidget = PlotWidget()
# Create 'Video' button
self.videoBtn = QPushButton('Open Video')
self.videoBtn.clicked.connect(self.open_video)
# Create 'Event Index' Button
self.eventIndexBtn = QPushButton('Event Index')
self.eventIndexBtn.setEnabled(False)
self.eventIndexBtn.clicked.connect(self.open_event_times)
# Create 'Time Series' Button
self.timeSeriesBtn = QPushButton('Time Series')
self.timeSeriesBtn.setEnabled(False)
self.timeSeriesBtn.clicked.connect(self.open_time_series)
# Create 'Prev' Button
self.prevBtn = QPushButton('Prev')
self.prevBtn.setEnabled(False)
# Create 'Next' Button
self.nextBtn = QPushButton('Next')
self.nextBtn.setEnabled(False)
# Create 'Replay' Button
self.replayBtn = QPushButton('Replay')
self.replayBtn.setEnabled(False)
# Create file dialog layout
fileBoxLayout = QVBoxLayout()
fileBoxLayout.addWidget(self.videoBtn)
fileBoxLayout.addWidget(self.eventIndexBtn)
fileBoxLayout.addWidget(self.timeSeriesBtn)
# Create controls layout
controlBoxLayout = QHBoxLayout()
controlBoxLayout.addWidget(self.prevBtn)
controlBoxLayout.addWidget(self.nextBtn)
controlBoxLayout.addWidget(self.replayBtn)
# Create GUI layout
GUILayout = QGridLayout()
GUILayout.addWidget(self.videoWidget, 0, 0, 8, 9)
GUILayout.addWidget(self.plotWidget, 8, 0, 2, 9)
GUILayout.addLayout(fileBoxLayout, 10, 0, 2, 3)
GUILayout.addLayout(controlBoxLayout, 10, 3, 2, 6)
self.setLayout(GUILayout)
self.mediaPlayer.setVideoOutput(self.videoWidget)
def open_video(self):
video_dialog = QFileDialog(self)
video_dialog.setNameFilters(["Videos (*.mp4 *.avi *.mov *.flv *.wmv)"])
video_dialog.selectNameFilter("Videos (*.mp4 *.avi *.mov *.flv *.wmv)")
video_dialog.exec_()
video_file_name = video_dialog.selectedFiles()
if len(video_file_name) != 0:
self.eventIndexBtn.setEnabled(True)
# Load first frame
self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(video_file_name[0])))
self.mediaPlayer.setPosition(0)
self.mediaPlayer.play()
self.mediaPlayer.pause()
def open_event_times(self):
event_times_dialog = QFileDialog(self)
event_times_dialog.setNameFilters(["Text (*.csv)"])
event_times_dialog.selectNameFilter("Text (*.csv)")
event_times_dialog.exec_()
event_times_file_name = event_times_dialog.selectedFiles()
if len(event_times_file_name) != 0:
self.timeSeriesBtn.setEnabled(True)
self.nextBtn.setEnabled(True)
def open_time_series(self):
time_series_dialog = QFileDialog(self)
time_series_dialog.setNameFilters(["Text (*.csv)"])
time_series_dialog.selectNameFilter("Text (*.csv)")
time_series_dialog.exec_()
time_series_file_name = time_series_dialog.selectedFiles()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())

The problem is that the QVideoWidget at startup does not have to display anything so the sizeHint is (-1x-1) making it occupy the smallest possible size causing the problem you are seeing.
A possible solution is to establish the same stretching factor between rows 0 and 1, it is not necessary nor should you place the proportions in the span, considering the above the solution is:
# Create GUI layout
GUILayout = QGridLayout()
GUILayout.addWidget(self.videoWidget, 0, 0, 1, 9)
GUILayout.addWidget(self.plotWidget, 1, 0, 1, 9)
GUILayout.addLayout(fileBoxLayout, 2, 0, 2, 3)
GUILayout.addLayout(controlBoxLayout, 3, 3, 2, 6)
GUILayout.setRowStretch(0, 1)
GUILayout.setRowStretch(1, 1)

Related

Retrieve data from a dialog in the calling window

I am struggling to retrieve results from a QDialog window and pass them back to the previous window. I want the user to be able to select a value/values, and then once they click okay in the dialog window, it should return them back to the previous window, and pass the text into the byVariables Textbox. The error returns on the line self.procUnivariate.byVariables.setText(item.text()). The error states:
AttributeError: 'ProcUnivariateVariables' object has no attribute 'procUnivariate'
I also tried using a return statement as you can see that is commented out. Unfortunately it seemed like the code got stuck and wasn't moving to the next line as I threw a print statement after and it never printed that line. I need help figuring out how to pass it back to the ProcUnivariate class once the okay button is clicked. Also, if I can figure this out, I'll likely turn the ProcUnivariate Class into a dialog window as well because it should take away the focus from the main window and shouldn't stay open if the main window is closed.
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QListWidget, QLineEdit, QTextEdit, QGridLayout, QHBoxLayout, QVBoxLayout, QDialog, QSizePolicy, QFileDialog, QTabWidget, QCheckBox
import PyQt5.QtGui as qtg
import glob
import os
from PyQt5.QtCore import Qt, QSettings
import inspect
from PyQt5 import QtCore
import pandas as pd
import pathlib
import pyreadstat
import json
class ProcUnivariateVariables(QDialog):
def __init__(self):
super().__init__()
self.procUnivariateByVariables = []
self.layout = QGridLayout()
self.setLayout(self.layout)
self.allVariables = QListWidget()
self.allVariables.setSelectionMode(3)
self.variablesSelected = QListWidget()
self.variablesSelected.setSelectionMode(3)
self.layout.addWidget(self.allVariables, 1,0, 4, 1)
self.layout.addWidget(self.variablesSelected, 1, 1, 4, 1)
self.okButton = QPushButton("Ok")
self.resetButton = QPushButton("Reset")
self.cancelButton = QPushButton("Cancel")
self.layout.addWidget(self.okButton, 1, 2)
self.layout.addWidget(self.resetButton, 2, 2)
self.layout.addWidget(self.cancelButton, 3, 2)
self.addByVariable = QPushButton(">")
self.removeVariable = QPushButton("<")
self.layout.addWidget(self.addByVariable, 5, 0)
self.layout.addWidget(self.removeVariable, 5, 1)
self.button_Connections()
def setItems(self, items):
self.allVariables.clear()
for item in items:
self.allVariables.addItem(item)
def add_by_variable(self):
selected_elements=[item.text() for item in self.allVariables.selectedItems()]
variableItem = self.variablesSelected.insertItems(self.variablesSelected.count(),selected_elements)
def button_Connections(self):
self.addByVariable.clicked.connect(self.add_by_variable)
self.removeVariable.clicked.connect(self.remove_variable)
self.okButton.clicked.connect(self.okay_clicked)
def remove_variable(self):
removeVariables = [item.text() for item in self.variablesSelected.selectedItems()]
self.selectedVariables.takeItems(removeVariables)
#sourceItem = self.currentSource.takeItem(oldSource)
def okay_clicked(self):
self.byVariablesSelected = [item.text() for item in self.variablesSelected.selectedItems()]
#self.procUnivariateByVariables = byVariablesSelected
print(self.byVariablesSelected)
self.accept()
#return self.byVariablesSelected
for item in self.byVariablesSelected:
print(item)
self.procUnivariate.byVariables.setText(item.text())
class ProcUnivariate(QWidget):
def __init__(self):
super().__init__()
self.procUnivariateVariables = None
layout = QGridLayout(self)
##Proc Univariate window Widgets
self.by = QLabel("By")
self.byVariables = QLineEdit()
self.byVariableList = QListWidget()
self.byButton = QPushButton("...")
self.varLabel = QLabel("Var")
self.classLabel = QLabel("Class")
self.freqLabel = QLabel("Freq")
self.histogramLabel = QLabel("Histogram")
self.freqBox = QLineEdit()
self.histBox = QLineEdit()
self.varBox = QLineEdit()
self.varButton = QPushButton("...")
self.sourceLabel = QLabel("Name")
self.sourceText = QLineEdit()
self.sourceButton = QPushButton("...")
self.selectionLabel = QLabel("Selection criteria")
self.selectionCriteria = QTextEdit()
self.statisticLabel = QLabel("Output")
self.statisticSearch = QLineEdit()
self.statisticList = QListWidget()
self.statisticList.setSortingEnabled(True)
self.outputLabel = QLabel("Output Statement")
self.outputText = QTextEdit()
self.fileOutputPath = QLineEdit("Output File Path")
self.runButton = QPushButton("Run")
self.previewButton = QPushButton("Preview")
self.okButton = QPushButton("Ok")
self.resetButton = QPushButton("Reset")
self.cancelButton = QPushButton("Cancel")
self.secondWindowConnections()
layout.addWidget(self.by, 1,0,1,1)
layout.addWidget(self.byVariables, 2, 0, 1, 4)
layout.addWidget(self.byButton, 2, 5, 1, 1)
#layout.addWidget(self.byVariableList, 3, 0, 1, 1)
layout.addWidget(self.freqLabel, 3, 0)
layout.addWidget(self.freqBox, 4, 0, 1, 4)
layout.addWidget(self.histogramLabel, 5, 0)
layout.addWidget(self.histBox, 6, 0, 1, 4)
layout.addWidget(self.varLabel, 7, 0, 1, 4)
layout.addWidget(self.varBox, 8, 0)
layout.addWidget(self.varButton, 8,5)
layout.addWidget(self.sourceLabel, 1, 6)
layout.addWidget(self.sourceText, 2, 6, 1, 4)
layout.addWidget(self.sourceButton, 2, 10)
layout.addWidget(self.selectionLabel, 3, 6)
layout.addWidget(self.selectionCriteria, 4, 6, 1, 4)
layout.addWidget(self.statisticLabel, 5, 6)
layout.addWidget(self.statisticSearch, 6, 6, 1, 4)
layout.addWidget(self.statisticList, 7, 6, 3, 4)
layout.addWidget(self.outputLabel, 10, 6)
layout.addWidget(self.outputText, 11, 6, 1, 4)
layout.addWidget(self.runButton, 12, 6)
layout.addWidget(self.previewButton, 12, 7)
layout.addWidget(self.okButton, 12, 8)
layout.addWidget(self.resetButton, 12, 9)
layout.addWidget(self.cancelButton, 12, 10)
self.setLayout(layout)
def secondWindowConnections(self): # this had a typo
self.byButton.clicked.connect(self.show_third_window)
def show_third_window(self):
if self.procUnivariateVariables is None: # if window has been created yet
self.procUnivariateVariables = ProcUnivariateVariables() # create window
if not self.procUnivariateVariables.isVisible(): # if window is showing
self.procUnivariateVariables.show() # show window
self.procUnivariateVariables.setItems(self.procUnivariateVariablesItems) # send items to window
def send_items(self, items): # this is to collect the variable that
self.procUnivariateVariablesItems = items # move to the third window
class MainWindow(QWidget):
def __init__(self):
super().__init__()
# Add a title
self.setWindowTitle("GUI Querying Program")
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.initUI()
self.setButtonConnections()
self.sw = None # dont initialize until neccessary.
def initUI(self):
subLayouts = {}
subLayouts['LeftColumn'] = QGridLayout()
self.layout.addLayout(subLayouts['LeftColumn'],1)
self.buttons = {}
self.buttons['addVariable'] = QPushButton('>')
self.buttons['removeVariable'] = QPushButton('<')
self.buttons['Toolkit'] = QPushButton('Toolkit')
self.variables = QListWidget()
self.selectedVariables = QListWidget()
subLayouts['LeftColumn'].addWidget(self.variables, 7,0,4,1)
subLayouts['LeftColumn'].addWidget(self.selectedVariables, 7,1,4,1)
subLayouts['LeftColumn'].addWidget(self.buttons['addVariable'], 10,0,1,1)
subLayouts['LeftColumn'].addWidget(self.buttons['removeVariable'], 10,1,1,1)
subLayouts['LeftColumn'].addWidget(self.buttons['Toolkit'], 11,1,1,1)
names = ['apple', 'banana', 'Cherry']
self.variables.insertItems(0, names)
def setButtonConnections(self):
self.buttons['addVariable'].clicked.connect(self.add_variable)
self.buttons['Toolkit'].clicked.connect(self.show_new_window)
# self.buttons['Toolkit'].clicked.connect(self.add_selected_variables)
# only use one connnect slot
def add_variable(self):
for item in self.variables.selectedItems():
self.selectedVariables.addItem(item.clone())
def show_new_window(self):
if self.sw is None: # check if window has been constructed
self.sw = ProcUnivariate() # construct window
if not self.sw.isVisible(): # If winow is not showing
self.sw.show() # show window
self.sw.send_items(self.add_selected_variables()) # send selected
# variables to second window
def add_selected_variables(self):
items = []
for i in range(self.selectedVariables.count()):
items.append(self.selectedVariables.item(i).clone())
# self.tw.setItems(items) ... self.tw doesnt exist so return them
return items
if __name__ == "__main__":
import sys
app = QApplication([])
mw = MainWindow()
mw.show()
app.exec()
What you need to do is connect a slot to the accepted signal of the dialog, and then retrieve the variables via a dedicated method of the dialog when that signal is emitted.
Here are all the relevant changes (fully tested as working):
class ProcUnivariateVariables(QDialog):
...
def button_Connections(self):
...
# connect to the dialog's built-in accept slot
self.okButton.clicked.connect(self.accept)
# add a method for retrieving the variables
def byVariablesSelected(self):
return [item.text() for item in self.variablesSelected.selectedItems()]
class ProcUnivariate(QWidget):
...
def secondWindowConnections(self): # this had a typo
self.byButton.clicked.connect(self.show_third_window)
self.varButton.clicked.connect(self.show_third_window)
def show_third_window(self):
if self.procUnivariateVariables is None: # if window has been created yet
self.procUnivariateVariables = ProcUnivariateVariables() # create window
# connect the new slot to the dialog's built-in accepted signal
self.procUnivariateVariables.accepted.connect(self.receive_selected_variables)
if not self.procUnivariateVariables.isVisible(): # if window is showing
self.procUnivariateVariables.show() # show window
self.procUnivariateVariables.setItems(self.procUnivariateVariablesItems) # send items to window
# set an identifier from the source button
self.procUnivariateVariables.sourceName = self.sender().text()
# add a slot for handling the dialog's accepted signal
def receive_selected_variables(self):
variables = self.procUnivariateVariables.byVariablesSelected()
text = ', '.join(variables)
if self.procUnivariateVariables.sourceName == 'byButton':
self.byVariables.setText(text)
else:
self.varBox.setText(text)
(Note that the above changes make the okay_clicked method of ProcUnivariateVariables redundant, so it should be removed).

Python PyQt5 MVC framework use of lambdas not working

Good Evening,
I am working on one of my first Python programs utilizing the MVC framework. I have looked at several tutorials and examples of MVC and I followed a youTube series of creating a calulator using MVC from Life in Code. https://www.youtube.com/channel/UCb__or4F5GkZLGYBhvfa5hg/featured
I am running into an issue with my program and I can't figure out why, I believe I have narrowed the issue down to the "View" class doesn't not see the "Controller" class to pass the function. I just don't know why. I am doing what I believe to be the same thing as Life in Code is doing with his example.
atdsController.py
import sys
from atdsCreatorUi import displayUi
#from atdsModel import atdsModel
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow
class atdsController:
def __init__(self):
#self.model = atdsModel()
self.view = displayUi(self)
def on_button_click(self, btnTxt):
print("I'm back in controller")
#file = self.model.openFile()
def main():
atdsFileCreator = QApplication(sys.argv)
atdsFileCreator.setStyle('Fusion')
#Show GUI
view = displayUi(QMainWindow)
view.show()
sys.exit(atdsFileCreator.exec_())
if __name__ == "__main__":
main()
atdsCreatorUi.
from PyQt5.QtCore import QCoreApplication, QDateTime, QTimer, QMetaObject
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import (
QMainWindow,
QFrame,
QWidget,
QHBoxLayout,
QVBoxLayout,
QGridLayout,
QGroupBox,
QLabel,
QPushButton,
QLineEdit,
QFileDialog,
QTableWidget,
QHeaderView,
QMenuBar,
QProgressBar,
QStatusBar,
QAction,
QWidgetAction,
QToolBar,
QPlainTextEdit)
__version__ = "1.0"
__author__ = "Daniel DeWitte"
ERROR_MSG = 'ERROR'
class VLine(QFrame):
def __init__(self):
super(VLine, self).__init__()
self.setFrameShape(self.VLine|self.Sunken)
class displayUi(QMainWindow):
def _createLeftLayout(self):
leftLayout = QVBoxLayout()
# Create Buttons
loadFile = QPushButton("Load File")
#btnTxt = loadFile.text()
loadFile.clicked.connect(lambda checked, btnTxt = loadFile.text():
self.controller.on_button_click(btnTxt))
loadFile.setFont(QFont('Times', 16))
leftLayout.addWidget(loadFile)
# Create Header Information
header = QGroupBox("Imported File Information:")
header.setObjectName("Header")
header.setStyleSheet("""QGroupBox#Header {border: 1px solid gray;
border-radius: 5px; margin-top: 0.5em;
font-size: 18px; font-weight: bold;}
QGroupBox:title {subcontrol-origin: margin;
subcontrol-position: top center; padding: 0px 0px;}""")
headerInfo = QGridLayout()
# Create QLineEdits()
empNum = QLineEdit()
empNum.setPlaceholderText("Employee Number")
empNum.setReadOnly(True)
empNum.setFixedWidth(200)
file = QLineEdit()
file.setPlaceholderText("File selected for conversion")
file.setReadOnly(True)
file.setFixedWidth(200)
testType = QLineEdit()
testType.setPlaceholderText("Test to be converted")
testType.setReadOnly(True)
testType.setFixedWidth(200)
partNum = QLineEdit()
partNum.setPlaceholderText("Part Number of UUT")
partNum.setReadOnly(True)
partNum.setFixedWidth(200)
serialNum = QLineEdit()
serialNum.setPlaceholderText("Serial Number of UUT")
serialNum.setReadOnly(True)
serialNum.setFixedWidth(200)
testStand = QLineEdit()
testStand.setPlaceholderText("Test stand test was performed on")
testStand.setReadOnly(True)
testStand.setFixedWidth(200)
standCal = QLineEdit()
standCal.setPlaceholderText("Calibration date of Test Stand")
standCal.setReadOnly(True)
standCal.setFixedWidth(200)
swBuildName = QLineEdit()
swBuildName.setPlaceholderText("OFP SW Version")
swBuildName.setReadOnly(True)
swBuildName.setFixedWidth(200)
buildName = QLineEdit()
buildName.setPlaceholderText("Relay Emulator Tools SW Version")
buildName.setReadOnly(True)
buildName.setFixedWidth(200)
buildToolsName = QLineEdit()
buildToolsName.setPlaceholderText("OM Build Tools SW Version")
buildToolsName.setReadOnly(True)
buildToolsName.setFixedWidth(200)
supportBuildName = QLineEdit()
supportBuildName.setPlaceholderText("Support Tools SW Version")
supportBuildName.setReadOnly(True)
supportBuildName.setFixedWidth(200)
numFilesGen = QLineEdit()
numFilesGen.setPlaceholderText("Number of ATDS files to be generated")
numFilesGen.setReadOnly(True)
numFilesGen.setFixedWidth(200)
# Create Layout
headerInfo.addWidget(loadFile, 0, 0, 1, 2)
headerInfo.addWidget(QLabel("Employee Number:"), 1, 0)
headerInfo.addWidget(empNum, 1, 1)
headerInfo.addWidget(QLabel("TDR File:"), 2, 0)
headerInfo.addWidget(tdrFile, 2, 1)
headerInfo.addWidget(QLabel("Test Name:"), 3, 0)
headerInfo.addWidget(testType, 3, 1)
headerInfo.addWidget(QLabel("Part Number:"), 4, 0)
headerInfo.addWidget(partNum, 4, 1)
headerInfo.addWidget(QLabel("Serial Number:"), 5, 0)
headerInfo.addWidget(serialNum, 5, 1)
headerInfo.addWidget(QLabel("Test Stand:"), 6, 0)
headerInfo.addWidget(testStand, 6, 1)
headerInfo.addWidget(QLabel("Test Stand Calibration Date:"), 7, 0)
headerInfo.addWidget(standCal, 7, 1)
headerInfo.addWidget(QLabel("OFP SW Version:"), 8, 0)
headerInfo.addWidget(ofpBuildName, 8, 1)
headerInfo.addWidget(QLabel("Relay Emulator Tools SW Version:"), 9, 0)
headerInfo.addWidget(reBuildName, 9, 1)
headerInfo.addWidget(QLabel("OM Build Tools SW Version:"), 10, 0)
headerInfo.addWidget(omBuildToolsName, 10, 1)
headerInfo.addWidget(QLabel("Support Tools SW Version:"), 11, 0)
headerInfo.addWidget(supportBuildName, 11, 1)
headerInfo.addWidget(QLabel("Number of ATDS Files to be Created:"), 12, 0)
headerInfo.addWidget(numFilesGen, 12, 1)
#headerInfo.setRowStretch(13, 1)
# Add Header to Frame
header.setLayout(headerInfo)
leftLayout.addWidget(header)
convertFile = QPushButton("Convert file to ATDS Format")
#convertFiles.clicked.connect(self.convertFile)
convertFile.setFont(QFont('Times', 16))
leftLayout.addWidget(convertFile)
# Add left layout to Main Layout
self.mainLayout.addLayout(leftLayout, 1)
def _createRightLayout(self):
rightLayout = QVBoxLayout()
#Create ATDS File Table
rightLayout.convTable = QTableWidget()
rightLayout.convTable.setRowCount(12)
rightLayout.convTable.setColumnCount(5)
# Table to fit the screen horizontally
rightLayout.convTable.horizontalHeader().setStretchLastSection(True)
rightLayout.convTable.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
rightLayout.convTable.horizontalHeader().setMinimumSectionSize(115)
# Set Column Names
rightLayout.convTable.setHorizontalHeaderLabels(["ATDS File Name:", "ATDS File Desc:", "Conversion Status:",
"Test Section:","Pass / Fail:"])
rightLayout.addWidget(rightLayout.convTable)
# Detailed information of selected files
#Create ATDS File Table
rightLayout.detailedInfoTable = QTableWidget()
rightLayout.detailedInfoTable.setRowCount(10)
rightLayout.detailedInfoTable.setColumnCount(7)
# Table to fit the screen horizontally
rightLayout.detailedInfoTable.horizontalHeader().setStretchLastSection(True)
rightLayout.detailedInfoTable.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
rightLayout.detailedInfoTable.horizontalHeader().setMinimumSectionSize(115)
# Set Column Names
rightLayout.detailedInfoTable.setHorizontalHeaderLabels(["Test Section:", "Test Description:", "Units:",
"Lower Limit:", "Upper Limit:", "Value:", "Pass / Fail:"])
rightLayout.addWidget(rightLayout.detailedInfoTable)
# Add right layout to Main Layout
self.mainLayout.addLayout(rightLayout, 3)
def findFile(self):
doc = QFileDialog.getOpenFileName(self,
"Load TDR File",
r'S:\\EDE\\adv_sys_mfg',
"Excel Files (*.xls*);; LibreOffice Files (*.ods)")
def _createMenu(self):
menu = QMenuBar(self)
# Create Menus
fileMenu = menu.addMenu("&File")
fileMenu.addAction(self.loadAction)
fileMenu.addSeparator()
fileMenu.addAction(self.quit)
aboutMenu = menu.addMenu("&Help")
self.setMenuBar(menu)
def _createActions(self):
self.loadAction = QAction("&Load New File", self)
self.loadAction.setShortcut("Ctrl+N")
self.loadAction.triggered.connect(self.findFile)
self.quit = QAction("Quit", self)
self.quit.setShortcut("Ctrl+Q")
self.quit.triggered.connect(QCoreApplication.instance().quit)
def _createStatusBar(self):
statusBar = QStatusBar()
# Create Status Bar
statusMsg = "Click Load File or Ctrl + N to begin"
statusBar.showMessage(statusMsg)
statusBar.addPermanentWidget(VLine())
# Create Progress Bar
progressBar = QProgressBar()
statusBar.addPermanentWidget(progressBar)
progressBar.setValue(50)
statusBar.addPermanentWidget(VLine())
# Add Date Time Widget to status bar
self.display = QLabel()
statusBar.addPermanentWidget(self.display)
current = QDateTime.currentDateTime()
self.display.setText(current.toString("yyyy-MMM-dd h:mm:ss AP"))
# Create date and time widget
timer = QTimer()
timer.timeout.connect(self.displayTime)
timer.start(1000)
self.displayTime()
self.setStatusBar(statusBar)
def displayTime(self):
current = QDateTime.currentDateTime()
self.display.setText(current.toString("yyyy-MMM-dd h:mm:ss AP"))
def __init__(self, atdsController):
# View Initializer
super().__init__()
# Connect atdsController
self.controller = atdsController()
# Set Window Properties
self.setWindowTitle("TDR File Conversion to ATDS")
self.setMinimumSize(1250, 600)
# Set Central Widget and general layout
self.mainLayout = QHBoxLayout()
self._centralWidget = QWidget(self)
self.setCentralWidget(self._centralWidget)
self._centralWidget.setLayout(self.mainLayout)
# Create a display and buttons
self._createActions()
self._createLeftLayout()
self._createRightLayout()
self._createMenu()
self._createStatusBar()
When I run this code, I get the following error:
"runfile('H:/C4ISR S2 NPI/Projects/Voice of the Process/ATDS TDR Importer - Python/ATDS File Creator/atdsController.py', wdir='H:/C4ISR S2 NPI/Projects/Voice of the Process/ATDS TDR Importer - Python/ATDS File Creator')
Reloaded modules: atdsCreatorUi
Traceback (most recent call last):
File "H:\C4ISR S2 NPI/Projects/Voice of the Process/ATDS TDR Importer - Python/ATDS File Creator\atdsCreatorUi.py", line 51, in
loadFile.clicked.connect(lambda checked, btnTxt = loadFile.text(): self.controller.on_button_click(btnTxt))
AttributeError: 'QMainWindow' object has no attribute on_button_click'
"
From what it looks like is that atdsCreatorUi is not seeing the atdsController but I don't know why.
Any help will be appreciated,
~Dan

vtkOrientationMarkerWidget embedded in QTWidget

I have embedded the vtkOrientationMarkerWidget to the QTWidget as an axes indicator, but I met a problem there arises a bug:
ERROR: In C:\Dev\Soft\vtk\source\Rendering\OpenGL2\vtkWin32OpenGLRenderWindow.cxx, line 217
vtkWin32OpenGLRenderWindow (00000278F82D1AD0): wglMakeCurrent failed in MakeCurrent(), error: Handle
Invalid
when I multiply generate QTWidget instances by clicking the button.
Because I want to add some buttons in the 3D view, I use QTWidget as a container, which is the root cause of the problem. Are there some solutions for this?
My environment is Python 3.8+PyQT5.15.0+VTK9.0.1
The code is:
The main window:
from PyQt5.QtWidgets import QMainWindow, QDesktopWidget, QFrame, QVBoxLayout, QApplication, QPushButton
from axes_widget import VTKWidget2
from axes import Axes
from axes2 import Axes2
class Widget:
def __init__(self):
self.window = QMainWindow()
self.window.resize(500, 400)
self.window.move(300, 310)
self.button = QPushButton('3D', self.window)
self.button.clicked.connect(self.click_3d)
self.button.move(380, 80)
def click_3d(self):
self._win_vtk = VTKWidget2()
self._win_vtk.show()
if __name__ == '__main__':
app = QApplication([])
widget_ins=Widget()
widget_ins.window.show()
app.exec_()
The VTK window:
from PyQt5.QtWidgets import QMainWindow, QDesktopWidget, QFrame, QVBoxLayout, QApplication
import sys
import vtk
import vtkmodules.qt
vtkmodules.qt.QVTKRWIBase = "QGLWidget"
# QGLWidget as the base class of the QVTKRenderWindowInteractor, instead of QWidget.
# This change is because it is reported that sometimes QWidget can cause rendering problems.
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from serum import dependency, singleton, inject
# #singleton
class VTKWidget2(QMainWindow):
def __init__(self, parent=None):
super(VTKWidget2, self).__init__(parent)
self.camera_focal_point = [0, 0, 0]
self.camera_position = [-4500, 0, 0]
self.camera_view_up = [0, -1, 0] # default, [0,1,0]
self.camera_azimuth = 0 # -20
self.camera_zoom = 1.8 # 1.75
self.z_angle = 0
self.x_angle = 0
self.y_angle = 0
self.x_transform = 200
self.y_transform = -900 # -820
self.z_transform = 1000
# vtk.vtkOutputWindow.SetGlobalWarningDisplay(0)
self.initial()
def initial(self):
self.setWindowTitle("3D_Model")
self.resize(1000, 800)
screen = QDesktopWidget().geometry()
self.self_size = self.geometry()
self.move(int((screen.width() - self.self_size.width()) / 2),
int((screen.height() - self.self_size.height()) / 2)) #
self.colors = vtk.vtkNamedColors()
# Create an actor
# self.actor = vtk.vtkActor()
self.left_text_actor = vtk.vtkTextActor()
self.right_text_actor = vtk.vtkTextActor()
# A renderer and render window
self.renderer = vtk.vtkRenderer()
# renderWindow = vtk.vtkRenderWindow()
# renderWindow.SetWindowName("Display Coordinate Axes")
# renderWindow.AddRenderer(renderer)
# An interactor
# renderWindowInteractor = vtk.vtkRenderWindowInteractor()
# renderWindowInteractor.SetRenderWindow(renderWindow)
self.renderWindowInteractor = QVTKRenderWindowInteractor()
self.renderWindow = self.renderWindowInteractor.GetRenderWindow()
self.renderWindow.AddRenderer(self.renderer)
# Add the actors to the scene
# self.renderer.AddActor(self.actor)
self.renderer.SetBackground(self.colors.GetColor3d("SlateGray"))
# add mouse interaction mode
vtkStyle = vtk.vtkInteractorStyleTrackballCamera()
# vtkStyle = MyInteractorStyle(self.renderWindow)
# vtkStyle = vtk.vtkInteractorStyleSwitch()
# vtkStyle = vtk.vtkInteractorStyleTrackballActor()
self.renderWindowInteractor.SetInteractorStyle(vtkStyle)
# self.renderWindow.GetInteractor().SetInteractorStyle(vtkStyle)
self.transform = vtk.vtkTransform()
self.transform.Translate(self.x_transform, self.y_transform, self.z_transform)
# set axes
self.add_axes()
self.vtkCamera = vtk.vtkCamera()
self.update_camera()
frame = QFrame()
self.setCentralWidget(frame) # QMainWindow's property
vl = QVBoxLayout()
vl.addWidget(self.renderWindowInteractor)
# vl.addWidget(self.widget) # problem: wrong: no widget for QT
frame.setLayout(vl)
# Begin mouse interaction
self.renderWindowInteractor.Initialize()
self.renderWindowInteractor.Start()
def add_axes(self):
# set axes
### important: widget must be set as field, otherwise it doesn't show.
# problem: no widget for QT
self.widget = vtk.vtkOrientationMarkerWidget()
self.axis = vtk.vtkAxesActor()
rgba = [0] * 4
self.colors.GetColor("Carrot", rgba)
self.widget.SetOutlineColor(rgba[0], rgba[1], rgba[2])
self.widget.SetOrientationMarker(self.axis) ### important
self.widget.SetInteractor(self.renderWindowInteractor)
self.widget.SetViewport(0.0, 0.0, 0.4, 0.4)
self.widget.SetEnabled(1)
self.widget.InteractiveOn()
def update_camera(self):
self.renderer.ResetCamera()
self.renderer.SetActiveCamera(self.vtkCamera)
self.vtkCamera.SetFocalPoint(self.camera_focal_point)
self.vtkCamera.SetPosition(self.camera_position)
self.vtkCamera.SetViewUp(self.camera_view_up)
self.vtkCamera.Azimuth(self.camera_azimuth)
self.vtkCamera.Zoom(self.camera_zoom)
self.renderWindow.Render()
The problem lies in Widget.click_3d(self). Every time you click the button you reassign a new VTKWidget2 to self._win_vtk. Since self._win_vtk is the only reference in your program to any of the VTKWidget2 windows, as soon as you assign a new value to self._win_vtk, the previous VTKWidget2 window will be deleted by the garbage collector (which apparently causes problems with the underlying QGLWidget). One way around this is to make a persistent reference to all the windows, for example by putting them in a list, e.g.
class Widget:
def __init__(self):
....
self.vtk_windows = []
def click_3d(self):
win = VTKWidget2()
self.vtk_windows.append(win)
win.show()
This still causes a bunch of errors when the last window is closed an the program exits which seems to be cause by using a QGLWidget as the base of your QVTKRenderWindowInteractor.

How to make QGridLayout resize cells the exact same size?

Whenever I resize the window (a QDialog), Reference Viewer and Selected Viewer (subclasses of QScrollArea) should have the exact same size at all time, even after a resize event.
However, once out of twice, I get a size 1 pixel smaller for the Selected Viewer (QScrollArea widget on the right). By once out of twice, I mean every odd pixel count.
It seems that the QGridLayout is forcing the right-most panel to that smaller size, probably due to rounding down the value of the space still available.
I use a QGridLayout because I need the toolbar to stay aligned in the center between the panels and it works well.
Here is a screencast demonstrating the problem: you can see the scrollbar showing up every-time the Selected Viewer (panel on the right) is resized one pixel shorter in width compared to the panel on the left.
Here is mostly what I'm doing:
verticalLayout = QVBoxLayout(self)
verticalLayout.setSpacing(0)
verticalLayout.setContentsMargins(0, 0, 0, 0)
gridLayout = QGridLayout()
# Minimum width for the toolbar in the middle:
gridLayout.setColumnMinimumWidth(1, 30)
gridLayout.setColumnStretch(0,1)
gridLayout.setColumnStretch(1,0)
gridLayout.setColumnStretch(2,1)
gridLayout.setSpacing(3)
selectedImageViewer = ScrollAreaImageViewer(self)
gridLayout.addWidget(selectedImageViewer, 0, 0, 3, 1)
verticalToolBar = QToolBar(self)
verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical))
gridLayout.addWidget(verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter)
referenceImageViewer = ScrollAreaImageViewer(self)
gridLayout.addWidget(referenceImageViewer, 0, 2, 3, 1)
verticalLayout.addLayout(gridLayout)
I add another widget below in the QVBoxLayout but it's irrelevant here.
I have tried adding spacers but it doesn't seem to change anything:
gridLayout.addItem(QSpacerItem(5,0, QSizePolicy.Minimum), 1, 3, 1, 1, Qt.Alignment(Qt.AlignCenter))
Is there a way to ensure both Viewers get the same size without using resize() on them on every resizeEvent()?
Or should this actually be considered a bug in Qt?
I have tried the following which works around the scrollbar flickering issue:
def resizeEvent(self, event):
self.gridLayout.setColumnMinimumWidth(0, self.selectedImageViewer.size().width())
self.gridLayout.setColumnMinimumWidth(2, self.selectedImageViewer.size().width())
But the sizes still differ by one pixel once out of twice.
Edit: here is a minimal reproducible example
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtWidgets import (QDialog, QLayout, QVBoxLayout,
QLabel, QSizePolicy, QToolBar, QGridLayout,
QWidget, QApplication )
class MyWidget(QWidget):
def __init__(self, parent):
super().__init__(parent)
self.label = QLabel(self)
def resizeEvent(self, event):
self.label.setText(f"{self.size()}")
class MyDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
self.setMinimumSize(QSize(500, 100))
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.setSpacing(0)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout = QGridLayout()
self.gridLayout.setColumnMinimumWidth(1, 30)
self.gridLayout.setColumnStretch(0,1)
self.gridLayout.setColumnStretch(1,0)
self.gridLayout.setColumnStretch(2,1)
self.gridLayout.setSpacing(3)
self.selectedImageViewer = MyWidget(self)
self.gridLayout.addWidget(self.selectedImageViewer, 0, 0, 3, 1)
self.verticalToolBar = QToolBar(self)
self.verticalToolBar.setOrientation(Qt.Orientation(Qt.Vertical))
self.gridLayout.addWidget(self.verticalToolBar, 1, 1, 1, 1, Qt.AlignCenter)
self.referenceImageViewer = MyWidget(self)
self.gridLayout.addWidget(self.referenceImageViewer, 0, 2, 3, 1)
self.verticalLayout.addLayout(self.gridLayout)
def main():
app = QApplication([()])
window = QWidget()
dialog = MyDialog(window)
dialog.show()
return app.exec()
if __name__ == "__main__":
main()
I assume that problem is with ScrollArea being used for referenceImageViewer, so in each resize event actual referenceImageViewer is trying to add a horizontal scrollbar to itself.
As a solution you can
Set referenceImageViewer's adjust policy to (QAbstractScrollArea.AdjustIgnored or QAbstractScrollArea.AdjustToContents).
Try to use Widgets instead of ScrollAreaImageViewer instances in gridLayout, and then adding ScrollAreaImageViewer inside that widgets.
Edited.
There must be difference between width of 1st and 3rd widgets as long
as ToolBar's width is fixed. E.g when window width is 501 and toolbar
width is fixed at 20 auto alignment can't equally divide remaining 481
pixels in a half..
As a solution your toolbar must be resizable too.
For reducing ToolBar width changes you can increase 1st and 3rd column
stretch in GridLayout for example to value 8, and set 2nd column
stretch to 1, so layout will automatically adjust width of each
column.

PyQt4 - How to read QLineEdit text from within QListWidgetItem

I created a QListWidget... The QListWidgetItem composes of a QPushButton and QLineEdit aligned inside QHBoxLayout...
Another button is created with a function to loop on QListWidgetItem(s) and read the text in each QLineEdit entry and print it...
The print shows nothing, I tried a lot of different combinations with QListWidget methods but with no hope!
import sys
from PyQt4 import QtGui, QtCore
def Add_OtherItem():
ItemOther = CustomItem()
ItemOther.SetupItem(OthersCommandsWidget)
def ReadText_fn():
for index in range(0,OthersCommandsWidget.count()):
TargetItem = (OthersCommandsWidget.item(index)).text()
print(TargetItem)
app = QtGui.QApplication(sys.argv)
class CustomItem(object):
def SetupItem(self, OthersCommandList):
self.Item = QtGui.QListWidgetItem()
self.Item.setStatusTip("TItem")
self.MainWidget = QtGui.QWidget()
self.CommandLine = QtGui.QLineEdit("")
self.DeleteButton = QtGui.QPushButton()
self.DeleteButton.setFixedSize(22, 22)
self.ItemLayoutBox = QtGui.QHBoxLayout()
self.ItemLayoutBox.addWidget(self.CommandLine)
self.ItemLayoutBox.addWidget(self.DeleteButton)
self.MainWidget.setLayout(self.ItemLayoutBox)
self.Item.setSizeHint(self.MainWidget.sizeHint())
OthersCommandList.addItem(self.Item)
OthersCommandList.setItemWidget(self.Item, self.MainWidget)
AppWindow = QtGui.QMainWindow()
AppWindow.setWindowTitle("PoC ListWidget")
AppWindow.setFixedSize(550, 550)
TabWindow = QtGui.QTabWidget(AppWindow)
TabWindow.setGeometry(8, 30, 535, 505)
WorkTAB = QtGui.QWidget()
TabWindow.addTab(WorkTAB, 'Tab.01')
OthersCommandsWidget = QtGui.QListWidget(WorkTAB)
OthersCommandsWidget.setGeometry(QtCore.QRect(8, 40, 515, 430))
AddButton = QtGui.QPushButton(WorkTAB)
AddButton.setText("Add Item")
AddButton.setGeometry(QtCore.QRect(8, 8, 0, 0))
AddButton.setFixedSize(70, 22)
AddButton.clicked.connect(Add_OtherItem)
ReadButton = QtGui.QPushButton(WorkTAB)
ReadButton.setText("Read Text")
ReadButton.setGeometry(QtCore.QRect(100, 8, 0, 0))
ReadButton.setFixedSize(70, 22)
ReadButton.clicked.connect(ReadText_fn)
AppWindow.show()
sys.exit(app.exec_())
You're trying to access the text field of a QLineEdit(). To do this, you must call its text() property. But first, you must grab the QLineEdit() object.
OthersCommandsWidget is of type QListWidget() which has a QListWidgetItem() that is connected to a QWidget(). You can grab the QWidget() object using itemWidget(). QWidget() has three embedded objects: QHBoxLayout(), QLineEdit() and QPushButton() which can be accessed using children() according to the order they were added.
Change
TargetItem = (OthersCommandsWidget.item(index)).text()
To:
TargetItem = OthersCommandsWidget.itemWidget(OthersCommandsWidget.item(index)).children()[1].text()

Categories

Resources