The slot detectClipboardUrl of QClipboard::dataChanged() was called twice sometimes when I copy url in Google Chrome's address bar in this code, tested with PyQt5.7,Python3.5 on Win7 32bit, also on Linux Mint 18,
while I need the slot to be called only once , is this a bug ? any solutions ?
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QTableView):
def __init__(self, parent=None):
super().__init__(parent)
self.clipboard = QApplication.clipboard()
self.clipboard.dataChanged.connect(self.detectClipboardUrl)
#pyqtSlot()
def detectClipboardUrl(self):
print(self.clipboard.text())
if __name__ == "__main__":
app = QApplication(sys.argv)
ui = MainWindow()
ui.show()
sys.exit(app.exec_())
If the changes are duplicates, you can do something like:
class MainWindow(QTableView):
def __init__(self, parent=None):
self.clipboard = QApplication.clipboard()
self._cb_last = hash(self.clipboard.text())
self.clipboard.dataChanged.connect(self.detectClipboardUrl)
#pyqtSlot()
def detectClipboardUrl(self):
text = self.clipboard.text()
cb_current = hash(text)
if cb_current != self._cb_last:
print('clipboard text changed:', text)
self._cb_last = cb_current
The reason for using hash is simply to avoid keeping very large strings in memory.
Alternatively, if the two signals arrive very close together, you could use a QTimer to block any changes that occur within a few milliseconds of the first one.
UPDATE:
As I suspected, the problem is caused by a bug in Chromium: see Issue 173691.
Related
I am trying to get my stdout displayed on a QTextEdit made via Qt Designer (PyQt5). Actually I made it work yet it doesn't show the info at the same time it was made. Instead it waits for the process to completely end and only then it shows all the information at once. I understand that this should be solved via threading. Also since the QTextEdit (itself) is a GUI element i need a different approach. I found the answer I was looking for here:
This question is referenced to:
Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread
#three_pineapples provided the answer.
My question is pretty much exactly the same, thus the answer is also correct. But my scenario is a little different and I'm having trouble making it work.
In all the threading answers I only see them using Classes. But the thing is in my main class I have a function that does all the stuff that would be printed on the QTextEdit. Sometimes it takes minutes to complete. I am looking for a way for the example code below to work using the answer provided by #three_pineapples.
Here is the example code:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_text.clicked.connect(self.test_write)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def test_write(self): # this is a long, complicated function. its nested in this class. I don't have a way to get it out as a different class.
print("something written")
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Is there a way to get the provided solution to work -directly- on the (test_write) function in my main class? How would I implement it?
To make it more clear, from the reference link "LongRunningThing" class is not available for me. The function that needs to run on a separate thread is within the main class (named Form() in the example code). Perhaps a nested class could be used that encapsulates the test_write function inside my main class? Is that even possible?
For this case you can use the native threading of python:
import sys
import threading
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
finished = pyqtSignal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_text.clicked.connect(self.start_task)
self.finished.connect(lambda: self.ui.pushButton_text.setEnabled(True))
def start_task(self):
var = self.ui.lineEdit.text()
self.thread = threading.Thread(target=self.test_write, args=(args, ))
self.thread.start()
self.ui.pushButton_text.setEnabled(False)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def test_write(self, *args):
var1 = args[0]
print("something written")
time.sleep(5) # simulate expensive task
print("something written ----")
self.finished.emit()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I realize this is a bit of a vague question, but I'm not sure how to make it more specific.
The skeleton for my code is below:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class FormWidget(QWidget):
def __init__(self, parent):
super(FormWidget, self).__init__(parent)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
form = FormWidget(self)
self.setCentralWidget(form)
[do a bunch of stuff with form]
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
Within the FormWidget class, there is a keyPressEvent() method that traps keyboard input, and on a specific key ('q'), calls this:
mainWindow.close()
Most of the time, that causes the app to exit, as desired. But occasionally (with no predictable pattern that I can discern), it causes a crash (dialog comes up that says 'python.exe has stopped working', with a 'Close program' button).
First question, I guess, is: Is this not the proper way to terminate a PyQt GUI app?
If it is, then second question is: Presumably, my code is misbehaving somewhere. But I'm not sure what sort of misbehavior to be looking for. What sort of mistake could I be making that would cause this?
Any thoughts appreciated.
/John
I'm using PyQt to build a simple IDE and getting weird errors if you load an empty file. A small example script is posted below:
#!/usr/bin/env python
import sys
from PyQt4 import QtGui
class TestApp(QtGui.QMainWindow):
def __init__(self, filename=None):
super(TestApp, self).__init__()
self._editor = QtGui.QPlainTextEdit()
self._editor.modificationChanged.connect(self._change_modified)
self.setCentralWidget(self._editor)
self._editor.setPlainText('a')
def _change_modified(self, have_change):
print(have_change)
if __name__ == '__main__':
a = QtGui.QApplication([])
app = TestApp()
app.show()
sys.exit(a.exec_())
As expected, this shows a window with a plain text editor. As soon as the setPlainText method is called, the editor emits two events: One modificationChanged event with changes=True, a second with changes=False.
A bit weird, but fine.
However, if you change setPlainText('a') to setPlainText(''), only a single event is emitted, this time with changes=True. Even worse, after telling the editor it's not modified with setModified(False), it insists it's been changed somehow.
Does anyone know what's causing this and how I can work around this issue?
Update: It seems to be a bug & also affects QPlainTextEdit.clear().
The workaround below places a wrapper around the QPlainTextEdit to fix clear() and setPlainText('').
#!/usr/bin/env python
import sys
from PyQt4 import QtGui
class TestApp(QtGui.QMainWindow):
def __init__(self, filename=None):
super(TestApp, self).__init__()
self._editor = PlainTextEdit()
self._editor.modificationChanged.connect(self._change_modified)
self.setCentralWidget(self._editor)
self._editor.setPlainText('')
def _change_modified(self, have_change):
print(have_change)
class PlainTextEdit(QtGui.QPlainTextEdit):
def clear(self):
self.selectAll()
cursor = self.textCursor()
cursor.removeSelectedText()
doc = self.document()
doc.clearUndoRedoStacks()
doc.setModified(False)
self.modificationChanged.emit(False)
def setPlainText(self, text):
if text:
super(PlainTextEdit, self).setPlainText(text)
else:
self.clear()
if __name__ == '__main__':
a = QtGui.QApplication([])
app = TestApp()
app.show()
sys.exit(a.exec_())
It's a Qt bug, and the straightforward workaround is to check for empty contents if modifications are indicated.
code like the following .After it running for about half a minute ,then close the window, then the "Python has stopped working" dialog pops up(you had better try more than once. )
I wonder why this happen ?any solution to this ?
Tested on Windows with PyQt4-4.11.3-gpl-Py3.4-Qt4.8.6-x32.exe
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowIcon(QIcon("./wa.png"))
self.createTrayIcon()
def createTrayIcon(self):
self.trayIcon = QSystemTrayIcon()
self.trayIcon.setIcon(self.windowIcon())
self.trayIcon.show()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
ui = MainWindow()
ui.show()
sys.exit(app.exec_())
I do my PyQt work on linux, so there might be differences, but usually I construct my MainWindow with the following call:
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
...
I can run your code in python3, but after I exit I get the Segmentation Fault. If I change the constructor to what I indicated above the Fault is not reproduced.
Hope this helps
There is an endless block when xml.etree.ElementTree.fromstring() function is called in the QThread. Also lots of other calls makes the QThread blocked like multiprocessing.Process().
Important to say that it is a pure block, no exception or break.
Here is the code (a little edited but same principle as the source):
from PyQt4.QtGui import *
from Ui_mainwindow import Ui_MainWindow
import sys
import xml.etree
class Bruton(QThread):
def __init__(self, mw):
super(Bruton, self).__init__(mw)
self.mw = mw
def run(self):
print("This message I see.")
tree = xml.etree.ElementTree.fromstring("<element>text</element>")
print("But this one never.")
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.init_bruton()
# When the form is shown...
def showEvent(self, arg1):
self.bruton.start()
def init_bruton(self):
self.bruton = Bruton(self)
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
The code as posted doesn't actually run, but with a couple minor changes it runs and works fine. Here is the code with modifications:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
import xml.etree.ElementTree
class Bruton(QThread):
def __init__(self, mw):
super(Bruton, self).__init__(mw)
self.mw = mw
def run(self):
print("This message I see.")
tree = xml.etree.ElementTree.fromstring("<element>text</element>")
print("But this one never.")
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.init_bruton()
# When the form is shown...
def showEvent(self, arg1):
self.bruton.start()
def init_bruton(self):
self.bruton = Bruton(self)
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
And here is the output:
$ python test.py
This message I see.
But this one never.
This is with Python 2.6.6, PyQt4 4.8.3, on Debian Unstable.
Can you try it in your environment and see if my modified example works for you? If so, you are on the road to a solution for your real code. =)
The code I've shown here is shortened (the source is divided to two files and __ini__.py). I noticed that the main module must be the module that starts the QApplication. So I added app.exec_() to the __init__.py which is the main module of my program.