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
Related
I'd like to be able to execute a callback when a QDialog is shown in PyQt4, preferably via the signal/slot mechanism. Looking at the PyQt documentation on QDialog, I can't find the correct signal to which to attach the slot that I want run.
What is a good way to do this?
If you want a signal to be emitted every time the dialog is shown, you could create a class like this:
class Dialog(QtGui.QDialog):
dialogShown = QtCore.pyqtSignal()
def showEvent(self, event):
super(Dialog, self).showEvent(event)
self.dialogShown.emit()
and then use it like this:
self.dialog = Dialog()
self.dialog.dialogShown.connect(self.handleDialogShown)
Would be fine in my opinion to override the show or showEvent method and put your callbacks there.
class MyDialog(QtGui.QDialog):
def show(self):
super(MyDialog, self).show()
callbacks()
Using PySide, I construct a draggable label that works exactly how I want:
class DraggableLabel(QtGui.QLabel):
def __init__(self, txt, parent):
QtGui.QLabel.__init__(self, txt, parent)
self.setStyleSheet("QLabel { background-color: rgb(255, 255, 0)}")
def mouseMoveEvent(self, event):
drag=QtGui.QDrag(self)
dragMimeData=QtCore.QMimeData()
drag.setMimeData(dragMimeData)
drag.exec_(QtCore.Qt.MoveAction)
(Note a full example that uses DraggableLabel is pasted below). Unfortunately, I do not understand what is going on with QMimeData, and fear I am going to run into big problems when I use similar code in real-world examples.
In particular, I am worried that my reimplementation of mouseMoveEvent creates an instance of QMimeData without any argument passed: QtCore.QMimeData(). Is this normal? Within more complex widgets will I be OK if I keep doing that within the relevant event handler: will the program automatically create the right type of MIME data for dragging and dropping?
The reason I fear I am missing something is because at the Qt Drag and Drop documentation, it has lines of code like:
mimeData -> setText(commentEdit->toPlainText());
which seems decidedly not like just letting the program take care of things within a reimplementation of an event handler.
Also, the QMimeData Documentation discusses convenience functions to test, get, and set data, but those are for standard data types (e.g., text, urls). I have found no clear way to define such convenience functions for widgets like my draggable QLabel. Am I missing it? Is there a simple way to find out if I am dragging around a widget of type X?
Edit: I've tried the same code above with much more complicated widgets than QLabels, and it does not work.
Potentially relevant posts:
Dragging a QWidget in QT 5
How to Drag and Drop Custom Widgets?
https://stackoverflow.com/questions/18272650/fill-the-system-clipboard-with-data-of-custom-mime-type
Python object in QMimeData
Important Caveat: if you just want to move a widget in a window, you do not need to invoke esoteric drag-drop mechanisms, but more vanilla event handling. See this: Dragging/Moving a QPushButton in PyQt.
Full working self-contained code example that incorporates the above:
# -*- coding: utf-8 -*-
from PySide import QtGui, QtCore
class LabelDrag(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.initUI()
def initUI(self):
self.lbl=DraggableLabel("Drag me", self)
self.setAcceptDrops(True)
self.setGeometry(40,50,200,200)
self.show()
def dragEnterEvent(self,event):
event.accept()
def dropEvent(self, event):
self.lbl.move(event.pos()) #moves label to position once the movement finishes (dropped)
event.accept()
class DraggableLabel(QtGui.QLabel):
def __init__(self, txt, parent):
QtGui.QLabel.__init__(self, txt, parent)
self.setStyleSheet("QLabel { background-color: rgb(255, 255, 0)}")
def mouseMoveEvent(self, event):
drag=QtGui.QDrag(self)
dragMimeData=QtCore.QMimeData()
drag.setMimeData(dragMimeData)
drag.exec_(QtCore.Qt.MoveAction)
def main():
import sys
qt_app=QtGui.QApplication(sys.argv)
myMover=LabelDrag()
sys.exit(qt_app.exec_())
if __name__=="__main__":
main()
Note I'm posting this at QtCentre as well, will post anything useful from there.
In my experience, MimeData is used to filter drag/drop operations so that the action actually makes sense. For instance, you shouldn't be able to drag your QLabel into the middle of a QTextEdit or your browsers address bar, or the desktop of your computer.
From the docs:
QMimeData is used to describe information that can be stored in the clipboard, and transferred via the drag and drop mechanism. QMimeData objects associate the data that they hold with the corresponding MIME types to ensure that information can be safely transferred between applications, and copied around within the same application.
If you were doing something standard, like dragging/dropping text from one place to another, you would use one of the standard MIME types (like setting the MIME data of your drag using dragMimeData.setText('your text') or equivalently dragMimeData.setData('text/plain', 'your text')). However, since you are doing something completely custom, you should probably specify a custom MIME type so that you can't accidentally do things that don't make sense.
So I would set the MIME data to something like dragMimeData.setData('MoveQLabel', QByteArray('any string you want')) which stores an arbitrary string for the MIME type MoveQLabel. This arbitrary string could be used to look up which widget you want to move at the end of the drag (maybe by storing it's position?).
You should modify your LabelDrag class above to check the MIME type of the event (use event.mimeData() to get the QMimeData object you set when you started the drag), and accept or reject the event depending on whether the MIME type matches MoveQLabel (or whatever you call your custom MIME type). This should be done in both the dragEnterEvent and dropEvent methods.
You code would look something like:
def dragEnterEvent(self, event):
# you may also need to check that event.mimeData() returns a valid QMimeData object
if event.mimeData().hasFormat('MoveQLabel'):
event.accept()
else:
event.reject()
If you also store some useful MIME data with the MIME type (aka something else instead of 'any string you want' above), you could use it to dynamically select, within LabelDrag.dropEvent, which widget it is that you want to move. You can access it through event.mimeData().data('MoveQLabel'). This means that your QWidget can handle moving multiple labels as it will always move the one that is being dropped.
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.
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.
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_())