How can I "Print Preview" of document created by QTextDocument in PyQt5? - python

Hello Experts!! I hope you are having great day. I am new in GUI programming specially PyQt5. I am practicing on simple GUI invoice application. In this application, I successfully generated the Invoice By QTextDocument. Now i want to add print dialogue and print preview option. I am having trouble in the code. This is saying
AttributeError: 'InvoiceForm' object has no attribute
'printpreviewDialog
As i am new, i am little bit confused in there. Could you please fix the code? That will help me a lot to study. Many Many Thanks.
The code has given below:-
import sys
from PyQt5.QtCore import pyqtSignal, QSize, QSizeF, QDate
from PyQt5.QtGui import QTextDocument, QTextCursor, QFont
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtWidgets import QWidget, QFormLayout, QLineEdit, QPlainTextEdit, QSpinBox, QDateEdit, QTableWidget, \
QHeaderView, QPushButton, QHBoxLayout, QTextEdit, QApplication, QMainWindow
font= QFont('Arial',16)
class InvoiceForm(QWidget):
submitted = pyqtSignal(dict)
def __init__(self):
super().__init__()
self.setLayout(QFormLayout())
self.inputs = dict()
self.inputs['Customer Name'] = QLineEdit()
self.inputs['Customer Address'] = QPlainTextEdit()
self.inputs['Invoice Date'] = QDateEdit(date=QDate.currentDate(), calendarPopup=True)
self.inputs['Days until Due'] = QSpinBox()
for label, widget in self.inputs.items():
self.layout().addRow(label, widget)
self.line_items = QTableWidget(rowCount=10, columnCount=3)
self.line_items.setHorizontalHeaderLabels(['Job', 'Rate', 'Hours'])
self.line_items.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.layout().addRow(self.line_items)
for row in range(self.line_items.rowCount()):
for col in range(self.line_items.columnCount()):
if col > 0:
w = QSpinBox()
self.line_items.setCellWidget(row, col, w)
submit = QPushButton('Create Invoice', clicked=self.on_submit)
print = QPushButton('Print Invoice', clicked=self.printpreviewDialog)
self.layout().addRow(submit,print)
def on_submit(self):
data = {'c_name': self.inputs['Customer Name'].text(),
'c_addr': self.inputs['Customer Address'].toPlainText(),
'i_date': self.inputs['Invoice Date'].date().toString(),
'i_due': self.inputs['Invoice Date'].date().addDays(self.inputs['Days until Due'].value()).toString(),
'i_terms': '{} days'.format(self.inputs['Days until Due'].value()),
'line_items': list()}
for row in range(self.line_items.rowCount()):
if not self.line_items.item(row, 0):
continue
job = self.line_items.item(row, 0).text()
rate = self.line_items.cellWidget(row, 1).value()
hours = self.line_items.cellWidget(row, 2).value()
total = rate * hours
row_data = [job, rate, hours, total]
if any(row_data):
data['line_items'].append(row_data)
data['total_due'] = sum(x[3] for x in data['line_items'])
self.submitted.emit(data)
# remove everything else in this function below this point
class InvoiceView(QTextEdit):
dpi = 72
doc_width = 8.5 * dpi
doc_height = 6 * dpi
def __init__(self):
super().__init__(readOnly=True)
self.setFixedSize(QSize(self.doc_width, self.doc_height))
def build_invoice(self, data):
document = QTextDocument()
self.setDocument(document)
document.setPageSize(QSizeF(self.doc_width, self.doc_height))
document.setDefaultFont(font)
cursor = QTextCursor(document)
cursor.insertText(f"Customer Name: {data['c_name']}\n")
cursor.insertText(f"Customer Address: {data['c_addr']}\n")
cursor.insertText(f"Date: {data['i_date']}\n")
cursor.insertText(f"Total Due: {data['total_due']}\n")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
central = QWidget()
self.setCentralWidget(central)
layout = QHBoxLayout(central)
self.invoiceForm = InvoiceForm()
layout.addWidget(self.invoiceForm)
self.invoiceView = InvoiceView()
layout.addWidget(self.invoiceView)
# hide the widget right now...
self.invoiceView.setVisible(False)
self.invoiceForm.submitted.connect(self.showPreview)
def showPreview(self, data):
self.invoiceView.setVisible(True)
self.invoiceView.build_invoice(data)
def printpreviewDialog(self):
printer = QPrinter(QPrinter.HighResolution)
previewDialog = QPrintPreviewDialog(printer, self)
previewDialog.paintRequested.connect(self.printPreview)
previewDialog.exec_()
def printPreview(self, printer):
self.invoiceView.build_invoice.print_(printer)
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
if __name__ == '__main__':
main()

