`QTextEdit` cursor becomes frozen after overriding its `dropEvent` - python

In a program, I want to accept drag and drop of a file into a QTextExit in order to edit certain parts of it.
The drag and drop works fine, however after a file is dropped in the QTextEdit, the QTextEdit's cursor becomes frozen (stops blinking and can no longer be moved).
Here is a minimal example:
After drag-and-drop:
(d&d works fine, but cursor freezes)
I can edit the content in the textEdit, but the cursor remains visible after the textEdit loses focus.
Code:
# -*- coding: utf-8 -*-
import sys
from PyQt5 import Qt
if __name__ == '__main__':
Application = Qt.QApplication(sys.argv)
from Drag_drop_window import Ui_Form # import QtDesigner file
class override_textEdit(Qt.QTextEdit): # override drop event for QTextEdit
drop_accepted_signal = Qt.pyqtSignal(str) # the signal sends the file name to other widget
def __init__(self):
super(override_textEdit,self).__init__()
self.setText("123")
self.setAcceptDrops(True)
def dropEvent(self, event):
if len(event.mimeData().urls())==1: # accept event when only one file was dropped
event.accept()
self.drop_accepted_signal.emit(event.mimeData().urls()[0].toLocalFile())
else:
Qt.QMessageBox.critical(self,"Accept Single File Only","Accept Single File Only",Qt.QMessageBox.Abort)
event.ignore()
class myWidget(Qt.QWidget):
def __init__(self):
super(myWidget, self).__init__()
self.main = Ui_Form()
self.main.setupUi(self)
self.main.textEdit = override_textEdit()
self.main.verticalLayout.addWidget(self.main.textEdit)
self.main.textEdit.drop_accepted_signal.connect(self.slot)
self.show()
def slot(self,filename):
self.main.lineEdit.setText(filename) # display file name in lineEdit
if __name__ == '__main__':
my_Qt_Program = myWidget()
my_Qt_Program.show()
sys.exit(Application.exec_())
Drag_drop_window.py generated by QtDesigner
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'Drag_drop_window.ui'
#
# Created: Sun Apr 5 17:53:03 2015
# by: PyQt5 UI code generator 5.3.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.verticalLayout = QtWidgets.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.lineEdit = QtWidgets.QLineEdit(Form)
self.lineEdit.setObjectName("lineEdit")
self.verticalLayout.addWidget(self.lineEdit)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))

I have found that adding the following four lines to the end of the dropEvent makes things work:
mimeData = QtCore.QMimeData()
mimeData.setText("")
dummyEvent = QtGui.QDropEvent(event.posF(), event.possibleActions(),
mimeData, event.mouseButtons(), event.keyboardModifiers())
super(override_textEdit, self).dropEvent(dummyEvent)
You've overridden the dropEvent method of the QTextEdit control, but you're not calling the overridden superclass method. I guess that there is some cleanup code in the overridden method that needs to run in order to sort out the issues you were having with the cursor. However, simply calling the superclass method with the drop event, i.e.
super(override_textEdit, self).dropEvent(event)
doesn't do what you are hoping for. This enters the URL of the dropped file into the text-edit control.
I didn't find that any combination of calling accept(), ignore() or setDropAction(Qt.IgnoreAction) before calling the superclass dropEvent had any effect. I suspect that the superclass method makes its own decisions on whether to accept or ignore the event, which may override what your subclass will have done.
So instead I create a 'fake' drag-and-drop event, identical to the one received except that the text data is empty, and pass this fake event onto the superclass. Of course, the superclass is welcome to insert this empty text somewhere if it wants to, but if it does it's not going to have any effect.

Related

How to call other functions while update image(opencv) on a label Pyqt5?

