PyQt QTableView with QComboBox - qt

I am displaying informations from a Database with a QTableView. I would like the fields to be displayed as a combobox so the user can modify them easily.
I read a lot of things about custom delegate items and flags having to be set to IsUserCheckable, but I don't understand how all of this is supposed to work.
I tried a couple of things with the flags and role, but with strictly no effect, so there really is something important that I am missing.
I would really appreciate a working code example of this, or at least some nice explanation if someone has that at hand :)

Here some example, but if you want something more advanced see QItemDelegate Class Reference.
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
table= QtGui.QTableWidget(5, 5)
self.setCentralWidget(table)
combobox = QtGui.QComboBox()
combobox.addItem('one')
combobox.addItem('two')
table.setCellWidget(3, 4, combobox)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Example()
window.setWindowTitle('ComboBox in Table Example')
window.resize(600, 400)
window.show()
sys.exit(app.exec_())

Related

Arduino Interfacing Within PyQt Widget

I have a PyQt program that hosts 2 widgets. The idea is to interface with my Arduino and display the Arduinos info in the program. I can't really attach my whole program but I'll give the highlights.
The Arduino takes a command over serial via ser.write and returns the subsequent information using ser.read()
So a simple function to continuously read the information from the Arduino would be
while True:
ser.write(command.encode()
time.sleep(.1)
resp=ser.read()
data=struct.unpack('<b',resp)
Now I want to use the information in data in my PyQt program however I cannot continuously run a loop in my Qt program because it will never display the program. I've tried using QThread by making a demo program however it crashes with error QThread: Destroyed while thread is still running. This is my demo program which should have similar functionality to the actual program.
import sys
import urllib
import urllib.request
import serial
import time
from PyQt4 import QtCore, QtGui
class CmdThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
BASIC="\x24\x4d\x3c\x00"
self.ser=serial.Serial()
#ser.port="COM12"
self.ser.port='COM12'
self.ser.baudrate=115200
self.ser.bytesize = serial.EIGHTBITS
self.ser.parity = serial.PARITY_NONE
self.ser.stopbits = serial.STOPBITS_ONE
self.ser.timeout = 0
self.ser.xonxoff = False
self.ser.rtscts = False
self.ser.dsrdtr = False
self.ser.writeTimeout = 2
self.ser.open()
print('Initializing in 10 seconds...')
time.sleep(10)
def run(self):
self.ser.write(self.BASIC.encode())
time.sleep(0.1)
resp=self.ser.read()
datalength=struct.unpack('<b',resp)[0]
data=self.ser.read(datalength+1)
data=data[4:-1]
temp=struct.unpack('<'+'h'*(int(len(data)/2)),data)
self.ser.flushInput()
self.ser.flushOutput()
print((temp[0]/10,temp[1]/10,temp[2]))
class MainWindow(QtGui.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.list_widget = QtGui.QListWidget()
self.button = QtGui.QPushButton("Start")
self.button.clicked.connect(self.start_cmd)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.list_widget)
self.setLayout(layout)
def start_cmd(self):
downloader = CmdThread()
downloader.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Can anyone explain why this is happening or maybe a solution on how to incorporate the simpler while loop into a Qt widget? Thanks so much!
You need to store a reference to the QThread so that it isn't garbage collected. Like this:
def start_cmd(self):
self.downloader = CmdThread()
self.downloader.start()
However be warned that clicking the button a second time will replace the original reference with a new one. And so the first thread may get garbage collected (which is probably fine if it is finished). You may want to eventually consider a more complex architecture where you always have a thread running and send commands from the main thread to the arduino thread via Qt signals/slots.
On another note, I assume at some point you will be using the results from the arduino to update a widget. Please make sure you don't directly access the widget from the thread (Qt GUI objects should only ever be accessed from the main thread). Instead you need to emit a signal (with a parameter that contains your data) from the thread which connects to a slot in the main thread. This slot is then able to safely access the GUI objects.
This shows a more complex example with two way communication between the main thread and a worker thread (albeit without sending data along with the signal emission, but that is a reasonably trivial change)

Don't auto focus QDialog fields

I am using QtDesigner (4.8.7) to build a QDialog which will serve as the main interaction point for a QGIS plugin I am creating. Most of the user input is entered through various QLineEdit fields, some of which have a placeholderText set.
Unfortunately, every time the QDialog is opened, a QLineEdit field is immediately selected (i.e. it receives focus), which causes the placeholderText to disappear in order to allow the user to enter text. Therefore, I'm wondering if it's possible to create a QDialog which does not automatically focus on any field.
This would allow the end user of my plugin to inspect the place holder texts before entering any value themselves.
I'm currently initializing the QDialog as follows:
import PyQt4.QtGui as QTG
import PyQt4.QtCore as QTC
from dialog_ui import Ui_dialog
class UI (object):
def __init__(self, iface):
# iface is just a way to interact with QGIS
self.iface = iface
self.container = QTG.QDialog()
self.ui = Ui_dialog()
self.setup()
def setup(self):
self.ui.setupUi(self.container)
# Custom ui setup code follows after this...
# Called by an external function
def show(self):
self.container.exec_()
Use the Tab Order Editing Mode in Qt Designer.
Start the tab-ordering on some widget that doesn't have placeholder text. That widget will get the initial focus.
Alternatively, just call setFocus() on an appropriate widget before showing the dialog.
I eventually went for a combination of ekhumoro's answer and the solution that I found here. The gist of the solution is to use clearFocus() on the QLineEdit to force it to lose focus.
I decided to make the QLineEdit lose focus when the user clicks on another location of the overarching QDialog. The code ends up looking like this:
import PyQt4.QtGui as QTG
import PyQt4.QtCore as QTC
from dialog_ui import Ui_dialog
class CustomDialog (QTG.QDialog):
def mousePressEvent(self, event):
focusWidget = QTG.QApplication.focusWidget()
if focusWidget:
focusWidget.clearFocus()
class UI (object):
def __init__(self, iface):
# iface is just a way to interact with QGIS
self.iface = iface
self.container = CustomDialog()
self.ui = Ui_dialog()
# Rest of the code remains unchanged

QWidget Geometry() not updating

I have QWidgets lined up in a QVBoxLayout inside a QScrollArea with setWidgetResizable(True)
When I resize one of the QWidgets, the QWidgets relocate themselves accordingly in the graphical user interface, but their geometry() property doesn't reflect that, geometry().x() and geometry().y() both remain the same before and after relocation.
The only method I have found so far to update the x() and y() coordinates is to hide() and show() the QScrollArea.
I have tried update(), updateGeometry() and repaint() without any success.
Here is a sample test code that I made to summarize the problem :
import sys
from PySide import QtGui, QtCore
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.showMaximized()
self.widget_window = QtGui.QWidget()
self.scrollarea = QtGui.QScrollArea()
self.v_layout = QtGui.QVBoxLayout()
self.test_1 = QtGui.QPushButton('Test 1')
self.test_2 = QtGui.QPushButton('Test 2')
self.v_layout.addWidget(self.test_1)
self.v_layout.addWidget(self.test_2)
self.widget_window.setLayout(self.v_layout)
self.scrollarea.setWidgetResizable(True)
self.scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scrollarea.setAlignment(QtCore.Qt.AlignCenter)
self.scrollarea.setWidget(self.widget_window)
self.setCentralWidget(self.scrollarea)
print(self.test_2.geometry().x())
print(self.test_2.geometry().y())
self.test_1.setFixedHeight(1000)
#uncommenting the following next two lines, solves the problem
#self.scrollarea.hide()
#self.scrollarea.show()
print(self.test_2.geometry().x())
print(self.test_2.geometry().y())
def main():
app = QtGui.QApplication(sys.argv)
main = MainWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
My questions are :
Is there a better solution ?
Isn't updateGeometry() supposed to actually update the geometry() ?
Why is this behavior happening ? (i.e why does it update graphically but not programmatically ?)
Can using hide() and show() successively cause some problems in other contexts (window flickering or something else...) ?
When you update the GUI, it's not instantaneous. It's performed when you give the control to the event loop. In this case, it's done after __init__ completes, which is obviously after all your prints. Therefore, you'll see the old position.
hide/show forces an update at that point. That's why you get correct values. However, this is not the best way, because you might observe flicker while hide/show does its job.
Better way would be telling the event loop to process events before continuing. This will ensure that the GUI is updated:
#...
print(self.test_2.geometry().x())
print(self.test_2.geometry().y())
self.test_1.setFixedHeight(1000)
QtGui.QApplication.processEvents()
print(self.test_2.geometry().x())
print(self.test_2.geometry().y())
#...
PS: I'm assuming, you have good reasons to do this. Most of the time, you won't care when the GUI is updated.

Detecting enter on a QLineEdit or QPushButton

I've built an app for a game, simple to start. It's a game in which the system randomly chooses a number and a gamer (player) tries to find out the number. Everything is almost done. The app consists of a QLineEdit, a label and three buttons. Once the app tells the player the range of the wanted number, he/she types a bet_number and clicks on the play button. And according to this number he/she gets a message about how close or far the wanted number is away from the bet_number.
But I find it a little disgusting to click a button. Instead I want to use Enter key to play. So to achieve this, it comes down to specifically two questions:
How could one change to using Enter to play (I mean I need know when QLineEdit detects enter key is pressed)? In this way I'll code properly to point the play method.
If the play button's got the focus, how do you use enter key on this button? (make Button accept Enter key)
For the QLineEdit connect to the returnPressed signal.
Alternatively, if you use the setAutoDefault method on your QPushButtons you emit the clicked signal when Enter is pressed on a focused QPushButton:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
from PyQt4 import QtGui, QtCore
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.pushButtonOK = QtGui.QPushButton(self)
self.pushButtonOK.setText("OK")
self.pushButtonOK.clicked.connect(self.on_pushButtonOK_clicked)
self.pushButtonOK.setAutoDefault(True)
self.lineEditNumber = QtGui.QLineEdit(self)
self.lineEditNumber.returnPressed.connect(self.pushButtonOK.click)
self.layoutHorizontal = QtGui.QHBoxLayout(self)
self.layoutHorizontal.addWidget(self.pushButtonOK)
self.layoutHorizontal.addWidget(self.lineEditNumber)
#QtCore.pyqtSlot()
def on_pushButtonOK_clicked(self):
inputNumber = self.lineEditNumber.text()
if inputNumber.isdigit():
info = "You selected `{0}`"
else:
info = "Please select a number, `{0}` isn't valid!"
print info.format(inputNumber)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
sys.exit(app.exec_())
QLineEdit will emit the signal returnPressed() whenever the user presses the enter key while in it: http://qt-project.org/doc/qt-4.8/qlineedit.html#signals. You can either connect this signal to your button's click() slot or directly call whatever your button's clicked() signal was connected to.
A slight C++ variation on other answers, it's not significantly different, but I thought i would include it anyway because how you lay things in QT code can be very different from codebase to codebase and I wanted to strip out the extraneous stuff to give the shortest and easiest to understand excerpt of code.
QLineEdit *TextSend = new QLineEdit("");
QPushButton *SendPB = new QPushButton("Send!");
connect(TextSend, &QLineEdit::returnPressed, this, &CLITab::SendCommand);
connect(SendPB, &QPushButton::released, this, &CLITab::SendCommand);
So what this is doing is we create a QLineEdit textbox and a QPushbutton.
We do cosmetic things like set the string label for them and add them to our layout.
Then we setup a callback handler, which will be triggered when the QLineEdit returns "returnPressed", which then calls automatically into a function which i wrote called "CLITab::SendCommand()", and then it's upto this function to extract the data out of QLineEdit and do whatever needs to be done. In practice the TextSend and SendPB pointers would live in the parent class, so that SendCommand() has visibility over these objects.
Just putting this here, along side an example pushbutton, because essentially they work in precisely the same way, all that's different is the signal name emitted.

Set bold rows in a QTreeView

I have a custom subclass of a QTreeView in a pyqt application. I'm trying to give the user the ability to highlight and "lowlight" (for lack of a better term) rows. Highlighted rows should have bold text and (optionally) a different background color. Any ideas?
I'm considering StyleSheets as an option, but have so far been unable to get that to work. If I set the QTreeView's stylesheet:
self.setStyleSheet("QTreeView::item:selected {border: 1px solid #567dbc;}")
I can't figure out how to manually enable 'states' that would keep only the desired rows at a particular state. If I try setting an individual item's stylesheet:
#modelIndex is a valid QModelIndex
modelIndex.internalPointer().setStyleSheet()
I get a segfault.
I'm not convinced stylesheets are the way to go, I'm open to all ideas. Thanks!
From what you've said it looks like the easiest solution would be to define a custom item delegate for your treeview and set items font weight to bold whenever it's needed. Pls, check if an example below would work for you, it should create a treeview with custom item delegate which would change item's font style.
import sys
from PyQt4 import QtGui, QtCore
class BoldDelegate(QtGui.QStyledItemDelegate):
def paint(self, painter, option, index):
# decide here if item should be bold and set font weight to bold if needed
option.font.setWeight(QtGui.QFont.Bold)
QtGui.QStyledItemDelegate.paint(self, painter, option, index)
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
model = QtGui.QStandardItemModel()
for k in range(0, 4):
parentItem = model.invisibleRootItem()
for i in range(0, 4):
item = QtGui.QStandardItem(QtCore.QString("item %0 %1").arg(k).arg(i))
parentItem.appendRow(item)
parentItem = item
self.view = QtGui.QTreeView()
self.view.setModel(model)
self.view.setItemDelegate(BoldDelegate(self))
self.setCentralWidget(self.view)
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
hope this helps, regards
I can think of a few ways to do this. The easiest, if you have access to the model, is to add some state tracking of the indexes in the model, and return the proper options for the roles requested in the data() function. The drawback to this is if you are using the same model in different views, and want to keep the highlights local to one view.
The second-easiest is probably to make a proxy model, which keeps track of the data itself, and gets all other data from the original model. In this situation (where you aren't changing the rows or columns of the original model) it would probably be pretty easy.
The hardest would be to make a custom delegate that keeps track of which rows/columns should be highlighted, and draws itself differently based on the row/column of the model index it is drawing. You would have to keep access to the delegate so that you could tell it which rows and columns need to be set. You would also need to deal with what happens when the model changes.

Resources