In my chart, I need to store many info in every QPointF, but when I subclass QPointF, it seems that it has no effect, can somebody give me good solution or idea?
The code is:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtPrintSupport import *
from PyQt5.QtChart import *
import random
class MyPoint(QPointF):
def __init__(self, *args):
super().__init__(*args)
def set_name(self, name):
self.name = name
def set_info(self, info):
self.info = info
class DemoChar(QChartView):
def __init__(self):
super().__init__()
self.setRenderHint(QPainter.Antialiasing)
self.chart = QChart()
self.chart.setTitle('Demo')
self.chart.setAnimationOptions(QChart.SeriesAnimations)
self.setChart(self.chart)
self.lineItem = QGraphicsLineItem(self.chart)
series = QLineSeries(name="random serie")
series.setPointsVisible(True)
series.clicked.connect(self.on_click)
self.series = series
for i in range(20):
#series << QPointF(0.1 * i, random.uniform(-10, 10))
pt = MyPoint( 0.1 * i, random.uniform(-10, 10) )
pt.set_name(str(pt))
pt.set_info(str(random.randint(1, 100)))
series << pt
self.chart.addSeries(series)
self.chart.createDefaultAxes()
def on_click(self, pt):
one_pt = self.series.pointsVector()[0]
print(one_pt)
# print(one_pt.name) #the point have no name, info attribute
app = QApplication([])
demo = DemoChar()
demo.show()
app.exec()
When you pass a QPointF to a QLineSeries, the copy constructor is used that only copies the x and y values, that is, it does not copy the object but only some attributes.
So instead of implementing a custom QPointF it is better to implement a model that is mapped to a QLineSeries using QVXYModelMapper:
import random
from PyQt5.QtCore import pyqtSlot, QPointF, Qt
from PyQt5.QtGui import QPainter, QStandardItem, QStandardItemModel
from PyQt5.QtWidgets import QApplication
from PyQt5.QtChart import QChart, QChartView, QLineSeries, QVXYModelMapper
class CustomModel(QStandardItemModel):
def add_point(self, pt, name="", info=""):
items = []
for value in (pt.x(), pt.y(), name, info):
it = QStandardItem()
it.setData(value, Qt.DisplayRole)
items.append(it)
self.appendRow(items)
def get_data(self, row):
if 0 <= row < self.rowCount():
pt = QPointF(
self.item(row, 0).data(Qt.DisplayRole),
self.item(row, 1).data(Qt.DisplayRole),
)
name = self.item(row, 2).data(Qt.DisplayRole)
info = self.item(row, 3).data(Qt.DisplayRole)
return pt, name, info
class DemoChar(QChartView):
def __init__(self, parent=None):
super().__init__(parent)
self.setRenderHint(QPainter.Antialiasing)
self.chart = QChart()
self.chart.setTitle("Demo")
self.chart.setAnimationOptions(QChart.SeriesAnimations)
self.setChart(self.chart)
self.model = CustomModel()
self.series = QLineSeries(name="random serie")
self.series.setPointsVisible(True)
self.series.clicked.connect(self.on_click)
self.mapper = QVXYModelMapper(xColumn=0, yColumn=1)
self.mapper.setModel(self.model)
self.mapper.setSeries(self.series)
for i in range(20):
pt = QPointF(0.1 * i, random.uniform(-10, 10))
name = "name-{}".format(i)
info = str(random.randint(1, 100))
self.model.add_point(pt, name, info)
self.chart.addSeries(self.series)
self.chart.createDefaultAxes()
#pyqtSlot(QPointF)
def on_click(self, pt):
# first point
index = 0
value = self.model.get_data(index)
if value is not None:
pt, name, info = value
print(pt, name, info)
def main(args):
app = QApplication(args)
demo = DemoChar()
demo.show()
ret = app.exec()
if __name__ == "__main__":
import sys
sys.exit(main(sys.argv))
Related
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 can I get audio input in real time from QAudioInput, store it in a NumPy array and pass it to SciPy FFT? What I have tried:
from PyQt5.QtMultimedia import QAudioDeviceInfo, QAudioFormat, QAudioInput
import sys
class Window(QMainWindow):
def __init__(self):
info = QAudioDeviceInfo()
input_device = info.defaultInputDevice()
if input_device.isNull():
# If no avaiable device is found, we display a error
print("There is no audio input device available.")
exit(-1)
audio_format = QAudioFormat()
audio_format.setSampleRate(44100)
audio_format.setSampleSize(8)
audio_format.setChannelCount(1)
audio_format.setCodec("audio/pcm")
audio_format.setSampleType(QAudioFormat.UnSignedInt)
if sys.byteorder == "little":
audio_format.setByteOrder(QAudioFormat.LittleEndian)
else:
audio_format.setByteOrder(QAudioFormat.BigEndian)
self.audioInput = QAudioInput(input_device, audio_format, self)
self.ioDevice = self.audioInput.start()
self.ioDevice.readyRead.connect(self.read_audio)
def read_audio(self):
data: QByteArray = self.ioDevice.readAll()
print(data.toUInt()) # Prints (0, False) which means error converting data
Inspired by the official example Audio Example I have created a QIODevice that allows obtaining the data. The following example takes the last N samples every T seconds by calculating its fft and displaying it using matplotlib.
import sys
import collections
from functools import cached_property
from PyQt5.QtCore import QIODevice, QObject, pyqtSignal, QTimer
from PyQt5.QtMultimedia import QAudioDeviceInfo, QAudioFormat, QAudioInput
from PyQt5.QtWidgets import QApplication, QMainWindow
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
from scipy.fft import fft, fftfreq
import numpy as np
FS = 44100
SAMPLE_COUNT = 2 * 1000
class AudioDevice(QIODevice):
data_changed = pyqtSignal(list, name="dataChanged")
def __init__(self, interval=1000, parent: QObject = None):
super().__init__(parent)
self.m_buffer = collections.deque(
[0 for _ in range(SAMPLE_COUNT)], maxlen=SAMPLE_COUNT
)
self.timer.timeout.connect(self.send_data)
self.timer.setInterval(interval)
self.timer.start()
#cached_property
def timer(self):
return QTimer()
def send_data(self):
self.data_changed.emit(list(self.m_buffer))
def readData(self, data, max_size):
return -1
def writeData(self, data):
max_size = len(data)
resolution = 4
start = 0
available_samples = int(max_size) // resolution
if available_samples < self.m_buffer.maxlen:
start = self.m_buffer.maxlen - available_samples
pos = 0
for _ in range(start, self.m_buffer.maxlen):
y = (1.0 * (data[pos] - 128)) / 128.0
self.m_buffer.append(y)
pos += resolution
return (self.m_buffer.maxlen - start) * resolution
class PlotWidget(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.canvas = FigureCanvas(Figure(figsize=(5, 3)))
self.setCentralWidget(self.canvas)
self.ax = self.canvas.figure.subplots()
self._line = None
def update_data(self, data):
T = 1 / FS
N = SAMPLE_COUNT
yf = fft(data)
xf = fftfreq(N, T)[: N // 2]
x = xf
y = 2.0 / N * np.abs(yf[0 : N // 2])
if self._line is None:
(self._line,) = self.ax.plot(x, y)
else:
self._line.set_data(x, y)
self.canvas.draw()
def main(args):
app = QApplication(args)
plot_widget = PlotWidget()
plot_widget.resize(640, 480)
plot_widget.show()
info = QAudioDeviceInfo()
input_device = info.defaultInputDevice()
if input_device.isNull():
print("There is no audio input device available.")
exit(-1)
audio_format = QAudioFormat()
audio_format.setSampleRate(FS)
audio_format.setSampleSize(8)
audio_format.setChannelCount(1)
audio_format.setCodec("audio/pcm")
audio_format.setSampleType(QAudioFormat.UnSignedInt)
if sys.byteorder == "little":
audio_format.setByteOrder(QAudioFormat.LittleEndian)
else:
audio_format.setByteOrder(QAudioFormat.BigEndian)
audio_input = QAudioInput(input_device, audio_format, None)
audio_device = AudioDevice(interval=100)
audio_device.data_changed.connect(plot_widget.update_data)
audio_device.open(QIODevice.WriteOnly)
audio_input.start(audio_device)
app.exec_()
if __name__ == "__main__":
main(sys.argv)
data.toUInt() converts whole byte array to one uint value - not what you want. To get sample values you can use either numpy.frombuffer or struct.unpack.
import numpy
def read_audio(self):
data = self.ioDevice.readAll()
values = numpy.frombuffer(data.data(), dtype=numpy.uint8)
or
import struct
def read_audio(self):
data = self.ioDevice.readAll()
fmt = "#{}B".format(data.size())
values = struct.unpack(fmt, data.data())
I added widget that shows waveform to demonstrate that samples actually reflect signal from microphone - not random numbers.
from PyQt5.QtMultimedia import QAudioDeviceInfo, QAudioFormat, QAudioInput
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget
from PyQt5.QtGui import QPainter, QPolygonF
from PyQt5.QtCore import QPointF
import sys
import numpy
class WaveWidget(QWidget):
def __init__(self, parent = None):
super().__init__(parent)
self._values = None
def setValues(self, values):
self._values = values
self.update()
def paintEvent(self, event):
if self._values is None:
return
painter = QPainter(self)
ys = self._values / 255 * self.height()
xs = numpy.linspace(0, self.width(), num = len(ys))
points = QPolygonF([QPointF(x,y) for x,y in zip(xs,ys)])
painter.drawPolyline(points)
class Window(QMainWindow):
def __init__(self):
super().__init__()
info = QAudioDeviceInfo()
input_device = info.defaultInputDevice()
if input_device.isNull():
# If no avaiable device is found, we display a error
print("There is no audio input device available.")
exit(-1)
audio_format = QAudioFormat()
audio_format.setSampleRate(44100)
audio_format.setSampleSize(8)
audio_format.setChannelCount(1)
audio_format.setCodec("audio/pcm")
audio_format.setSampleType(QAudioFormat.UnSignedInt)
if sys.byteorder == "little":
audio_format.setByteOrder(QAudioFormat.LittleEndian)
else:
audio_format.setByteOrder(QAudioFormat.BigEndian)
self.audioInput = QAudioInput(input_device, audio_format, self)
self.ioDevice = self.audioInput.start()
self.ioDevice.readyRead.connect(self.read_audio)
widget = WaveWidget()
self._widget = widget
self.setCentralWidget(widget)
def read_audio(self):
data = self.ioDevice.readAll()
values = numpy.frombuffer(data.data(), dtype=numpy.uint8)
self._widget.setValues(values)
if __name__ == "__main__":
app = QApplication([])
window = Window()
window.show()
app.exec()
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()
i used a class from stackoverflow that applies the drag and drop feature to table rows but it doesn't save images from being removed from cells when dragging and dropping any row i want some help figuring out how to fix the code to do this jop right !
the code of the whole project with database:
database used: https://gofile.io/d/c0srQ0
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout, QStyledItemDelegate, QCheckBox, QTableWidgetItem, QAbstractItemView, QTableWidget)
import sqlite3
import random
import ast
#import qdarkstyle
class Main(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.cashed = []
self.setWindowTitle('Arabic Math Project')
self.setContentsMargins(20,20,20,20)
# create table:
#Options()
#self.setStyleSheet(qdarkstyle.load_stylesheet())
layout = QVBoxLayout()
self.searchBar = QtWidgets.QLineEdit()
self.searchBar.setAlignment(QtCore.Qt.AlignCenter)
self.searchBar.setPlaceholderText("search")
self.searchBar.textChanged.connect(self.startSearchThreading) #editingFinished()
#self.table = QtWidgets.QTableWidget()
self.table = TableWidgetDragRows()
conn = sqlite3.connect("math.db")
conn.text_factory = bytes
self.cur = conn.cursor()
data = self.cur.execute("select * from problems;").fetchall();conn.close()
self.dims = (lambda x: (len(x), len(x[0])))(data) #(rows number, col number)
[self.table.insertRow(i) for i in [i for i in range(self.dims[0])]]
[self.table.insertColumn(i) for i in [i for i in range(self.dims[1]+1)]]
#changing h-header names .
self.table.setHorizontalHeaderLabels(["Unique", "Image", "Test", "Year", "Lesson", "Page","Comment", "Options", "Options-advanced"])
for c in range(self.dims[1]):
for r in range(self.dims[0]):
if c!=1:self.table.setItem(r, c, QtWidgets.QTableWidgetItem(data[r][c].decode("utf-8")))
else:self.table.setCellWidget(r, c, self.getImage(data[r][c]))
for r in range(self.dims[0]):self.table.setCellWidget(r, self.dims[1], Options(ops=[data[r][self.dims[1]-1].decode("utf-8")], row=data[r]))
#table h & v auto - resizing .
header = self.table.horizontalHeader()
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
header.setSectionResizeMode(2, QtWidgets.QHeaderView.Interactive)
header.setSectionResizeMode(7, QtWidgets.QHeaderView.Interactive)
headerv = self.table.verticalHeader()
headerv.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
#hide the options column cuz it doesn't concern user to see it.
self.table.setColumnHidden(7, True)
#layout setting up.
layout.addWidget(self.searchBar)
layout.addWidget(self.table)
self.setLayout(layout)
def getImage(self, image):
imageLabel = ScaledPixmapLabel()
pixmap = QtGui.QPixmap()
pixmap.loadFromData(image, "jpg")
imageLabel.setPixmap(pixmap)
return imageLabel
def startSearchThreading(self):
self.table.setRowCount(0)
self.update = updateTable(data= self.searchBar.text())
self.update.new_signal.connect(self.Search)
self.update.start()
def create(self):
self.createFormGroupBox()
return self.formGroupBox
#QtCore.pyqtSlot(int, int, bytes, bool, bytes)
def Search(self, r, c, d, newRowOrder, ops):
if newRowOrder:self.table.insertRow(self.table.rowCount()) # create new row.
if c!=1:self.table.setItem(r, c, QtWidgets.QTableWidgetItem(d.decode("utf-8")))
else:self.table.setCellWidget(r, c, self.getImage(d))
self.cashed.append(d)
if c ==self.dims[1]-1:
self.table.setCellWidget(r, self.dims[1], Options(ops=[ops.decode("utf-8")], row=self.cashed))
self.cashed = []
class updateTable(QtCore.QThread):
def __init__(self, parent=None, data=True):
super(QtCore.QThread, self).__init__()
self.data = data
new_signal = QtCore.pyqtSignal(int, int, bytes, bool, bytes)
def run(self):
currRow = 0
conn = sqlite3.connect("math.db")
conn.text_factory = bytes
self.cur = conn.cursor()
searched = self.cur.execute(" SELECT * FROM problems WHERE text LIKE ('%' || ? || '%') ", (self.data,)).fetchall()
dims = (lambda x: (len(x), len(x[0])))(searched) if searched!=[] else (0, 0)
for r in range(dims[0]):
for c in range(dims[1]):
self.new_signal.emit(r, c, searched[r][c], True if c==0 else False, searched[r][dims[1]-1])
class ScaledPixmapLabel(QtWidgets.QLabel):
def __init__(self):
super().__init__()
self.setScaledContents(True)
def paintEvent(self, event):
if self.pixmap():
pm = self.pixmap()
originalRatio = pm.width() / pm.height()
currentRatio = self.width() / self.height()
if originalRatio != currentRatio:
qp = QtGui.QPainter(self)
pm = self.pixmap().scaled(self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
rect = QtCore.QRect(0, 0, pm.width(), pm.height())
rect.moveCenter(self.rect().center())
qp.drawPixmap(rect, pm)
return
super().paintEvent(event)
class Options(QtWidgets.QWidget):
def __init__(self, parent=None,row=[], ops=[]):
super(Options, self).__init__(parent)
self.i = random.randint(1, 100)
self.row = [j.decode("utf-8") if i!=1 else "image" for i,j in enumerate(row)]
self.formLayout = QFormLayout()
self.setStyleSheet(open("styles.css").read())
ops = ast.literal_eval(ops[0]) if ops!=[] else {}
for i,j in ops.items():
widget = {"lineedit": QLineEdit(), "combobox": QComboBox(), "spinbox": QSpinBox(), "checkbox": QCheckBox()}
title = j.get("title", "")
combos = j.get("combos", [])
if i == "lineedit":widget[i].setText(j.get("default", ""))
elif i == "combobox":widget[i].addItems(combos)
elif i == "checkbox":
widget[i].setCheckState((lambda x: {1:2, 2:2, 0:0}[x])(j.get("default", 0)))
widget[i].setText(j.get("text", ""))
elif i == "spinbox":
widget[i].setMaximum(j.get("max", 100))
widget[i].setMinimum(j.get("min", 0))
self.formLayout.addRow(QLabel(title), widget[i])
self.btn = QtWidgets.QPushButton("Submit")
self.btn.clicked.connect(self.do)
self.formLayout.addWidget(self.btn)
self.formLayout.setFormAlignment(QtCore.Qt.AlignVCenter)
self.setLayout(self.formLayout)
def do(self):
#[STOPED HERE]
heads, tails = [], []
for i in range(self.formLayout.count()):
w = self.formLayout.itemAt(i).widget()
if i%2==0:heads.append(w.text())
elif isinstance(w, QLineEdit):tails.append(w.text())
elif isinstance(w, QComboBox):tails.append(w.currentText())
elif isinstance(w, QSpinBox):tails.append(w.value())
elif isinstance(w, QCheckBox):tails.append(w.checkState())
values = {i:j for i,j in zip(heads, tails)}
print(self.row[:-1]+[values])
class TableWidgetDragRows(QTableWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.viewport().setAcceptDrops(True)
self.setDragDropOverwriteMode(False)
self.setDropIndicatorShown(True)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
#self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setDragDropMode(QAbstractItemView.InternalMove)
def dropEvent(self, event: QDropEvent):
if not event.isAccepted() and event.source() == self:
drop_row = self.drop_on(event)
rows = sorted(set(item.row() for item in self.selectedItems()))
print(rows)
rows_to_move = [[QTableWidgetItem(self.item(row_index, column_index)) for column_index in range(self.columnCount())]
for row_index in rows]
for row_index in reversed(rows):
self.removeRow(row_index)
if row_index < drop_row:
drop_row -= 1
for row_index, data in enumerate(rows_to_move):
row_index += drop_row
self.insertRow(row_index)
for column_index, column_data in enumerate(data):
self.setItem(row_index, column_index, column_data)
event.accept()
for row_index in range(len(rows_to_move)):
self.item(drop_row + row_index, 0).setSelected(True)
self.item(drop_row + row_index, 1).setSelected(True)
super().dropEvent(event)
def drop_on(self, event):
index = self.indexAt(event.pos())
if not index.isValid():
return self.rowCount()
return index.row() + 1 if self.is_below(event.pos(), index) else index.row()
def is_below(self, pos, index):
rect = self.visualRect(index)
margin = 2
if pos.y() - rect.top() < margin:
return False
elif rect.bottom() - pos.y() < margin:
return True
# noinspection PyTypeChecker
return rect.contains(pos, True) and not (int(self.model().flags(index)) & Qt.ItemIsDropEnabled) and pos.y() >= rect.center().y()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.resize(600,600)
main.show()
app.exec_()
You are setting images using QLabels set as cell widgets. Setting cell widgets is only a feature used by the table UI: it is completely unrelated to the underlying model and the QTableWidgetItems know nothing about them also. So, it's completely up to you to check if a cell has a widget set and move that widget accordingly.
Semi pseudo-code:
def dropEvent(self, event: QDropEvent):
if not event.isAccepted() and event.source() == self:
drop_row = self.drop_on(event)
rows = sorted(set(item.row() for item in self.selectedItems()))
print(rows)
rows_to_move = []
images = []
for row_index in rows:
items = []
for column_index in range(self.columnCount()):
items.append(QTableWidgetItem(
self.item(row_index, column_index)))
widget = self.cellWidget(row_index, column_index)
if isinstance(widget, ScaledPixmapLabel):
images.append((row_index, column_index, widget.pixmap()))
rows_to_move.append(items)
# ...
for row, column, pixmap in widgets:
self.setCellWidget(row + drop_row, column,
ScaledPixmapLabel(pixmap))
As you can see I recreated the label instance each time, that's because when you set a cell widget, the table takes its ownership. While you could reparent the widget, the result might be undefined, so it's better to create a brand new instance based on the pixmap.
Tip: avoid using inline if/else or for loops: there's absolutely no benefit in doing it, and the only result is that it makes your code much less readable
I have created a table in PyQt5 and and retrieve the data in the cell by this method. But when i try the cell in QTableWidgetItem becomes None. So, how i can solve this problem and how i can insert other determined items in a specifice cell for exemple item 2, item3,... Thank you
import sys
from PyQt5.QtWidgets import (QWidget, QTableWidget, QHBoxLayout, QApplication, QTableWidgetItem)
from PyQt5.QtGui import QBrush, QColor
from PyQt5 import QtCore
data = {'111':['Title 1','121','94565','','','','684651','','','44651','','',''], '112':['Title 2','65115','','466149','46645','555641','','','','412045','98416','',''], '113':['Title 3','','','','466149','46645','555641','98656','','','412045','98416','','']}
class Table(QWidget):
def __init__(self, *args, parent=None):
super().__init__()
self.data = data
self.setuptUI()
k = 'str'
v = int
n = int
m = int
self.setdata(k, v, n, m)
def setuptUI(self):
self.setWindowTitle("QTableWidgetItem")
self.resize(1200, 800)
conLayout = QHBoxLayout()
self.tableWidget =QTableWidget(self)
self.tableWidget.setRowCount(55)
self.tableWidget.setColumnCount(14)
conLayout.addWidget(self.tableWidget)
def setdata(self, k, v, n, m):
global item
item = str(self.data.get(k))
for key in self.data:
if k in key:
item = self.data.get(k)[v]
print(item)
newItem = QTableWidgetItem(str(item))
newItem.setForeground(QBrush(QColor(255, 0, 0)))
self.tableWidget.setItem(23, 4, newItem)
if __name__ == '__main__':
app = QApplication(sys.argv)
windows = Table(data)
item1= Table()
item1.setdata(k="113", v=5, n=24, m=4)
#item2= Table()
#item2.setdata(k="113", v=5, n=25, m=4
windows.show()
sys.exit(app.exec_())
Try it:
import sys
from PyQt5.QtWidgets import (QWidget, QTableWidget, QHBoxLayout, QApplication, QTableWidgetItem)
from PyQt5.QtGui import QBrush, QColor
from PyQt5 import QtCore
data = {'111':['Title 1','121','94565','','','','684651','','','44651','','',''],
'112':['Title 2','65115','','466149','46645','555641','','','','412045','98416','',''],
'113':['Title 3','','','','466149','46645','555641','98656','','','412045','98416','','']}
class Table(QWidget):
def __init__(self, data): # data
super().__init__()
self.data = data
self.setuptUI()
# k = 'str'
# v = int
# n = int
# m = int
# self.setdata(k, v, n, m)
def setuptUI(self):
self.setWindowTitle("QTableWidgetItem")
self.resize(1200, 600)
conLayout = QHBoxLayout(self) # + self
self.tableWidget = QTableWidget(self)
self.tableWidget.setRowCount(55)
self.tableWidget.setColumnCount(14)
conLayout.addWidget(self.tableWidget)
def setdata(self, k, v, n, m):
# global item # ---
# item = str(self.data.get(k))
# for key in self.data:
# if k in key:
item = self.data.get(k)[v]
print(item)
newItem = QTableWidgetItem(str(item))
newItem.setForeground(QBrush(QColor(255, 0, 0)))
# self.tableWidget.setItem(23, 4, newItem)
self.tableWidget.setItem(n, m, newItem)
if __name__ == '__main__':
app = QApplication(sys.argv)
windows = Table(data)
#- item1= Table()
#- item1.setdata(k="113", v=5, n=24, m=4)
windows.setdata(k="113", v=5, n=24, m=4)
windows.setdata(k="113", v=6, n=24, m=5) # v=6, m=5
windows.show()
sys.exit(app.exec_())