I want to update the label with images but when I start to update the label, nothing works until the updating is done for example I want to pause updating or closing the window. It worked perfect when I used tkinter but it doesn't work same method in Pyqt5. Here is the code I have
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QDialog, QMainWindow,QMessageBox,QGroupBox,QSlider,QPushButton,QRadioButton,QLabel,QCheckBox,QFrame,QWidget,QTabWidget,QProgressBar,QTextBrowser,QTableWidget
from PyQt5.QtCore import QCoreApplication, Qt, QSize,QThread, QRect,QSize
from PyQt5.QtGui import QIcon, QFont,QPixmap,QIcon
import cv2
import os
import glob
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(800, 800)
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(165, 125, 61, 16))
self.label.setObjectName("label")
self.btn1 = QPushButton(Form)
self.image_frame = QtWidgets.QLabel(Form)
self.btn1.setGeometry(QRect(350, 200, 111, 31))
self.btn1.clicked.connect(self.load_images_from_folder)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.btn1.setText(_translate("Form","Btn"))
def load_images_from_folder(self):
for filename in glob.glob('path/*.jpg'):
img = cv2.imread(filename, cv2.IMREAD_UNCHANGED)
img = cv2.resize(img,(200,200))
print(filename)
self.showimg(img)
def showimg(self,img):
self.image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
self.image = QtGui.QImage(self.image.data, self.image.shape[1], self.image.shape[0], QtGui.QImage.Format_RGB888).rgbSwapped()
self.image_frame.setPixmap(QtGui.QPixmap.fromImage(self.image))
self.image_frame.setGeometry(QRect(70, 310, 461, 441))
Form.update()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
After I clicked Btn label updates but I can't click Btn again until the updating is finished
In UI environments, control has to be returned as quickly as possible to the main thread. If you have lots of image processing, this means that the function that loads the image (which also calls the function that sets the image) will not release control to the main thread until it's finished.
If you don't, the result is that the UI will be completely frozen (no updates, no user input) until that function returns.
The solution is to use threads, but you also need to consider that UI elements are involved, and no access is allowed to UI elements from external threads. So, the actual solution is to use QThreads and custom signals: Qt signals are thread safe, and they are queued whenever two objects belonging to different threads are trying to communicate.
In this specific case, the thread will be a QThread with a custom signal (which has to be emitted whenever a new image is ready):
class ImageLoader(QtCore.QThread):
imageReady = QtCore.pyqtSignal(object)
def __init__(self):
super().__init__()
self.images = []
def run(self):
while True:
while self.images:
img = cv2.imread(self.images.pop(), cv2.IMREAD_UNCHANGED)
img = cv2.resize(img,(200,200))
self.imageReady.emit(img)
def loadImages(self, imageList):
# if you want to *clear* the current queue
self.images[:] = imageList
# otherwise, just append:
# self.images.extend(imageList)
The implementation then requires to create the thread instance and add the images to the queue in load_images_from_folder:
class Ui_Form(object):
def setupUi(self, Form):
# ...
self.imageLoader = ImageLoader()
self.imageLoader.imageReady.connect(self.showimg)
self.imageLoader.start()
def load_images_from_folder(self):
self.imageLoader.loadImages(glob.glob('path/*.jpg'))
Important notes:
editing files generated with pyuic is considered bad practice, as it almost always leads to unexpected behavior and bugs that are difficult to track; the comment in the header of those files is pretty clear: you should not edit them; instead, create those files, leave them as they are, and then import them in your main script, as explained in the official guidelines about using Designer;
accessing Form (Form.update()), which is fundamentally a global variable, is another bad practice; just like editing pyuic files, it's something that should never be done unless you really know what you're doing (and if you do know, you won't probably do it at all); in any case, setting the pixmap on a label automatically schedules an update, so that function call is completely useless since we're properly using threading, which allows the main event loop to request updates on the UI;
setting fixed geometries is yet another practice usually discouraged; learn how to use layout managers and how to use them in Designer instead; if you want the QLabel to have a fixed size, use setFixedSize();

Adding text to a QListView window generated

While teaching myself python3 wanting to send data to the terminal using print(message). This was relatively straight forward and i can generate the console list of CAN data (details of code omitted here, just have bare min code) using three lines:
import os #windows os only,
os.system('cls'),
print(myStr) (also not shown here in code, but it does work!)
Wanting to pretty it all up and start expanding on the data and displaying it, over the last week been learning qt5 designer and pyqt5 and pyuic to generate the GUI for python. So far so good and I can get the thread working to read the CAN port (could have as easily been a UART port for those not familiar with CAN bus) confirmed this with console window still receiving messages, and the GUI window shows up. Then I read a bunch about MVC and watched tutorials and that's were the wheels started to fall off. I cant get in my brain/head around how to load the data into my model to display to the listview window in the GUI. I think i might have the signal and slot correctly done (sure someone will correct me if that isn't the case) but am struggling to get the data in the correct presentation to be displayed from line ??????self.listView (str)
from PyQt5 import QtCore, QtGui, QtWidgets, QtSvg
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import pyqtSignal
import sys
import can
import threading
import time
class MainWindow(QMainWindow):
app = None
Form = None
receiveUpdateSignal = pyqtSignal(str)
def __init__(self,app):
super().__init__()
self.app = app
self.initTool()
self.initEvent()
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.listView = QtWidgets.QListView(Form)
self.listView.setGeometry(QtCore.QRect(40, 40, 256, 192))
self.listView.setObjectName("listView")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
def receiveCan(self):
while True:
try:
message = self.bus.recv(0.2)
if message is not None:
print(message)
#data = message.data
self.receiveUpdateSignal.emit("messge in")
except Exception as e:
if hasattr(e, 'message'):
print(e.message)
else:
print(e)
break
time.sleep(0.005)
def initTool(self):
self. bus = can.interface.Bus(bustype='pcan', channel='PCAN_USBBUS1', bitrate=250000)
return
def startReceive(self):
receiveProcess = threading.Thread(target=self.receiveCan)
receiveProcess.setDaemon(True)
receiveProcess.start()
def initEvent(self):
self.receiveUpdateSignal.connect(self.updateReceivedDataDisplay)
def updateReceivedDataDisplay(self,str):
if str != "":
try:
**??????self.listView (str)**
except Exception as e:
if hasattr(e, 'message'):
print(e.message)
else:
print(e)
return
def main():
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = MainWindow(app)
ui.setupUi(Form)
ui.startReceive()
Form.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
First of all do not use str, it is a reserved word in Python and its use is considered bad programming practice.
On the other hand in the case of QListView this is based on the MVC pattern, where the model has the information, the view shows it and the controller handles when and what information is displayed. In this case the model must be a class that inherits from QAbstractListModel, the easiest class to handle is QStandardItemModel, we create an object of that class and we establish it to QListView, after the information is added to the model, the model will internally notify the view causing it to update.
class MainWindow(QMainWindow):
...
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.listView = QtWidgets.QListView(Form)
self.listView.setGeometry(QtCore.QRect(40, 40, 256, 192))
self.listView.setObjectName("listView")
self.model = QtGui.QStandardItemModel() # <----
self.listView.setModel(self.model) # <----
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
...
def updateReceivedDataDisplay(self, text):
if text:
it = QtGui.QStandardItem(text)
self.model.appendRow(it)

