I have written the following code snippet with an QLineEdit that can be edited by pushing the button "Add Text".
import sys
import os
from PyQt4 import QtGui
from PyQt4 import *
class SmallGUI(QtGui.QMainWindow):
def __init__(self):
super(SmallGUI,self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300,300,300,300)
self.setWindowTitle('Sample')
#One input
self.MyInput = QtGui.QLineEdit(self)
self.MyInput.setGeometry(88,25,110,20)
###############
QtCore.QObject.connect(self.MyInput,QtCore.SIGNAL("textChanged(bool)"),self.doSomething)
#Add Text
self.MyButton = QtGui.QPushButton(self)
self.MyButton.setGeometry(QtCore.QRect(88,65,110,20))
self.MyButton.setText('Add Text')
###############
QtCore.QObject.connect(self.MyButton,QtCore.SIGNAL("clicked(bool)"),self.addText)
self.show()
def addText(self):
self.MyInput.setText('write something')
def doSomething(self):
print "I'm doing something"
def main():
app = QtGui.QApplication(sys.argv)
sampleForm = SmallGUI()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
What I would like to do is to execute an action when the text of the QLineEdit is changed programmatically, i.e. by clicking the button 'Add Text', doing the following:
QtCore.QObject.connect(self.MyInput,QtCore.SIGNAL("textChanged(bool)"),self.doSomething)
The reason why I have used the signal "textChanged" is related to what the class documentation says, that is "this signal is also emitted when the text is changed programmatically, for example, by calling setText()."
However this does not work cause the print statement is not executed. Can anyone help me out with that?
The problem is that the signal is not textChanged(bool) because it takes a string argument, so it should probably be: textChanged(str).
To avoid this kind of errors you should use the new-style syntax for connecting signals:
self.MyInput.textChanged.connect(self.doSomething)
# or:
self.MyInput.textChanged[str].connect(self.doSomething)
This syntax has several advantages:
It is clearer
It's less verbose and more readable
It provides more error checking because if the signal doesn't exist it raises an error. With the old syntax no error is raised, but the signal isn't connected either and the result is the behaviour you have seen.
Related
OS: W10. This may be significant. If you have different results on a different platform, feedback would be helpful.
Here is an MRE. If you run it and go Ctrl+O, the menu labels become greyed. If you select a file in the QFileDialog by clicking the "Open" button or using its mnemonic (Alt+O), the open-file dialog is dismissed and the "Files" and "Help" menus become un-greyed.
However, if you go Ctrl+O again, and this time enter the name of a file in the "File name" box (QLineEdit), and then press Return, the dialog is dismissed (with a successful selection result) but the "Files" and "Help" menus remain greyed-out. It looks like this:
import sys, os
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Greying of menus MRE')
self.setGeometry(QtCore.QRect(100, 100, 400, 200))
menubar = QtWidgets.QMenuBar(self)
self.setMenuBar(menubar)
self.files_menu = QtWidgets.QMenu('&Files', self)
menubar.addMenu(self.files_menu)
self.help_menu = QtWidgets.QMenu('&Help', self)
menubar.addMenu(self.help_menu)
self.new_action = QtWidgets.QAction('&New', self)
self.files_menu.addAction(self.new_action)
self.open_action = QtWidgets.QAction('&Open', self)
self.files_menu.addAction(self.open_action)
self.open_action.setShortcut("Ctrl+O")
self.open_action.triggered.connect(self.open_file)
def focusInEvent(self, event ):
print('main_window focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('main_window focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('main_window activateWindow')
super().activateWindow()
def open_file(self):
print('open file')
main_window_self = self
# open_doc_dialog = QtWidgets.QFileDialog(self.get_main_window())
class OpenDocFileDialog(QtWidgets.QFileDialog):
def accepted(self):
print('accepted')
super().accepted()
def accept(self):
print('accept')
super().accept()
def close(self):
print('close')
super().close()
def done(self, r):
print(f'done r {r}')
# neither of these solves the problem:
# main_window_self.activateWindow()
# main_window_self.files_menu.activateWindow()
super().done(r)
def hide(self):
print(f'hide')
super().hide()
def focusInEvent(self, event ):
print('focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('activateWindow')
super().activateWindow()
open_doc_dialog = OpenDocFileDialog(self)
open_doc_dialog.setWindowTitle('Choose file')
open_doc_dialog.setDirectory(os.getcwd())
# we cannot use the native dialog, because we need control over the UI
options = open_doc_dialog.Options(open_doc_dialog.DontUseNativeDialog)
open_doc_dialog.setOptions(options)
open_doc_button = open_doc_dialog.findChild(QtWidgets.QDialogButtonBox).button(QtWidgets.QDialogButtonBox.Open)
lineEdit = open_doc_dialog.findChild(QtWidgets.QLineEdit)
# this does not solve the problem
# lineEdit.returnPressed.disconnect()
# lineEdit.returnPressed.connect(open_doc_button.click)
print(f'open_doc_button {open_doc_button}, lineEdit {lineEdit}')
# show the dialog
dialog_code = open_doc_dialog.exec()
if dialog_code != QtWidgets.QDialog.Accepted: return
sel_files = open_doc_dialog.selectedFiles()
print(f'sel_files: {sel_files}')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())
This problem can be understood, if not solved, with reference to this answer.
Note that this greying-out is not disablement. As explained in the above link, this has to do with "active/inactive states" of the menus (or their labels). The menus remain enabled throughout, although in this case it's impossible to know that while the open-file dialog is showing because it is modal. Clicking on one menu after the dialog has gone, or just hovering over it, is enough to un-grey them both...
The explanation, as I understand it, is that the "File name" box QLineEdit has a signal, returnPressed, which appears to activate something subtley different to the slot which is invoked when you use the "Choose" button. You can see I have experimented with trying to re-wire that signal, to no avail.
The method done of the QFileDialog appears to be called however the dialog closes (unlike close!), so I tried "activating" the main window... and then the individual QMenus... Doesn't work.
I am not clear how to get a handle on this "active state" business or why the slot connected to returnPressed is (seemingly) unable to give the "active state" back to the menus when the other slot manages to do so.
Edit
Searching on Musicamante's "unpolishing" suggestion led me to this:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
lineEdit.returnPressed.connect(return_pressed)
... unfortunately this doesn't work.
This looks like a possible Windows-related bug, since I can't reproduce it on Linux. As a work-around, you could try forcing a repaint after the dialog closes:
# show the dialog
dialog_code = open_doc_dialog.exec()
self.menubar.repaint()
Finally got it, thanks to Musicamante's suggestion:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
main_window_self.menubar.repaint()
lineEdit.returnPressed.connect(return_pressed)
... I actually tried this several times, just to make sure it was doing what was intended. So in fact, fortunately, no single-shot timer was needed in this case.
I was trying to analyse the sample code cited here: PyQt - QMessageBox
Here's the snippet:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Window(QMainWindow):
def __init__(self):
super().__init__()
w = QWidget()
b = QPushButton(self)
b.setText("Show message!")
b.clicked.connect(self.showdialog)
w.setWindowTitle("PyQt Dialog demo")
def showdialog(self):
msg = QMessageBox()
msg.setIcon(QMessageBox.Question)
# self.connect(msg, SIGNAL('clicked()'), self.msgbtn)
msg.buttonClicked.connect(self.msgbtn)
msg.exec_()
def msgbtn(self, i):
print("Button pressed is:", i.text())
if __name__ == '__main__':
app = QApplication([])
w = Window()
w.show()
app.exec_()
There are two ways of connecting signals to slots in PyQt. For buttons, it's:
QtCore.QObject.connect(button, QtCore.SIGNAL(“clicked()”), slot_function)
or
widget.clicked.connect(slot_function)
Using it the second way works fine: the msgbtn slot method is called as intended. However, if I try to change it to the more usual, 'PyQt-onic' way of connecting (i.e. the first one - I commented it out in the snippet), the slot method is never called. Could anyone please help me out with this?
the signal you pass to SIGNAL is incorrect, the QMessageBox does not have the clicked signal but the signal is buttonClicked (QAbstractButton *) so the correct thing is:
self.connect(msg, SIGNAL("buttonClicked(QAbstractButton *)"), self.msgbtn)
On the other hand that is not the PyQt-onic style, but the old style which is not recommended to use, but we recommend using the new style.
Old style:
self.connect(msg, SIGNAL("buttonClicked(QAbstractButton *)"), self.msgbtn)
New style:
msg.buttonClicked.connect(self.msgbtn)
For more detail read the docs.
I need to change the color of QPushButton, but an error occurred: "AttributeError: type object 'ProyectoTFM' has no attribute 'ui'".
I don't know hoy to acced to a ui variable from my thread.
This is my code:
import sys
import OpenOPC
import time
import threading
from proyectoQt import *
def actualizarDatosOPC():
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
ProyectoTFM.ui.AP08Button.setStyleSheet("background-color: red")
return
class ProyectoTFM(QtGui.QMainWindow):
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC)
threadQt.start()
def clienteOPC():
opc=OpenOPC.client()
opc.connect('Kepware.KEPServerEX.V6')
global itemsOPC
while 1:
itemsOPC = opc.read(opc.list('PLC.PLC.TAGS'))
time.sleep(5)
return
threads = list()
threadOPC = threading.Thread(target=clienteOPC)
threads.append(threadOPC)
threadOPC.start()
time.sleep(5)
if __name__== "__main__":
app=QtGui.QApplication(sys.argv)
myapp = ProyectoTFM()
myapp.show()
sys.exit(app.exec_())
threadOPC.__delete()
Sorry for my English and thanks.
It is not correct to modify the view from a different thread to the main one, a way to solve the problem without using QThread is to create a signal that connects to some slot that changes the color of the button. To be able to emit the signal from the new thread we must pass the object to him through the parameter args.
def actualizarDatosOPC(obj):
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
obj.sendChangeColor.emit()
return
class ProyectoTFM(QtGui.QMainWindow):
sendChangeColor = QtCore.pyqtSignal()
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
self.sendChangeColor.connect(lambda: self.ui.AP08Button.setStyleSheet("background-color: red"))
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC, args=(self,))
threadQt.start()
Even if you got this to work, you can't modify the UI from a thread directly.
A few things:
You never actually pass the UI to the function actualizarDatosOPC
so it doesn't know it exists.
Is there any reason you can't use PyQt's built in threading tools? If you are going to use PyQt it might make sense to buy into the whole framework.
def startTheThread(self):
self.threadQt = QThread()
d = actualizarDatosOPC(self)
d.moveToThread(self.threadQt)
self.threadQt.start()
def actualizarDatosOPC(widget):
.... widget.AP08Button.setStyleSheet("background-color: red")
If you do choose to go this route, I'd take a look at this thread which has a good example:
How to use QThread correctly in pyqt with moveToThread()?
Additionally, while the way you initialize your Window works, this is the more standard way to do it:
class ProyectoTFM(QMainWindow, Ui_MainWindow):
def __init__(self, parent):
# General Init Stuff
super(Login, self).__init__(parent)
self.setupUi(self)
After that, whenever you want to refer to something in the UI all you need to do is refer to self._____. For example, if you have a button named buttonA, self.buttonA would be the appropriate reference.
Edit:
As mentioned in another answer, the proper way to actually change the button color would be to emit a trigger that to the main thread which could then respond by changing the button color.
I'm a newbie to Python and PyQt. I've tried to manage closeEvent to ask before to close mainwindow, but this work well only from 'X' button. From the QMEssageBox created to ask the user, callEvent() it's called two times.
This is the relevant part of the code :
self.ui.actionChiudi.triggered.connect(self.close)
def closeEvent(self, event):
#check presence of data in the table
if self.csvViewer.rowCount() > 0:
print event # only to analyze the caller
#show a warning
Error = QtGui.QMessageBox()
Error.setIcon(QtGui.QMessageBox.Question)
Error.setWindowTitle('ATTENZIONE !!')
Error.setInformativeText(u"Sei sicuro di voler uscire?")
Error.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
ret = Error.exec_()
if ret == QtGui.QMessageBox.Ok:
event.accept()
else:
event.ignore()
else:
#close directly
event.accept()
'actionChiudi' is the menù item in the main menù.
For what I can understand, when use 'X' button, the function close() it's called only one time directly from mainwindow object, then close my app.
When use the menù item, the function create the new object 'QMessageBox' then call 'closeEvent()' one time for this object, then recall the same function for the mainwindow object. If this is correct, I don't know how to manage this.
Thanks in advance for any help!
thankyou for your tips about howto construct a good example of code. Sorry but I'm new there, then I did not succeed in making a good example. But, in the meantime, I was thinking about the problem and, at the end, I've solved this with a little trick.
I've added a global variable that count if you are closing the main window, then at the other calls to closeEvent() avoid to recall the test procedure.
I've tried to create a class for the generation of QMessageBox external to main class, then to override closeEvent() of this object, but don't work. The only way that I've found is with global variable. The program continue to call two times closeEvent (one for the QMessageBox and one for the mainwindow) but now, the second time ignore to recall the test. It's a trick, it's not elegant, but it works for me.
The piece of code now is this :
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
import os
import datetime
import inspect
import csv
import codecs
import pymssql
from PyQt4 import QtGui, QtCore, Qt
import mainwindow
#import _winreg only on Microsoft platforms
import platform
winos = True
if os.name == 'nt' and platform.system() == 'Windows':
import _winreg
#other stuff needed under Windows
import _mssql
import decimal
import uuid
winos = True
else:
winos = False
#other global variables
liccode = False
closemain = False
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
super(MainWindow, self).__init__(parent)
self.ui = mainwindow.Ui_MainWindow()
self.ui.setupUi(self)
# some stuff and widgets to visualize in the main window
#connettors to menù functions; leave only the closing one
self.ui.actionChiudi.triggered.connect(self.close)
#override of closeEvent - there I think, it's better to work to separate
#closeEvent for mainwindow from general closeEvent. But how?
def closeEvent(self, event):
global closemain
#make evaluation test to decide how to work. Close directly or no?
if self.csvViewer.rowCount() > 0 and not closemain:
print event
#show a warning
Error = QtGui.QMessageBox()
Error.setIcon(QtGui.QMessageBox.Question)
Error.setWindowTitle('WARNING !!')
Error.setInformativeText(u"Are you sure to leave?")
Error.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel)
ret = Error.exec_()
if ret == QtGui.QMessageBox.Ok:
closemain = True
event.accept()
else:
event.ignore()
else:
#close directly
event.accept()
#start application and create main
app = QtGui.QApplication(sys.argv)
my_mainWindow = MainWindow()
my_mainWindow.show()
sys.exit(app.exec_())
In any case, any advice is well accepted to make a better code. Thank you all!
I made a dialog with a simple QLineEdit and QbuttonBox (lineEdit and buttonBox respectively), now I'm trying to use what is in the line edit when I press OK. It simply comes out blank and doesn't print during go and prints "None" for the bottom of print(base). Surfed and found the text() but still no love. Any help is appreciated.
from PyQt4 import QtGui, QtCore
import sys
import x
class Dialog(QtGui.QDialog, x.Ui_Dialog):
def __init__(self):
super(Dialog, self).__init__()
self.setupUi(self)
global base
base = self.buttonBox.accepted.connect(self.go)
def go(self):
what = self.lineEdit.text()
return what
print(what)
app = QtGui.QApplication(sys.argv)
form = Dialog()
form.show()
app.exec_()
print(base)
The example code is mostly correct, except that the go() method is returning before it has a chance to print anything. So if you remove that line, it should work as expected, i.e:
class Dialog(QtGui.QDialog, x.Ui_Dialog):
def __init__(self):
super(Dialog, self).__init__()
self.setupUi(self)
self.buttonBox.accepted.connect(self.go)
def go(self):
what = self.lineEdit.text()
print(what)
Also, there is no point in grabbing the return value when you connect a signal to a handler. If the connection is invalid, it will just raise an error.
EDIT:
If you want to access the text of the line-edit from outside of the dialog, then you don't really need a signal. Just make sure the dialog blocks until the user has entered the text, and then access the line-edit directly:
dialog = Dialog()
if dialog.exec_() == QtGui.QDialog.Accepted:
text = dialog.lineEdit.text()
# do stuff with text...
else:
print('cancelled')