Barcode scanning with PyQt5 - python

I have a usb barcode scanner which I am connecting to my computer. Everytime it scans a barcode, it types the data into the computer like a keyboard. My goal was to have the data be typed into PyQT5 Table widget.
I have created the table below and I simply scan the items into it. The problem is that when I scan an item, it edits the first cell, but the cursor does not move automatically to the next row so I can scan a new item into the table. I have to click on the second cell and then scan the item. Then click on the third cell and scan the item and so on.
I was wondering how I can automate it so that after an item is scanned into the first cell, it automatically moves to the next cell and waits for input from the scanner?
import sys
from PyQt5.QtWidgets import *
#Main Window
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'Specimen Dashboard'
self.setWindowTitle(self.title)
self.tableWidget = QTableWidget()
self.createTable()
self.tableWidget.itemChanged.connect(self.go_to_next_row)
self.layout = QVBoxLayout()
self.layout.addWidget(self.tableWidget)
self.setLayout(self.layout)
self.show()
def go_to_next_row(self):
#Not working
#Trying to see if I can automatically move to next cell, but editing it
self.tableWidget.setItem(1,0, QTableWidgetItem("Name"))
#Create table
def createTable(self):
self.tableWidget.setRowCount(4)
self.tableWidget.setColumnCount(2)
self.tableWidget.horizontalHeader().setStretchLastSection(True)
self.tableWidget.horizontalHeader().setSectionResizeMode(
QHeaderView.Stretch)
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())

By default the scanners send an endline("\n") that is translated a Return or Enter key and this by default closes the editor, in this case that event must be intercepted, move the cursor and open the editor:
import sys
from PyQt5 import QtCore, QtWidgets
class TableWidget(QtWidgets.QTableWidget):
def keyPressEvent(self, event):
if (
event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return)
and self.state() == QtWidgets.QAbstractItemView.EditingState
):
index = self.moveCursor(
QtWidgets.QAbstractItemView.MoveNext, QtCore.Qt.NoModifier
)
self.selectionModel().setCurrentIndex(
index, QtCore.QItemSelectionModel.ClearAndSelect
)
self.edit(index)
else:
super().keyPressEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.tableWidget = TableWidget(4, 2)
self.setCentralWidget(self.tableWidget)
self.tableWidget.horizontalHeader().setStretchLastSection(True)
self.tableWidget.horizontalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.Stretch
)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

You can subclass the table and overwrite closeEditor(): the hint argument tells the view what should happen when the editor has been closed; by default, when pressing Enter the current cell data is submitted, but you can override this behavior like this:
from PyQt5 import QtGui, QtWidgets
class Table(QtWidgets.QTableView):
# leave to False for the default behavior (the next cell is the one at the
# right of the current, or the first of the next row; when set to True it
# will always go to the next row, while keeping the same column
useNextRow = False
def closeEditor(self, editor, hint):
if hint == QtWidgets.QAbstractItemDelegate.SubmitModelCache:
if self.useNextRow:
super().closeEditor(editor, hint)
current = self.currentIndex()
newIndex = current.sibling(current.row() + 1, current.column())
if newIndex.isValid():
self.setCurrentIndex(newIndex)
self.edit(newIndex)
return
else:
hint = QtWidgets.QAbstractItemDelegate.EditNextItem
super().closeEditor(editor, hint)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
test = Table()
test.show()
model = QtGui.QStandardItemModel(10, 5)
test.setModel(model)
sys.exit(app.exec_())

Related

QComboBox show whole row of QTreeView