The main problem is that self.printpreviewDialog is a member of MainWindow, not of InvoiceForm, so you should connect the clicked signal from the main window instead.
Also note that you tried to use self.invoiceView.build_invoice.print_(), but this wouldn't work as you are not calling build_invoice, and even if you did, that function doesn't return anything.
You should use the self.invoiceView.document() instead, but you must ensure that the data has been built before.
class InvoiceForm(QWidget):
submitted = pyqtSignal(dict)
def __init__(self):
# ...
submit = QPushButton('Create Invoice', clicked=self.on_submit)
# make the button a member of the instance instead of a local variable,
# so that we can connect from the main window instance
self.printButton = QPushButton('Print Invoice')
self.layout().addRow(submit, self.printButton)
# ...
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.invoiceForm.printButton.clicked.connect(self.printpreviewDialog)
# ...
def printPreview(self, printer):
self.invoiceView.document().print_(printer)
Note: never, never use built-in functions and statements for variable names, like print.

Try it:
import sys
from PyQt5.QtCore import pyqtSignal, QSize, QSizeF, QDate
from PyQt5.QtGui import QTextDocument, QTextCursor, QFont
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtWidgets import (QWidget, QFormLayout, QLineEdit, QPlainTextEdit,
QSpinBox, QDateEdit, QTableWidget, QHeaderView, QPushButton, QHBoxLayout,
QTextEdit, QApplication, QMainWindow)
font = QFont('Arial',16)
class InvoiceForm(QWidget):
submitted = pyqtSignal(dict)
def __init__(self, parent=None): # + parent=None
super().__init__(parent) # + parent
self.setLayout(QFormLayout())
self.inputs = dict()
self.inputs['Customer Name'] = QLineEdit()
self.inputs['Customer Address'] = QPlainTextEdit()
self.inputs['Invoice Date'] = QDateEdit(date=QDate.currentDate(), calendarPopup=True)
self.inputs['Days until Due'] = QSpinBox()
for label, widget in self.inputs.items():
self.layout().addRow(label, widget)
self.line_items = QTableWidget(rowCount=10, columnCount=3)
self.line_items.setHorizontalHeaderLabels(['Job', 'Rate', 'Hours'])
self.line_items.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.layout().addRow(self.line_items)
for row in range(self.line_items.rowCount()):
for col in range(self.line_items.columnCount()):
if col > 0:
w = QSpinBox()
self.line_items.setCellWidget(row, col, w)
submit = QPushButton('Create Invoice', clicked=self.on_submit)
# + vvvvvv vvvvvvvvvvvvv
_print = QPushButton('Print Invoice', clicked=self.window().printpreviewDialog) # + _print, + self.window()
self.layout().addRow(submit, _print) # + _print
def on_submit(self):
data = {'c_name': self.inputs['Customer Name'].text(),
'c_addr': self.inputs['Customer Address'].toPlainText(),
'i_date': self.inputs['Invoice Date'].date().toString(),
'i_due': self.inputs['Invoice Date'].date().addDays(self.inputs['Days until Due'].value()).toString(),
'i_terms': '{} days'.format(self.inputs['Days until Due'].value()),
'line_items': list()}
for row in range(self.line_items.rowCount()):
if not self.line_items.item(row, 0):
continue
job = self.line_items.item(row, 0).text()
rate = self.line_items.cellWidget(row, 1).value()
hours = self.line_items.cellWidget(row, 2).value()
total = rate * hours
row_data = [job, rate, hours, total]
if any(row_data):
data['line_items'].append(row_data)
data['total_due'] = sum(x[3] for x in data['line_items'])
self.submitted.emit(data)
# remove everything else in this function below this point
# +
return data # +++
class InvoiceView(QTextEdit):
dpi = 72
doc_width = 8.5 * dpi
doc_height = 6 * dpi
def __init__(self):
super().__init__(readOnly=True)
self.setFixedSize(QSize(self.doc_width, self.doc_height))
def build_invoice(self, data):
document = QTextDocument()
self.setDocument(document)
document.setPageSize(QSizeF(self.doc_width, self.doc_height))
document.setDefaultFont(font)
cursor = QTextCursor(document)
cursor.insertText(f"Customer Name: {data['c_name']}\n")
cursor.insertText(f"Customer Address: {data['c_addr']}\n")
cursor.insertText(f"Date: {data['i_date']}\n")
cursor.insertText(f"Total Due: {data['total_due']}\n")
# +
return document # +++
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
central = QWidget()
self.setCentralWidget(central)
layout = QHBoxLayout(central)
# + vvvv
self.invoiceForm = InvoiceForm(self) # + self
layout.addWidget(self.invoiceForm)
self.invoiceView = InvoiceView()
layout.addWidget(self.invoiceView)
# hide the widget right now...
self.invoiceView.setVisible(False)
self.invoiceForm.submitted.connect(self.showPreview)
def showPreview(self, data):
self.invoiceView.setVisible(True)
self.invoiceView.build_invoice(data)
# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
def printpreviewDialog(self):
previewDialog = QPrintPreviewDialog()
previewDialog.paintRequested.connect(self.printPreview)
previewDialog.exec_()
def printPreview(self, printer):
# self.invoiceView.build_invoice.print_(printer)
data = self.invoiceForm.on_submit()
document = self.invoiceView.build_invoice(data)
document.print_(printer)
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
def main():
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
if __name__ == '__main__':
main()

