Using Python 3.2x and PyQT 4.8x:
I initialized an action and assigned to a menu item:
self.__actionOpen = QtGui.QAction(self.__mw)
self.__actionOpen.setObjectName("actionOpen")
self.__actionOpen.setText("OpenFile")
QtCore.QObject.connect(self.__actionOpen, QtCore.SIGNAL("triggered()"), self.__accessFile)
self.__menuFile.addAction(self.__actionOpen)
Works fine - menu item is there with caption "OpenFile" and the action signal/slot is invoked.
I tried it with a QPushButton - same QAction object:
self.__buttonFile.addAction(self.__actionOpen)
Nothing: No caption on the button, nothing happens when it's clicked.
Do actions not work with QButton (the addAction call did not complain...)? Or is there something wrong with my code? Perhaps the "triggered()" signal is not appropriate for an action that interacts with QPushButton?
You can't assign a QAction to a QPushButton the way you want. QPushButton doesn't redefine addAction so the behavior comes from QWidget.addAction which adds the action to the context menu of the button.
You can however assign the action to a QToolButton with setDefaultAction which will change the button caption and trigger the action when clicked.
Or you could do it manually anyway by subclassing QPushButton and adding a setDefaultAction method that would change everything in the button according to the action (caption, tooltip...) and connects the relevant button's signals to the action's slots.
Adding an action won't "run" the action when the button is clicked, and that is by design.
If what you are after is to reuse or refer the QAction's behaviour you can just connect the clicked() signal of the QPushButton to the trigger() of the QAction:
QtCore.QObject.connect(self.__menuFile,
QtCore.SIGNAL("clicked()"),
self.__actionOpen.trigger)
That way the self.__actionOpen action will be triggered whenever the self.menuFile button is clicked.
My solution for this issue:
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QPushButton
class QActingPushButton(QPushButton):
"""QPushButtons don't interact with their QActions. This class triggers
every `QAction` in `self.actions()` when the `clicked` signal is emitted.
https://stackoverflow.com/a/16703358
"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.clicked.connect(self.trigger_actions)
#pyqtSlot()
def trigger_actions(self) -> None:
for act in self.actions():
act.trigger()
You could create a PushButtonAction:
h file:
#ifndef PUSHBUTTONACTION_H
#define PUSHBUTTONACTION_H
#include <QAction>
#include <QPushButton>
class PushButtonAction: public QPushButton
{
Q_OBJECT
public:
PushButtonAction(QAction *action, QWidget *parent = 0);
};
#endif // PUSHBUTTONACTION_H
cpp file:
#include "pushbuttonaction.h"
PushButtonAction::PushButtonAction(QAction *action, QWidget *parent):
QPushButton(parent)
{
setIcon(action->icon());
setText(action->text());
connect(this, SIGNAL(clicked()), action, SLOT(trigger()));
}
Related
I'm still not able to understand how to properly connect Qt_pushButton or Qt_LineEdit to methods. I would be so glad to get a explanation which even I do truly understand...
I've put together a pretty basic UI with Qt Designer. It contains a lineEdit called "lineEditTest" which I can indeed change by typing
self.lineEditTest.setText("changed Text")
However, I'm totally stuck with getting the text which the user entered back into my program. I would like to automatically submit the entered text into my function which sets a var to this value and returns it into my UI class. QLineEdit's signal editingFinished sounds perfect for that I guess? But it won't pass the text which the user entered into my function.
QLineEdit does have a property called "text" right? So it seems logical to me that I just have to pass another arg - apart from self - to my function called text.
My code does look like this but it obviously won't work at all:
from PyQt5 import QtWidgets, uic
import sys
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('test.ui', self)
self.lineEditTest.setText("test")
self.lineEditTest.editingFinished.connect(self.changeText(text))
self.show()
def changeText(self):
currentText = text
print (currentText)
return currentText
app = QtWidgets.QApplication(sys.argv)
window = Ui()
app.exec_()
This will just crash with:
NameError: name 'text' is not defined`
The problem seems to be that the OP doesn't understand the logic of the signals and slots (I recommend you check here). The signals are objects that emit information, and the slots are functions (generally callable) that are connected to the signals to receive that information. And the information transmitted by the signal depends on each case, for example if you check the docs of editingFinished signal:
void QLineEdit::editingFinished() This signal is emitted when the
Return or Enter key is pressed or the line edit loses focus. Note that
if there is a validator() or inputMask() set on the line edit and
enter/return is pressed, the editingFinished() signal will only be
emitted if the input follows the inputMask() and the validator()
returns QValidator::Acceptable.
That signal does not send any information so do not expect to receive any information except knowing that the edition has ended by the user. So how can I get the text? Well, through the text() method of QLineEdit:
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi("test.ui", self)
self.lineEditTest.setText("test")
self.lineEditTest.editingFinished.connect(self.changeText)
self.show()
def changeText(self):
text = self.lineEditTest.text()
print(text)
And how to do if the signal sends information? Then the slot (the function) that this connected will have as arguments to that information, for example if you use the textChanged signal that is emitted every time the text of the QLineEdit is changed, it should be as follows:
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi("test.ui", self)
self.lineEditTest.setText("test")
self.lineEditTest.textChanged.connect(self.changeText)
self.show()
def changeText(self, text):
print(text)
# or
# text = self.lineEditTest.text()
# print(text)
The way you're binding your callback isn't correct. When you bind a callback (and this is true for other frameworks, not just for binding callbacks in PyQt), you want to bind the function which should be triggered when a certain event occurs. That's not what you're doing - you're explicitly invoking the callback self.changeText(text) (note: the parentheses invoke the function). So, you're invoking (calling) the function, evaluating it and placing the return value in ...editingFinished.connect().
Don't explicitly invoke the function. Just pass the function's name. This makes sense if you think about it: we're passing a callable object to connect - this is the function which should be called at a later point in time, when the editingFinished event occurs.
You also don't need to pass lineEditTest's text into the callback, since you can just get whatever text is in the widget via self.lineEditTest.text() from inside the callback.
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
from PyQt5 import uic
super(MainWindow, self).__init__()
uic.loadUi("mainwindow.ui", self)
self.lineEditTest.setPlaceholderText("Type something...")
self.lineEditTest.editingFinished.connect(self.on_line_edit_finished_editing)
#pyqtSlot()
def on_line_edit_finished_editing(self):
text = self.lineEditTest.text()
print(f"You finished editing, and you entered {text}")
def main():
from PyQt5.QtWidgets import QApplication
application = QApplication([])
window = MainWindow()
window.show()
return application.exec()
if __name__ == "__main__":
import sys
sys.exit(main())
In my code, I have 2 classes in 2 separate files. I have a signal testSignal, a button pushButton and I connected the button like this:
testSignal = QtCore.pyqtSignal()
pushButton.pressed.connect(buttonPressed)
def buttonPressed(self):
testSignal.emit()
Now what I want to do is "receive" the emitted signal in the class/file, but I'm somewhat in the dark on how emit() actually works. Does anyone have any links for guides for the emit() function or can help somehow?
Thanks
[Py]Qt signals must be declared at the class level, as they become "active" only whenever a new class instance is created. Also, they can only be used for classes that inherit from QObject (including QWidget subclasses), and using them with standard python object classes will not work.
class SomeWindow(QtWidgets.QWidget):
testSignal = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
# ...
pushButton.pressed.connect(self.buttonPressed)
# connect the custom signal with an *instance method*
self.testSignal.connect(self.someInstanceFunction)
def buttonPressed(self):
# emit the signal
self.testSignal.emit()
def someInstanceFunction(self):
print('hello from the instance!')
def someAnonymousFunction():
print('hello from outside!')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = SomeWindow()
# connect the signal with an anonymous function
window.testSignal.connect(someAnonymousFunction)
sys.exit(app.exec_())
Obviously the example above doesn't make a lot of sense, as custom signals are normally used to communicate between instances of (possibly different) classes, but that's just for explanation purposes. Also, you could "concatenate" signals (as long as their signature is compatible):
class SomeWindow(QtWidgets.QWidget):
testSignal = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
# ...
# emit the testSignal when the button emits its pressed signal
pushButton.pressed.connect(self.testSignal)
self.testSignal.connect(self.someInstanceFunction)
# ...
Note that if you want to do something when the user clicks a button, you should use clicked(), not the pressed() one and the difference is very important: it's common convention that a button is considered clicked only when the user presses and releases the mouse button while still in the button area, so that the "click" can be avoided if the mouse button is released outside it. If you connect to pressed(), the signal will be emitted as soon as the mouse button is just pressed down on the button, which is not considered a standard behavior.
Icons for various states of QPushbutton etc.
How do set the icon for a QPushButton or QToolbutton for when it is off and for when it is toggled.
btn1 = QPushButton("Test")
icon1 = QIcon("normal.png")
icon2 = QIcon("toggled.png")
# set the icon for when btn1.toggled returns True
# and when btn1.toggled returns False
Creating a QPushbutton with three states
I want to create a qpushbutton that can have three states. I am using the button in a media player I am creating. These are the states I want the button to have:
normal (repeat off)
toggled state 1 (repeat all)
toggled state 2 (repeat one)
Upon research I've realised I may have to override QAbstractButton.nextCheckState. The trouble is that the is no signature for the method in the documentation. I therefore have no idea on how to override it or even if the is a state property to set or modify.
Any help will be appreciated.
You can use a simple way of checking the checked flag of a pushbutton on the clicked() signal.
connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(buttonClicked(bool)));
And in your code, define a slot as buttonClicked(bool checked) and implement it as:
void MainWindow::buttonClicked(bool checked)
{
if ( checked )
ui->pushButton->setIcon(QIcon(":/on.png"));
else
ui->pushButton->setIcon(QIcon(":/off.png"));
}
This can be implemented for a QToolbutton as well accordingly.
And please note the icons in here are used from the resources. So, it's better to add your icons to the resources file.
I don't know about QAbstractButton.nextCheckState, but I suggest making use of Qt's signal/slot mechanism.
Whenever the state of repeat mode changes in the model, emit a signal like notifyModeChanged. Connect a slot to that signal in which the state (e.g. the icon) of the button is set as required.
I put together a quick example with everything contained in the MainWindow class. In a real application one would divide the code at least into model and view. The example is based on the auto-generated project of QtCreator's new project wizard. In Design view, I added a QPushButton.
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "ui_mainwindow.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0) :
QMainWindow(parent),
ui(new Ui::MainWindow),
icon1("icon1.png"),
icon2("icon2.png"),
icon3("icon3.png")
{
ui->setupUi(this);
ui->pushButton->setIcon(icon2);
connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::changeMode);
connect(this, &MainWindow::notifyModeChanged, this, &MainWindow::modeChanged);
}
~MainWindow() {
delete ui;
}
signals:
void notifyModeChanged();
public slots:
void changeMode() {
mode = (mode + 1) % 3;
emit notifyModeChanged();
}
void modeChanged() {
switch (mode) {
case 0: ui->pushButton->setIcon(icon1); break;
case 1: ui->pushButton->setIcon(icon2); break;
case 2: ui->pushButton->setIcon(icon3); break;
}
}
private:
Ui::MainWindow *ui;
QIcon icon1;
QIcon icon2;
QIcon icon3;
int mode{0};
};
#endif // MAINWINDOW_H
I have a problem. I am writing a simple app in Pyqt5. I am trying to do this block of code in PyQt:
QNetworkAccessManager manager;
QNetworkReply *response = manager.get(QNetworkRequest(QUrl(url)));
QEventLoop event;
connect(response,SIGNAL(finished()),&event,SLOT(quit()));
event.exec();
QString html = response->readAll();
But when I am trying to use "connect" IDE tells me that "MainWindow" don't have method. How can I do it ?? Please help
This is my code:
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent = None):
super(MainWindow, self).__init__()
# window settings
self.setWindowTitle("Hello world app")
# main layout
self.lay = QtWidgets.QVBoxLayout()
# main widgets
self.label = QtWidgets.QLabel("Enter URL:")
self.line = QtWidgets.QLineEdit()
self.label_conn = QtWidgets.QLabel("")
self.btn = QtWidgets.QPushButton("Connect")
self.btn.clicked.connect(self.btn_click)
# adding widgets to layout
self.lay.addWidget(self.label, alignment=QtCore.Qt.AlignBottom)
self.lay.addWidget(self.line)
self.lay.addWidget(self.btn)
self.lay.addWidget(self.label_conn, alignment=QtCore.Qt.AlignTop | QtCore.Qt.AlignCenter)
self.setLayout(self.lay)
self.connect()
The connect method belongs to the signal that you wish to connect to a specific slot, not to the MainWindow widget itself. (BTW, you should consider inheriting from QMainWindow instead.)
In your code, the MainWindow widget is not a signal, so does not have a connect method. Also, even if it did, you need to specify the slot to which you're trying to connect the signal, which is also missing.
In other words, you must declare a pyqtSignal, if you're not using a pre-existing one, and then connect it to the pyqtSlot of your choice. Whether this slot is pre-defined or a custom one is up to you.
Consider the following code snippet, which I tested in Python3:
#!/usr/bin/python3 -B
import sys
from PyQt5.QtWidgets import QApplication, QDialog, QPushButton
if __name__ == '__main__':
app = QApplication(sys.argv)
diag = QDialog()
diag.setWindowTitle('Signal Demo')
diag.resize(200,50)
btn = QPushButton(diag)
btn.setText('Close Dialog')
# connect button's clicked signal to dialog's close slot
btn.clicked.connect(diag.close)
diag.show()
diag.exec_()
Notice that the button's clicked signal, not the button, is what gets connected to the dialog's close slot, not the dialog itself.
EDIT 1:
Just noticed that the very code you've posted has an example of how to properly perform a connection.
If your code has not simply been copy-pasted from some other place, you should've noticed that you seem to know how to properly connect signals and slots already. This line plainly gives it away:
self.btn.clicked.connect(self.btn_click)
If your MainWindow does have a btn_click method, then it should get invoked after the QPushButton named btn gets clicked.
EDIT 2:
Based on your recent comment, you seem to simply be trying to translate a snippet for a larger application, so consider the following code:
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtCore import QEventLoop, QUrl
app = QApplication(sys.argv)
url = 'https://stackoverflow.com'
manager = QNetworkAccessManager()
response = manager.get(QNetworkRequest(QUrl(url)))
event = QEventLoop()
response.finished.connect(event.quit)
event.exec()
html = str(response.readAll()) # in Python3 all strings are unicode, so QString is not defined
print(html)
The code above was tested to work as expected.
PS: I did notice that some seemingly valid URLs were returning an empty response (e.g. http://sourceforge.net/), but others, such as the one above, worked fine. It seems to be unrelated to the code snippet itself.
I'm working in a classic QgraphicsView / QGraphicsScene / QGraphicsItem framework.
I'm declaring a context menu in the QgraphicsView:
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.__contextMenu)
and then later on:
# ---------------------------------------------------------------------------
def __contextMenu(self, position):
""" """
# ----> Creating Context menu -----------------------------------
oTreeMenu = QtGui.QMenu()
etc ...
then in the QGraphicsItem instancing class I use the contextMenuEvent like the following:
# ---------------------------------------------------------------------------
def contextMenuEvent(self, event):
""" """
# ----> Creating Context menu -----------------------------------
oTreeMenu = QtGui.QMenu()
The problem being that the QGraphicsItem event is completely overriden by the QGraphicsView's.
How should I proceed to get both of them ?
I did it in C ++ but I think it should help
In GraphicsView:
void MyGraphicsView::contextMenuEvent(QContextMenuEvent * event)
{
QGraphicsView::contextMenuEvent(event);
if(!event->isAccepted())
{
QMenu * menu = new QMenu;
menu->addAction("GraphicsView Action 1");
menu->addAction("GraphicsView Action 2");
menu->popup(event->globalPos());
}
}
In GraphicsItem:
void MyGraphicsItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * event)
{
QMenu *menu = new QMenu;
menu->addAction("Action 1");
menu->addAction("Action 2");
menu->popup(event->screenPos());
event->setAccepted(true);
}
Ok,I found a solution:
I'm using the QGraphicsScene contextMenuEvent instead of the 2 others.
Within the event I'm checking whether the mouse is above a QGraphicsItem or not, and I build
the corresponding menu.
I dont really like this solution as all my functions will be under the QGraphicsScene class and most of them will be concerning the item, not the scene.
So it's kinda messy, but it works.
Thanks in advance to anyone who has a better solution.