Qt Designer UI (python) to JSON

I recently started using qt to build a python GUI. I have two problems I can't quite find the solutions to. the code below is a sample of what I need to build.
1: Check which radio button from a list of radio buttons in a vertical layout has been clicked. In the GUI it only selects one radio button out of all others available in the layout. How do I perceive which has been clicked?
2:I would like to add the clicked value to a JSON object but I believe that is a simple if statement of if this then that. Unless it's more complicated in which case please push me in the right direction.
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(596, 466)
self.verticalLayoutWidget = QtWidgets.QWidget(Dialog)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(180, 70, 61, 80))
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.that = QtWidgets.QRadioButton(self.verticalLayoutWidget)
self.that.setObjectName("that")
self.verticalLayout.addWidget(self.that)
self.thi = QtWidgets.QRadioButton(self.verticalLayoutWidget)
self.thi.setObjectName("thi")
self.verticalLayout.addWidget(self.thi)
self.sure = QtWidgets.QRadioButton(self.verticalLayoutWidget)
self.sure.setObjectName("sure")
self.verticalLayout.addWidget(self.sure)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.that.setText(_translate("Dialog", "that"))
self.thi.setText(_translate("Dialog", "this"))
self.sure.setText(_translate("Dialog", "sure"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
There is a nice way to solve this using Qt Designer, which allows you to group your buttons into a QButtonGroup, and then connect to its buttonClicked signal to get the button that was clicked.
All you need to do is, in Qt Designer, select all the buttons (using Ctrl+click), then right-click one of the buttons and select Assign to button group -> New button group. This will create a new button-group object and automatically add all the buttons to it.
After re-generating your gui module, you can then do somehting like this:
ui.radioButtonGroup.buttonClicked.connect(radioButtonClicked)
def radioButtonClicked(button):
print(button.text())
I think you need something like this (not tested)
# Set Default
self.thi.setChecked(True)
# create a signal
QtCore.QObject.connect(self.that,
QtCore.SIGNAL("toggled(bool)"),
self.radio_clicked)
then create a function
def self.radio_clicked(self):
print 'ive been clicked' # work from here

PyQt can't set a value for an object from a dialog in main window

I have a Main program that calls various Dialogs with their own GUIs. Basically what I want to do is to set up a value in Main for an object that is another class:
class ZoneManager(QMainWindow, mainWindow.Ui_zzzMainWindow):
def __init__(self):
QMainWindow.__init__(self)
mainWindow.Ui_zzzMainWindow.__init__(self)
.....
def cookie_find(self):
match = re.search('cookie_id=(.*?)\"', page).group(1)
rga = str(match)
print (match)
dialog = QDialog()
dialog.ui = rga_session.Ui_rga_sessionDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
dialog.ui.rgaSessionText.setText(rga) # <<<<I want to set the text into a QLineEdit object
but I can't.
The dialog is in a separate file and made it in QTDesigner with standard 2 methods:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_rga_sessionDialog(object):
def setupUi(self, rga_sessionDialog):
rga_sessionDialog.setObjectName("rga_sessionDialog")
self.rgaSessionText = QtWidgets.QLineEdit(rga_sessionDialog)
self.rgaSessionText.setGeometry(QtCore.QRect(110, 30, 261, 21))
self.rgaSessionText.setFocusPolicy(QtCore.Qt.ClickFocus)
self.rgaSessionText.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
self.rgaSessionText.setObjectName("rgaSessionText")
..........
def retranslateUi(self, rga_sessionDialog):
_translate = QtCore.QCoreApplication.translate
.....
Who I can I append that text that I found from Regex into "rgaSessionText" ? What I'm doing wrong? Thanks in advance
I think it's better to communicate between the mainwindow and other dialogs through the signal-slot way.
In your ZoneManager class, define:
settextsignal= pyqtSignal(str)
In your Ui_rga_sessionDialog class, define:
#pyqtSlot(str)
def textUpdate(self, rga):
self.rgaSessionText.setText(rga)
Then in your cookie_find method, after initializing the Ui_rga_sessionDialog, put:
self.settextsignal.connect(dialog.ui.textUpdate)
Then whenever you want to set the text, just call:
self.settextsignal.emit(text)

pyQt signals/slots with QtDesigner

I'm trying to write a program that will interact with QGraphicsView. I want to gather mouse and keyboard events when the happen in the QGraphicsView. For example, if the user clicks on the QGraphicsView widget I will get the mouse position, something like that. I can hard code it rather easily, but I want to use QtDesigner because the UI will be changing frequently.
This is the code that I have for the gui.py. A simple widget with a QGraphicsView in it.
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_graphicsViewWidget(object):
def setupUi(self, graphicsViewWidget):
graphicsViewWidget.setObjectName(_fromUtf8("graphicsViewWidget"))
graphicsViewWidget.resize(400, 300)
graphicsViewWidget.setMouseTracking(True)
self.graphicsView = QtGui.QGraphicsView(graphicsViewWidget)
self.graphicsView.setGeometry(QtCore.QRect(70, 40, 256, 192))
self.graphicsView.setObjectName(_fromUtf8("graphicsView"))
self.retranslateUi(graphicsViewWidget)
QtCore.QMetaObject.connectSlotsByName(graphicsViewWidget)
def retranslateUi(self, graphicsViewWidget):
graphicsViewWidget.setWindowTitle(QtGui.QApplication.translate("graphicsViewWidget", "Form", None, QtGui.QApplication.UnicodeUTF8))
The code for the program:
#!/usr/bin/python -d
import sys
from PyQt4 import QtCore, QtGui
from gui import Ui_graphicsViewWidget
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_graphicsViewWidget()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.graphicsView, QtCore.SIGNAL("moved"), self.test)
def mouseMoveEvent(self, event):
print "Mouse Pointer is currently hovering at: ", event.pos()
self.emit(QtCore.SIGNAL("moved"), event)
def test(self, event):
print('in test')
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
When I run this code, it gives me the opposite of what I want. I get the mouse position everywhere except for inside the QGraphicsView.
I'm sure it's a problem with my QObject.connect. But every time I go back and read about signals and slots it makes sense but I can't get it.
Please help, I've been banging my head for the past few days now. I'm sorry if this as been asked before but I've been through all the threads on this topic and I can't get anywhere.
Thanks
The signal must come from the QGraphicsView object that was defined in the ui.
You can create a class derived from QGraphicsView like this
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyView(QGraphicsView):
moved = pyqtSignal(QMouseEvent)
def __init__(self, parent = None):
super(MyView, self).__init__(parent)
def mouseMoveEvent(self, event):
# call the base method to be sure the events are forwarded to the scene
super(MyView, self).mouseMoveEvent(event)
print "Mouse Pointer is currently hovering at: ", event.pos()
self.moved.emit(event)
Then, in the designer:
right-click on the QGraphicsView then Promote to
write the class name in the Promoted Class Name field (e.g. "MyView"),
write the file name where that class is in the Header file field but without the .py extension,
click on the Add button and then on the Promote button.
And you can regenerate your file gui.py with pyuic4.

Categories

Resources