I want to use QtreeView to organize the data shown by a QComboBox. As you can see in my example, creating the box and setting up data works so far.
But my problem is, that the combobox itself only shows the first argument and not the whole line. what I want to have is, that there is shown the whole row, not only the first item of the row.
Is this maybe related to the fact, that each cell is selectable? Do I have to prohibit to select items at the end of the tree branch?
How can I achieve this while adding the elements to the QtreeView-data?
minimal example:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
data = [['a','b','c'],['d','e','f'],['g','h','i']]
class MainWindow(QMainWindow):
dispatcher = 0
def __init__(self):
super().__init__()
# buil UI
self.init_ui()
def init_ui(self):
# layout
self.box_window = QVBoxLayout()
# content
model = QStandardItemModel(len(data),len(data[0]))
row = 0
for r in data:
col = 0
for item in r:
model.setData(model.index(row, col), item)
col += 1
row += 1
tree_view = QTreeView()
tree_view.setHeaderHidden(True)
tree_view.setRootIsDecorated(False)
tree_view.setAlternatingRowColors(True)
combobox = QComboBox()
combobox.setMinimumSize(250,50)
combobox.setView(tree_view)
combobox.setModel(model)
self.box_window.addWidget(combobox)
self.box_window.addStretch()
# build central widget and select it
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.centralWidget().setLayout(self.box_window)
# show window
self.setGeometry(50,50,1024,768)
self.setWindowTitle("Test")
self.show()
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
A possible solution is to concatenate the texts in the row and set as the text to be painted:
class ComboBox(QComboBox):
def paintEvent(self, event):
painter = QStylePainter(self)
painter.setPen(self.palette().color(QPalette.Text))
# draw the combobox frame, focusrect and selected etc.
opt = QStyleOptionComboBox()
self.initStyleOption(opt)
values = []
for c in range(self.model().columnCount()):
index = self.model().index(self.currentIndex(), c, self.rootModelIndex())
values.append(index.data())
opt.currentText = " ".join(values)
painter.drawComplexControl(QStyle.CC_ComboBox, opt)
# draw the icon and text
painter.drawControl(QStyle.CE_ComboBoxLabel, opt)

How to customize Qtreewidget item editor in PyQt5?

I am making a QtreeWidget with item editable,but the problem is with the Item Editor or QAbstractItemDelegate(might be called like this,not sure).I am unable to change the stylesheet,actually i dont know how to do this.And also i want the selected lines(blue in editor) should be according to my wish.like below picture
here i want that blue selected line upto ".jpg",so that anyone cant change that ".jpg". Only ,one can change upto this".jpg"
Here is my code:
import sys
from PyQt5 import QtCore, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Edit')
self.button.clicked.connect(self.edittreeitem)
self.tree = QtWidgets.QTreeWidget()
self.tree.setStyleSheet('background:#333333;color:grey')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tree)
layout.addWidget(self.button)
columns = 'ABCDE'
self.tree.setColumnCount(len(columns))
for index in range(50):
item=QtWidgets.QTreeWidgetItem(
self.tree, [f'{char}{index:02}.jpg' for char in columns])
item.setFlags(item.flags()|QtCore.Qt.ItemIsEditable)
def edittreeitem(self):
getSelected = self.tree.selectedItems()
self.tree.editItem(getSelected[0],0)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setWindowTitle('Test')
window.setGeometry(800, 100, 540, 300)
window.show()
sys.exit(app.exec_())
You can create your own delegate that only considers the base name without the extension, and then set the data using the existing extension.
class BaseNameDelegate(QtWidgets.QStyledItemDelegate):
def setEditorData(self, editor, index):
editor.setText(QtCore.QFileInfo(index.data()).completeBaseName())
def setModelData(self, editor, model, index):
name = editor.text()
if not name:
return
suffix = QtCore.QFileInfo(index.data()).suffix()
model.setData(index, '{}.{}'.format(name, suffix))
class Window(QtWidgets.QWidget):
def __init__(self):
# ...
self.tree.setItemDelegate(BaseNameDelegate(self.tree))
The only drawback of this is that the extension is not visible during editing, but that would require an implementation that is a bit more complex than that, as QLineEdit (the default editor for string values of a delegate) doesn't provide such behavior.

revert rejected dialogue/form to show last accepted values on reopen