Related

Dynamically create a PYQT5 UI form from a dictionary and then update UI with a new dictionary

Summary:
I am trying to make a pyqt5 UI that reads in a dictionary from a json file and dynamically creates an editable form. I would then like to be able to change the json file and have my form update.
What I've tried:
I think the simplest solution would be just to somehow destroy the ui and re-initialize the ui with a new dictionary, I have tried to understand this post, but I am not to sure how to modify this answer to reboot and then instantiate a new UI class with a new dictionary?
I have also read a few posts like this post which I think I can use to first delete all my widgets, then delete my layout and then re add new widgets and layouts from a new dictionary, but I wonder if this is just over complicating the problem?
Some example code:
import sys
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.addLayout(self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
if __name__ == "__main__":
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary1)
MainWindow.show()
sys.exit(test_app.exec_())
I suppose I am at the stage of learning where I probably don't know exactly the right questions to ask or how to describe terminology correctly, so I hope this makes sense.
As long as you're using a QFormLayout, you can consider using removeRow(), and do that before adding the new widgets (even the first time). Note that the layout has to be created outside that function, so that you can always reuse it as needed.
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QFormLayout,
QMainWindow, QLineEdit, QPushButton)
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary.copy()
self.setWindowTitle("Main Window")
central = QWidget(self)
self.setCentralWidget(central)
self.main_layout = QVBoxLayout(central)
self.form_layout = QFormLayout()
self.main_layout.addLayout(self.form_layout)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
while self.form_layout.rowCount():
self.form_layout.removeRow(0)
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.form_layout.addRow(key, self.dynamic_dictionary[key])
QApplication.processEvents()
self.adjustSize()
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
self.createDynamicForm()
As suggested in the comments, I tried out the Qtable widget. In this script the table builder refreshes, removes all table rows and then builds the table from a dictionary.
It builds the first table as a show event, although I don't think that's actually needed in the end. I thought it might be a way to have the table rebuild upon closing and reopening the window as suggested in the comments too. In the end the refresh button is enough on it's own I think, without closing the window.
The research I did was from a training video by Chris Zurbrigg on the the Table widget in pyside2.
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QMainWindow, QLineEdit, QPushButton,
QTableWidget, QTableWidgetItem, QHeaderView)
class TableEditUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('table edit')
self.setMinimumWidth(500)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.create_widgets()
self.create_layouts()
self.toggle = True
self.set_dictionary()
self.refresh_table()
self.create_connections()
def create_widgets(self):
self.table = QTableWidget()
self.table.setColumnCount(2)
self.table.setHorizontalHeaderLabels(["Attribute", "Data"])
header_view = self.table.horizontalHeader()
header_view.setSectionResizeMode(1, QHeaderView.Stretch)
self.toggle_dictionary_btn = QPushButton('toggle dictionary')
def refresh_table(self):
self.table.setRowCount(0)
self.dynamic_dictionary = {}
for count, (key, value) in enumerate(self.dictionary.items()):
print(count, key, value)
self.dynamic_dictionary[key] = QLineEdit(value)
self.table.insertRow(count)
self.insert_item(count, 0, str(key))
self.table.setCellWidget(count, 1, self.dynamic_dictionary[key])
def insert_item(self, row, column, text):
item = QTableWidgetItem(text)
self.table.setItem(row, column, item)
def showEvent(self, e):
super(TableEditUI, self).showEvent(e)
self.refresh_table
def create_layouts(self):
self.main_layout = QVBoxLayout(self.main_widget)
self.main_layout.addWidget(self.table)
self.main_layout.addWidget(self.toggle_dictionary_btn)
def create_connections(self):
self.toggle_dictionary_btn.clicked.connect(self.set_dictionary)
def set_dictionary(self):
self.toggle = not self.toggle
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
if self.toggle:
self.dictionary = dictionary1
else:
self.dictionary = dictionary2
self.refresh_table()
if __name__ == "__main__":
app = QApplication(sys.argv)
TableEditUIWindow = TableEditUI()
TableEditUIWindow.show()
sys.exit(app.exec())
Solution 1
I modify your codes in a way that delete the self.dynamiclayout and rebuild it. Just by using the link you have posted.
You just need to add a call of deleteItemsOfLayout(self.dynamiclayout) and then self.createDynamicForm() in your updateDictionary() function. Another small modification is changing self.main_layout.addLayout(self.dynamiclayout) in your createDynamicForm() function to self.main_layout.insertLayout(0, self.dynamiclayout) in order to keep the order of elements in the self.main_layout.
import sys
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
# copy from https://stackoverflow.com/a/45790404/9758790
def deleteItemsOfLayout(layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.setParent(None)
else:
deleteItemsOfLayout(item.layout())
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.insertLayout(0, self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
deleteItemsOfLayout(self.dynamiclayout)
self.createDynamicForm()
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
if __name__ == "__main__":
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary1)
MainWindow.show()
sys.exit(test_app.exec_())
Solution 2
You can also re-build the whole test_app. However,
In this way I think you must make the dictionary a global varibal instead of a property of the class JsonEditor. To be honest, I think this is not elegant.
In addition, the restart of the whole application may be unnecessary, perhaps you just need to close() the window and create a new one as suggested by #musicamante. I am not sure how to create it again. I wonder whether it's possible for a widget to close() itself and rebuild itself again, if not, you need to add the JsonEditor to parent widget and close/rebuild the JsonEditor there. I think it will be troublesome.
Note that the window will "flash"(disappear and appear again) in the solution 2.
import sys
from PyQt5.QtWidgets import QWidget, qApp
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.addLayout(self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
global dictionary
dictionary = dictionary2
qApp.exit(EXIT_CODE_REBOOT)
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
dictionary = dictionary1
EXIT_CODE_REBOOT = -11231351
if __name__ == "__main__":
exitCode = 0
while True:
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary)
MainWindow.show()
exitCode = test_app.exec_()
test_app = None
if exitCode != EXIT_CODE_REBOOT:
break
In fact, this is my first time to try to restart a Qt GUI. I referred to this answer, however, it didn't work directly as I stated in the comment below it. This answer works fine and is adopted in my answer. I also tried this answer, however, the application configuration stayed unchanged if using this implementation.

PyQt5: GUI crashes when I attempt to filter

I'm relatively new to PyQt5, so it may be a simple mistake, but here is my problem.
So my goal is to have a GUI where you can select filters and input data into these filters. Then click a "search" button and have a new window pop up with your filtered results.
The pop up window works perfectly fine when the dataframe has no filters applied, but as soon as I try to filter the dataframe, my GUI will crash after pressing "search".
Here is my code, the issue is in the "search_clicked" function. If you have any solutions or suggestions it would be extremely helpful.
I found out that it was because df was written before the class. After moving it to the init method and making it self.df, the code works fine
import sys
import csv
import pandas as pd
import numpy as np
from PyQt5 import QtCore, QtGui
from PyQt5.QtCore import QAbstractTableModel, QSortFilterProxyModel, Qt
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import (
QApplication,
QWidget,
QPushButton,
QRadioButton,
QHBoxLayout,
QVBoxLayout,
QLabel,
QLineEdit,
QListWidget,
QButtonGroup,
QTableView,
QGroupBox,
QDialog
)
def data_parser():
df = pd.read_csv("cleaned_crime_data.csv")
df.drop(["Unnamed: 0"], inplace = True, axis = 1)
return df
df = data_parser()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Crime Search")
#hbox0 & hbox1
self.v_button = QRadioButton("Violent")
self.nv_button = QRadioButton("Not Violent")
self.both_button = QRadioButton("Both")
self.filter_label = QLabel("Filter/s:")
self.date_button = QPushButton("Date")
self.day_button = QPushButton("Week Day")
self.month_button = QPushButton("Month")
self.year_button = QPushButton("Year")
self.time_button = QPushButton("Time of Day")
self.neighborhood_button = QPushButton("Neighborhood")
self.data_label = QLabel("Data:")
self.results = QTableView()
self.search = QPushButton("Search")
self.date_box = QLineEdit("2010-05-25")
self.day_box = QLineEdit("Friday")
self.month_box = QLineEdit("May")
self.year_box = QLineEdit("2010")
self.time_box = QLineEdit("Afternoon")
self.neighborhood_box = QLineEdit("Georgia Tech")
self.model = pandasModel(df)
self.results.setModel(self.model)
proxyModel = QSortFilterProxyModel(self)
proxyModel.setSourceModel(self.model)
self.results.setModel(proxyModel)
groupBox = QGroupBox()
groupBoxLayout = QVBoxLayout()
groupBox.setLayout(groupBoxLayout)
groupBoxLayout.addWidget(self.v_button)
groupBoxLayout.addWidget(self.nv_button)
groupBoxLayout.addWidget(self.both_button)
self.search.setFont(QFont("",25))
self.search.setAutoDefault(True)
self.both_button.setChecked(True)
self.date_box.setEnabled(False)
self.day_box.setEnabled(False)
self.month_box.setEnabled(False)
self.year_box.setEnabled(False)
self.time_box.setEnabled(False)
self.neighborhood_box.setEnabled(False)
self.date_button.pressed.connect(self.date_clicked)
self.day_button.pressed.connect(self.day_clicked)
self.month_button.pressed.connect(self.month_clicked)
self.year_button.pressed.connect(self.year_clicked)
self.time_button.pressed.connect(self.time_clicked)
self.neighborhood_button.pressed.connect(self.neighborhood_clicked)
self.search.pressed.connect(self.search_clicked)
###Layout####
hbox0 = QHBoxLayout()
hbox0.addWidget(self.filter_label)
# hbox0.addWidget(self.v_button)
# hbox0.addWidget(self.nv_button)
# hbox0.addWidget(self.both_button)
#hbox0.addWidget(groupBox)
hbox1 = QHBoxLayout()
hbox1.addWidget(self.date_button)
hbox1.addWidget(self.day_button)
hbox1.addWidget(self.month_button)
hbox1.addWidget(self.year_button)
hbox1.addWidget(self.time_button)
hbox1.addWidget(self.neighborhood_button)
hbox1.addWidget(groupBox)
# hbox1.addWidget(self.v_button)
# hbox1.addWidget(self.nv_button)
# hbox1.addWidget(self.both_button)
hbox2 = QHBoxLayout()
hbox2.addWidget(self.date_box)
hbox2.addWidget(self.day_box)
hbox2.addWidget(self.month_box)
hbox2.addWidget(self.year_box)
hbox2.addWidget(self.time_box)
hbox2.addWidget(self.neighborhood_box)
hbox2.addWidget(self.search)
hbox3 = QHBoxLayout()
hbox3.addWidget(self.data_label)
# hbox3.addWidget(self.search)
vbox1 = QVBoxLayout()
vbox1.addWidget(self.results)
vbox_main = QVBoxLayout()
vbox_main.addLayout(hbox0)
vbox_main.addLayout(hbox1)
vbox_main.addLayout(hbox2)
vbox_main.addLayout(hbox3)
vbox_main.addLayout(vbox1)
self.setLayout(vbox_main)
###Fucntions###
def date_clicked(self):
if self.date_box.isEnabled():
self.date_box.setEnabled(False)
else:
self.date_box.setEnabled(True)
def day_clicked(self):
if self.day_box.isEnabled():
self.day_box.setEnabled(False)
else:
self.day_box.setEnabled(True)
def month_clicked(self):
if self.month_box.isEnabled():
self.month_box.setEnabled(False)
else:
self.month_box.setEnabled(True)
def year_clicked(self):
if self.year_box.isEnabled():
self.year_box.setEnabled(False)
else:
self.year_box.setEnabled(True)
def time_clicked(self):
if self.time_box.isEnabled():
self.time_box.setEnabled(False)
else:
self.time_box.setEnabled(True)
def neighborhood_clicked(self):
if self.neighborhood_box.isEnabled():
self.neighborhood_box.setEnabled(False)
else:
self.neighborhood_box.setEnabled(True)
def search_clicked(self):
##This is the part that won't work##
# if self.date_box.isEnabled():
# df = df.mask((df["Occur Date"] == self.date_box.text)).dropna()
# if self.day_box.isEnabled():
# df = df.mask((df["Day Occured"] == self.day_box.text)).dropna()
# if self.month_box.isEnabled():
# df = df.mask((df["Month Occured"] == self.month_box.text)).dropna()
# if self.year_box.isEnabled():
# df = df.mask((df["Year Occured"] == self.year_box.text)).dropna()
# if self.time_box.isEnabled():
# df = df.mask((df["Time of Day"] == self.month_box.text)).dropna()
# if self.neighborhood_box.isEnabled():
# df = df.mask((df["Neighborhood"] == self.neighborhood_box.text)).dropna()
# if self.v_button.isChecked():
# df = df.mask((df["Violent"] == False )).dropna()
# if self.nv_button.isChecked():
# df = df.mask((df["Violent"] == True )).dropna()
self.launchPopup(df)
def launchPopup(self, dataframe):
pop = Popup(dataframe, self)
pop.show()
### Popup Window ###
class Popup(QDialog):
def __init__(self, dataframe, parent):
super().__init__(parent)
#self.setModal(True)
self.resize(950, 500)
self.view = QTableView()
self.view.setModel(pandasModel(dataframe))
vbox = QVBoxLayout()
vbox.addWidget(self.view)
self.setLayout(vbox)
### Pandas DataFrame ###
class pandasModel(QAbstractTableModel):
def __init__(self, data):
QAbstractTableModel.__init__(self)
self._data = data
def rowCount(self, parent=None):
return self._data.shape[0]
def columnCount(self, parnet=None):
return self._data.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
return None
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
return None
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
results.resize(800, 600)
results.show()

How to change the audio output device of QMediaPlayer in PySide2/PyQt5

How can I set the audio output of a QMediaPlayer to a specific output in Windows 7 and later?
This was really easy in PySide (using Phonon) but I can't find a way to do it in PySide2.
There are some related questions already, like this old but still not solved one, or this one that asks exactly what I want.
They are both in c++ and its difficult to convert it to PySide2.
The second one is answered with this code:
QMediaService *svc = player->service();
if (svc != nullptr)
{
QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
(svc->requestControl(QAudioOutputSelectorControl_iid));
if (out != nullptr)
{
out->setActiveOutput(this->ui->comboBox->currentText());
svc->releaseControl(out);
}
}
Another one with an attempt to python conversion didn't work also.
I tried to convert them to Python code, but the result was not successful.
Here is my minimal attempt:
import sys
from PySide2 import QtMultimedia
from PySide2.QtCore import QUrl, Qt
from PySide2.QtMultimedia import QMediaPlayer, QMediaContent
from PySide2.QtWidgets import (QPushButton, QSlider, QHBoxLayout, QVBoxLayout,
QFileDialog, QStyle, QApplication, QDialog, QComboBox)
class Window(QDialog):
def __init__(self):
super().__init__()
self.out_combo = QComboBox()
mode = QtMultimedia.QAudio.AudioOutput
devices = QtMultimedia.QAudioDeviceInfo.availableDevices(mode)
for item in [(dev.deviceName(), dev) for dev in devices]:
self.out_combo.addItem(item[0], item[1])
self.out_combo.currentIndexChanged.connect(self.out_device_changed)
openBtn = QPushButton('Open file')
openBtn.clicked.connect(self.open_file)
self.playBtn = QPushButton()
self.playBtn.setEnabled(False)
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playBtn.clicked.connect(self.play_file)
self.slider = QSlider(Qt.Horizontal)
self.slider.setRange(0, 0)
self.slider.sliderMoved.connect(self.set_position)
hor_layout = QHBoxLayout()
hor_layout.setContentsMargins(0, 0, 0, 0)
hor_layout.addWidget(openBtn)
hor_layout.addWidget(self.playBtn)
hor_layout.addWidget(self.slider)
ver_layout = QVBoxLayout()
ver_layout.addWidget(self.out_combo)
ver_layout.addLayout(hor_layout)
self.setLayout(ver_layout)
self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.player.stateChanged.connect(self.mediastate_changed)
self.player.positionChanged.connect(self.position_changed)
self.player.durationChanged.connect(self.duration_changed)
self.show()
def open_file(self):
file_name, _ = QFileDialog.getOpenFileName(self, "Open file")
if file_name != '':
self.player.setMedia(QMediaContent(QUrl.fromLocalFile(file_name)))
# self.label.setText(basename(file_name))
self.playBtn.setEnabled(True)
def play_file(self):
if self.player.state() == QMediaPlayer.PlayingState:
self.player.pause()
else:
self.player.play()
def mediastate_changed(self, state):
if self.player.state() == QMediaPlayer.PlayingState:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
def position_changed(self, position):
self.slider.setValue(position)
def duration_changed(self, duration):
self.slider.setRange(0, duration)
def set_position(self, position):
self.player.setPosition(position)
def out_device_changed(self, idx):
device = self.out_combo.itemData(idx)
service = self.player.service()
if service:
out = service.requestControl("org.qt-project.qt.mediastreamscontrol/5.0")
if out:
out.setActiveOutput(self.out_combo.currentText())
service.releaseControl(out)
else:
print("No output found!")
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())