I'm setting up an "options" dialog in a program, where I can change some values and close the dialog with Ok/Cancel to accept of reject my changes. After closing the dialog with cancel and reopening it, i would like the last accepted values to be displayed, however I am know sure how to implement this.
Below is a very simplified version of my code. I chose to instanciate the dialog only once (as opposed to creating a new instance each time I call the dialog), mainly to avoid having to call the __init__ and import data from save files each time I open the dialog.
from PyQt5.QtWidgets import QMainWindow, QPushButton,\
QApplication, QTextEdit, QDialog, QDialogButtonBox
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
btn = QPushButton('open text 1', self)
btn.move(10, 10)
btn.clicked.connect(self.open_dlg)
self.txtdlg = TextDialog()
def open_dlg(self):
if self.txtdlg.exec_() == QDialog.Accepted:
print(self.txtdlg.preferences)
class TextDialog(QDialog):
def __init__(self):
super().__init__()
self.preferences = "text here"
self.resize(200, 150)
self.textedit = QTextEdit(self)
self.textedit.resize(200, 100)
self.textedit.setText(self.preferences)
btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
btns.move(20, 100)
btns.accepted.connect(self.save_and_close)
btns.rejected.connect(self.reject)
def save_and_close(self):
self.preferences = self.textedit.toPlainText()
self.accept()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
MW = MainWindow()
MW.show()
sys.exit(app.exec_())
As it is, after clicking Cancel the dialog keeps any unsaved changes to its widgets if I reopen it. My fist idea was to connect the cancel button to a close_without_saving method that updates the dialog to the last saved values before closing, but the displayed values will not be up to date if preferences is changed for some reason while the dialog is invisible. Can I run some code when i call exec_ ? Or is the logic behind my implementation wrong somehow?
You have to implement a method that sets the values of the dialog to the default values:
# ...
class MainWindow(QMainWindow):
# ...
def open_dlg(self):
self.txtdlg.reset()
if self.txtdlg.exec_() == QDialog.Accepted:
print(self.txtdlg.preferences)
class TextDialog(QDialog):
# ...
def reset(self):
self.preferences = "text here"
self.textedit.setText(self.preferences)
def save_and_close(self):
self.preferences = self.textedit.toPlainText()
self.accept()
# ...

Why does my QTableWidgetItems's rowHeight not get properly set on refresh?

I'm creating a table widget that I want to auto-refresh at certain intervals. The trouble I'm having is that refreshing the contents of the table is resetting their rowHeight property and ignoring the call to setRowHeight().
For example, I have a repeater class here running on a separate thread:
class RepeatedTimer(QtCore.QThread):
def __init__(self, obj):
super(RepeatedTimer, self).__init__(obj)
self.obj = obj
self.stop = False
def run(self):
while not self.stop:
time.sleep(2)
self.obj.refresh()
and it's being used in my QTableWidget like this:
from PySide import QtCore, QtGui
import sys, time
class TestTable(QtGui.QTableWidget):
def __init__(self, parent=None):
super(TestTable, self).__init__(parent)
self.setColumnCount(1)
self.thread = RepeatedTimer(self) # Create the auto-refresher thread
self.thread.start() # Start the thread
self.refresh()
def refresh(self):
print "Clearing table"
while self.rowCount():
self.removeRow(0)
for each in xrange(3):
self.insertRow(each)
text = str(time.time())
item = QtGui.QTableWidgetItem(text)
self.setItem(each, 0, item)
for row in xrange(self.rowCount()):
self.setRowHeight(row, 100) # This part is not behaving as expected
print 'Row %d height: %d' % (row, self.rowHeight(row))
def closeEvent(self, event):
print 'Stopping thread...'
self.thread.stop = True
self.thread.exit()
app = QtGui.QApplication(sys.argv)
test = TestTable()
test.show()
sys.exit(app.exec_())
If you run this, you'll see that each time the table refreshes, it clears all the contents, adds new items in each row, and sets all the row heights to 100. Except that last part. It is correctly looping through the rows because it prints each time. But for some reason it stops setting the row heights after the first loop.
Any ideas why this is happening?
It is not necessary to create a thread to update the QTableWidget, you could use a QTimer, on the other hand remove QTableWidgetItem and set them again is expensive so I recommend you update them.
import sys
import time
from PySide import QtCore, QtGui
class TestTable(QtGui.QTableWidget):
def __init__(self, parent=None):
super(TestTable, self).__init__(parent)
self.setRowCount(3)
self.setColumnCount(1)
for row in range(self.rowCount()):
for col in range(self.columnCount()):
self.setItem(row, col, QtGui.QTableWidgetItem(str(time.time())))
for each in range(self.rowCount()):
self.setRowHeight(each, 100)
self.setColumnWidth(each, 250)
timer_refresh = QtCore.QTimer(self)
timer_refresh.timeout.connect(self.refresh)
timer_refresh.start(2000)
def refresh(self):
for row in range(self.rowCount()):
for col in range(self.columnCount()):
self.item(row, col).setText(str(time.time()))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
test = TestTable()
test.show()
sys.exit(app.exec_())
Sorry, but you do not need a separate thread for this task.
Use the QTimer class provides repetitive and single-shot timers.
Here is an example, sorry for me PyQt5
import sys
import time
#from PySide import QtCore, QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TestTable(QTableWidget):
def __init__(self, parent=None):
super(TestTable, self).__init__(parent)
self.setWindowTitle("QTableWidget setRowHeight ")
self.resize(530, 330);
self.setRowCount(3)
self.setColumnCount(1)
self.setHorizontalHeaderLabels(['time.time())',])
for each in range(3):
self.setRowHeight(each, 100)
self.setColumnWidth(each, 250)
self.my_qtimer = QTimer(self)
self.my_qtimer.timeout.connect(self.timerTick)
self.my_qtimer.start(1000) # msec
def timerTick(self):
for each in range(3):
item = QTableWidgetItem(str(time.time()))
self.setItem(0, each, item)
app = QApplication(sys.argv)
test = TestTable()
test.show()
sys.exit(app.exec_())

PyQt: QListView in connection with QTextEdit

I have a QListView and a QTextEdit on a form and I would like to get them working together, as follows: if a checkbox is checked, the index of the respective item in the QlistView should be displayed in tbe QTextEdit; if the checkbox is unchecked, the value should be deleted from the QTextEdit. The indexes should be displayed cumulatively, delimited by one character (say, a comma), eg., 0,1,3.
Conversely, if a value is typed in the the QTextEdit, the respective checkbox should be automatically checked (or none, in case the value entered does not correspond to any index in the QListView).
I attempted to catch the indices of the selected checboxes by attaching an handler to the clicked event of the QListView, as below:
<del>#QtCore.pyqtSlot(QtCore.QModelIndex)
def onclick(self, index):
editbox.setText(str(index.row()))</del>
but got the error message: "NameError: global name 'self' is not defined".
Any hints? Thanks in advance for any assistance you can provide!
Here is my complete test code:
EDIT: I changed the code below to deal properly with event handlers.
import sys
from PyQt4 import Qt, QtCore, QtGui
class MainWindow(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
model = QtGui.QStandardItemModel()
for n in range(10):
item = QtGui.QStandardItem('Item %s' % n)
item.setCheckState(QtCore.Qt.Unchecked)
item.setCheckable(True)
model.appendRow(item)
listview = QtGui.QListView()
listview.setModel(model)
listview.clicked.connect(self.onclick)
self.editbox = QtGui.QTextEdit()
self.editbox.textChanged.connect(self.onchange)
grid = QtGui.QGridLayout()
grid.setRowStretch(0, 6)
grid.setRowStretch(1, 4)
grid.addWidget(listview)
grid.setSpacing(2)
grid.addWidget(self.editbox)
self.setLayout(grid)
self.setGeometry(300, 150, 350, 300)
self.setWindowTitle("Example")
self.show()
##QtCore.pyqtSlot(QtCore.QModelIndex)
def onclick(self, index):
self.editbox.append(str(index.row()))
def onchange(self):
print "text in edit box changed"
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
sys.exit(app.exec_())
Since you're defining onclick outside of a class definition, there's no self (which refers to an instance of the class) either. Define it as a regular function instead:
#QtCore.pyqtSlot(QtCore.QModelIndex)
def onclick(index):
editbox.setText(str(index.row()))
and connect it to the signal as listview.clicked.connect(onclick).

Categories