PYQT Accessing and changing dynamically added controls

I am a beginner with GUI's and PYQT. What I am trying to do is dynamically set up a grid of QComboBox's and QLineEdit's. From the QComboBox you can select a choice and from that choice, it will fill in the corresponding QLineEdit with some numbers. The problem I'm having is creating the link between the first QComboBox and the first QLineEdit box. I could make a function for each row but I would like to know a better way. I will post some sample code. Thank you for any help or advice that you might have.
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 700, 600)
self.home()
def home(self):
Test1Choices = ['Test1:','Choice1', 'Choice2', 'Choice3', 'Choice4','Choice5', 'Choice6', 'Choice7', 'Choice8', 'Choice9']
Test2Choices= ['Test2:','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
for i in range(0,10):
Choice1ComboBox = QComboBox(self)
Choice1ComboBox.addItems(Test1Choices)
Choice1ComboBox.resize(150,25)
Choice1ComboBox.move(30,(150+(i*35)))
Choice1ComboBox.setCurrentIndex(2)
Choice2ComboBox = QComboBox(self)
Choice2ComboBox.setObjectName("Choice2ComboBox"+str(i))
Choice2ComboBox.addItems(Test2Choices)
Choice2ComboBox.resize(75,25)
Choice2ComboBox.move(200,(150+(i*35)))
Choice2ComboBox.setCurrentIndex(2)
Choice2ComboBox.activated[str].connect(self.doSomething)
numTextBox = QLineEdit(self)
numTextBox.setObjectName("numBox"+str(i))
numTextBox.move(325,(150+(i*35)))
numTextBox.resize(35,25)
result1TextBox = QLineEdit(self)
result1TextBox.setObjectName("result1Box"+str(i))
result1TextBox.move(400,(150+(i*35)))
result1TextBox.resize(100,25)
result1TextBox.setEnabled(0)
result2TextBox = QLineEdit(self)
result2TextBox.setObjectName("result2Box"+str(i))
result2TextBox.move(525,(150+(i*35)))
result2TextBox.resize(100,25)
result2TextBox.setEnabled(0)
self.show()
def doSomething(self):
numbers=['result1','result2','result3','result4','result5','result6','result7','result8','result9','result10','result11','result12','result13','result14','result15']
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()
To summarize I would like to bring in the index of the selected QComboBox. Then use that index number to reference the answer that is in the "numbers" array. Then print that result in the QLineEdit that is in the same row
We use sender() to get the object that emits the signal, then we look for the name of that object with setObjectName(), and we search the index, then we get the other objects with findChildren(), for example the output will be the union of the selected texts.
add name to Choice1ComboBox:
Choice1ComboBox.setObjectName("Choice1ComboBox"+str(i))
doSomething function:
def doSomething(self, _):
sender = self.sender()
l = sender.objectName().split("Choice1ComboBox")
if len(l) > 1:
number = l[1]
else:
number = sender.objectName().split("Choice2ComboBox")[1]
combo1 = self.findChildren(QComboBox, "Choice1ComboBox"+number)[0]
combo2 = self.findChildren(QComboBox, "Choice2ComboBox"+number)[0]
obj = self.findChildren(QLineEdit, "numBox"+number)[0]
obj.setText(combo1.currentText() + " " + combo2.currentText())
Complete code:
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 700, 600)
self.home()
def home(self):
Test1Choices = ['Test1:','Choice1', 'Choice2', 'Choice3', 'Choice4','Choice5', 'Choice6', 'Choice7', 'Choice8', 'Choice9']
Test2Choices= ['Test2:','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
for i in range(0,10):
Choice1ComboBox = QComboBox(self)
Choice1ComboBox.setObjectName("Choice1ComboBox"+str(i))
Choice1ComboBox.addItems(Test1Choices)
Choice1ComboBox.resize(150,25)
Choice1ComboBox.move(30,(150+(i*35)))
Choice1ComboBox.setCurrentIndex(2)
Choice1ComboBox.activated[str].connect(self.doSomething)
Choice2ComboBox = QComboBox(self)
Choice2ComboBox.setObjectName("Choice2ComboBox"+str(i))
Choice2ComboBox.addItems(Test2Choices)
Choice2ComboBox.resize(75,25)
Choice2ComboBox.move(200,(150+(i*35)))
Choice2ComboBox.setCurrentIndex(2)
Choice2ComboBox.activated[str].connect(self.doSomething)
numTextBox = QLineEdit(self)
numTextBox.setObjectName("numBox"+str(i))
numTextBox.move(325,(150+(i*35)))
numTextBox.resize(35,25)
result1TextBox = QLineEdit(self)
result1TextBox.setObjectName("result1Box"+str(i))
result1TextBox.move(400,(150+(i*35)))
result1TextBox.resize(100,25)
result1TextBox.setEnabled(0)
result2TextBox = QLineEdit(self)
result2TextBox.setObjectName("result2Box"+str(i))
result2TextBox.move(525,(150+(i*35)))
result2TextBox.resize(100,25)
result2TextBox.setEnabled(0)
self.show()
def doSomething(self, _):
sender = self.sender()
l = sender.objectName().split("Choice1ComboBox")
if len(l) > 1:
number = l[1]
else:
number = sender.objectName().split("Choice2ComboBox")[1]
combo1 = self.findChildren(QComboBox, "Choice1ComboBox"+number)[0]
combo2 = self.findChildren(QComboBox, "Choice2ComboBox"+number)[0]
obj = self.findChildren(QLineEdit, "numBox"+number)[0]
obj.setText(combo1.currentText() + " " + combo2.currentText())
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()

Update window every 5 minutes in Qt

Im looking to update the temp and humidity in this script.
from PyQt4.QtCore import Qt
from PyQt4.QtGui import QWidget, QApplication, QSplitter, QLabel, QVBoxLayout, QColor
import Adafruit_DHT
import urllib2
from BeautifulSoup import BeautifulSoup
sensor_args = { '11': Adafruit_DHT.DHT11,
'22': Adafruit_DHT.DHT22,
'2302': Adafruit_DHT.AM2302 }
humidity, temperature = Adafruit_DHT.read_retry(11, 4)
temp = 'Temp={0:0.1f}* Humidity={1:0.1f}%'.format(temperature, humidity)
soup = BeautifulSoup(urllib2.urlopen('http://partner.mlb.com/partnerxml/gen/news/rss/bos.xml').read())
title = soup.find('item').title
desc = soup.find('item').description
url = soup.find('item').guid
temperature = temperature * 9/5.0 + 32
class MyWidget(QWidget):
def __init__( self, parent = None ):
super(MyWidget, self).__init__(parent)
# create widgets
a = QLabel('Humidity:{:0.1f}%'.format(humidity),self )
a.setMinimumSize(100, 100)
b = QLabel('Temperature:{:0.1f}F'.format(temperature),self )
b.setMinimumSize(100, 100)
c = QLabel("Redsox News \nTitle: %s\nSummary: %s " % (title.text, desc.text), self)
c.setWordWrap(True)
c.setMinimumSize(280, 200)
d = QLabel("This is some bullshit wordwrap and i cant get it tow work", self)
d.setWordWrap(True)
d.setMinimumSize(180, 300)
for lbl in (a, b, c, d):
lbl.setAlignment(Qt.AlignLeft)
# create 2 horizontal splitters
h_splitter1 = QSplitter(Qt.Horizontal, self)
h_splitter1.addWidget(a)
h_splitter1.addWidget(b)
h_splitter2 = QSplitter(Qt.Horizontal, self)
h_splitter2.addWidget(c)
h_splitter2.addWidget(d)
h_splitter1.splitterMoved.connect(self.moveSplitter)
h_splitter2.splitterMoved.connect(self.moveSplitter)
self._spltA = h_splitter1
self._spltB = h_splitter2
# create a vertical splitter
v_splitter = QSplitter(Qt.Vertical, self)
v_splitter.addWidget(h_splitter1)
v_splitter.addWidget(h_splitter2)
layout = QVBoxLayout()
layout.addWidget(v_splitter)
self.setLayout(layout)
#color widget code
palette = self.palette()
role = self.backgroundRole()
palette.setColor(role, QColor('black'))
self.setPalette(palette)
a.setStyleSheet("QLabel {color:yellow}")
b.setStyleSheet("QLabel {color:yellow}")
c.setStyleSheet("QLabel {background-color: black; color:white}")
d.setStyleSheet("QLabel {background-color: black; color:white}")
#self.setWindowFlags(Qt.CustomizeWindowHint)
timer=self.QTimer()
timer.start(5000)
timer.timeout.connect(self.temp.update)
def moveSplitter( self, index, pos ):
splt = self._spltA if self.sender() == self._spltB else self._spltB
splt.blockSignals(True)
#splt.moveSplitter(index, pos)
splt.blockSignals(False)
if ( __name__ == '__main__' ):
app = QApplication([])
widget = MyWidget()
widget.show()
app.exec_()
Ive been learning a lot about pyQt and all the ins and outs of it. Slow going i might add as i am very new to python.
What I would like is to have it so that this updates the temp and humidity every 5 minutes. I have tried this..
timer=self.QTimer()
timer.start(300)
timer.timeout.connect(self.temp.update)
This does not seem to work for me. I get the error no attribute QTimer.
(Note, I'm not really familiar with pyqt, so if this is wrong, please let me know and I'll delete the answer...)
The line
timer=self.QTimer()
is wrong. this is a QWidget subclass, which does not have QTimer attribute. In fact, QTimer is a regular Qt class, so that line should simply be:
timer = QTimer()
You also need the right import, of course, which I think is:
from PyQt4.QtCore import QTimer

Categories