Overriding an event stops signal/slot from triggering - python

why overriding keyPressEvent() in QPlainTextEdit,
self.clientDataEdit.keyPressEvent = self.clientKeyPressEvent
stops the signal textChanged() in QPlainTextEdit from triggering
The clientKeyPressEvent() method triggers when keys are pressed but QPlainTextEdit stopped updating and textChanged() signal stopped triggering.
If required, I can try posting code but maybe this is something 'simple'

Short Answer:
Because they are removing the default behavior, and one of them was to emit that signal.
Long Answer:
Do not modify the methods by doing foo.bar = baz especially in the Qt bindings since they keep a cache of the used methods that can cause you these problems.
If you want to modify the behavior of a method without losing the default behavior then create a class that inherits and calls super from the method:
class PlainTextEdit(QPlainTextEdit):
def keyPressEvent(self, event):
super().keyPressEvent(event)
# TODO
In addition, Qt allows you to listen to the events without the need to override the methods using an event filter:
class Helper(QObject):
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self.widget.installEventFilter(self)
#property
def widget(self):
return self._widget
def eventFilter(self, obj, event):
if self.widget is obj and event.type() == QEvent.KeyPress:
print(event.key(), event.text())
return super().eventFilter(obj, event)
helper = Helper(self.clientDataEdit)

Related

How do I use the double click signal in the QLineEdit widget?

I have a QLineEdit widget and I want to use the double click event on it. How can I do that?
def __init__(self):
#... other codes
self.title = QLineEdit()
self.title.returnPressed.connect(self.lockTitle)
#like this -> 'self.title.doubleClicked.connect(self.unlockTitle)'
#... other codes
def lockTitle(self):
self.title.setDisabled(True)
def unlockTitle(self):
self.title.setDisabled(False)
A possible solution is to create a custom QLineEdit by creating a new signal that is emitted in the mouseDoubleClickEvent method, but the problem in your case is that the QLineEdit is disabled and that method is not invoked so instead of using that method you should use the event method:
class LineEdit(QLineEdit):
doubleClicked = pyqtSignal()
def event(self, event):
if event.type() == QEvent.Type.MouseButtonDblClick:
self.doubleClicked.emit()
return super().event(event)
self.title = LineEdit()
self.title.returnPressed.connect(self.lockTitle)
self.title.doubleClicked.connect(self.unlockTitle)

Syntax for checking child interactions in kivy

I am learning kivy from a book that involves making a paint app. The author at one point introduces new buttons on a canvas class (inherited from Widget) and says that we have to check if the click received by the application on the canvas, also lies on one of its children. In that case, we would neglect the former and act on the latter.
I thought of achieving this by looping through self.children, checking if the on_touch_down(touch) member function returns true for any child, and returning from the function if that was the case:
class CanvasWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.my_color = get_color_from_hex('#FF0000')
self.line_width=2
def on_touch_down(self, touch):
for child in self.children:
if(child.on_touch_down(touch)):
return
with self.canvas:
Color(rgba=self.my_color)
touch.ud['current_line'] = Line(points=(touch.x, touch.y), width=self.line_width)
And this works fine.
However, I don't follow and would like to understand his shorthand syntax:
class CanvasWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.my_color = get_color_from_hex('#FF0000')
self.line_width=2
def on_touch_down(self, touch):
#I don't understand the following line
if Widget.on_touch_down(self, touch):
return
#Widget refers to the parent class (I hope?) how does it check with the
#children?
with self.canvas:
Color(rgba=self.my_color)
touch.ud['current_line'] = Line(points=(touch.x, touch.y), width=self.line_width)
Any clarity is appreciated. Thanks!
The Widget class is the base class for all the kivy widgets, and it handles the propagation of touch events to its children via its on_touch_down() method. Rather than referencing the Widget base class directly, a better approach is to use the super() method, like this:
if super(CanvasWidget, self).on_touch_down(touch):
return True
This does the propagation and returns True (to stop the propagation) if any of the children want the propagation stopped. The code in Widget that does this is:
for child in self.children[:]:
if child.dispatch('on_touch_down', touch):
return True
Since it does not directly reference the Widget base class, the super() method is safer if you decide to change the base class of CanvasWidget.

How to bind a function to an Action from Qt menubar?

I'm using Python3 and PyQt5, make my widgets and windows in Qt Designer. What is more, I do not generate .py files from a .ui. I simply load it using next code:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('UI/Qt/source/MainWindow.ui', self)
So, I wanted to know, how do I bind menu bar actions to functions.
Is there any way I can do something like this?
self.getActionByName("actionTest_Action").connect(self.do_something)
It is not necessary to use findChild when using loadUi since this method adds the object to the attributes of the class using the objectName as a name, for example in this particular case a cleaner code than the other answer is:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('UI/Qt/source/MainWindow.ui', self)
self.actionTest_Action.triggered.connect(self.test)
def test(self):
print("Test")
So, answering my own question..
One way to do this, is to find an action by using FindChild(QAction, "ActionName") function, and then bind a function using connect() function
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
uic.loadUi('UI/Qt/source/MainWindow.ui', self)
action = self.findChild(QAction, "actionTest_Action")
action.triggered.connect(self.test)
def test(self):
print("Test")

Signaling between parent and child widgets in tkinter

I have a moderately complex GUI that I'm building for interacting with and observing some simulations. I would like to be able to continue to refactor and add features as the project progresses. For this reason, I would like as loose as possible a coupling between different widgets in the application.
My application is structured something like this:
import tkinter as tk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.instance_a = ClassA(self)
self.instance_b = ClassB(self)
# ... #
class ClassA(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ... #
class ClassB(tk.Frame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# ... #
def main():
application = Application()
application.mainloop()
if __name__ == '__main__':
main()
I would like to be able to perform some action in one widget (such as selecting an item in a Treeview widget or clicking on part of a canvas) which changes the state of the other widget.
One way to do this is to have the following code in class A:
self.bind('<<SomeEvent>>', self.master.instance_b.callback())
With the accompanying code in class B:
def callback(self): print('The more that things change, ')
The problem that I have with this approach is that class A has to know about class B. Since the project is still a prototype, I'm changing things all the time and I want to be able to rename callback to something else, or get rid of widgets belonging to class B entirely, or make instance_a a child of some PanedWindow object (in which case master needs to be replaced by winfo_toplevel()).
Another approach is to put a method inside the application class which is called whenever some event is triggered:
class Application(tk.Tk):
# ... #
def application_callback():
self.instance_b.callback()
and modify the bound event in class A:
self.bind('<<SomeEvent>>', self.master.application_callback())
This is definitely easier to maintain, but requires more code. It also requires the application class to know about the methods implemented in class B and where instance_b is located in the hierarchy of widgets. In a perfect world, I would like to be able to do something like this:
# in class A:
self.bind('<<SomeEvent>>', lambda _: self.event_generate('<<AnotherEvent>>'))
# in class B:
self.bind('<<AnotherEvent>>', callback)
That way, if I perform an action in one widget, the second widget would automatically know to to respond in some way without either widget knowing about the implementation details of the other. After some testing and head-scratching, I came to the conclusion that this kind of behavior is impossible using tkinter's events system. So, here are my questions:
Is this desired behavior really impossible?
Is it even a good idea?
Is there a better way of achieving the degree of modularity that I want?
What modules/tools can I use in place of tkinter's built-in event system?
My code in answer avoids the issue of class A having to know about internals of class B by calling methods of a handler object. In the following code methods in class Scanner do not need to know about the internals of a ScanWindow instance. The instance of a Scanner class contains a reference to an instance of a handler class, and communicates with the instance of ScannerWindow through the methods of Handler class.
# this class could be replaced with a class inheriting
# a Tkinter widget, threading is not necessary
class Scanner(object):
def __init__(self, handler, *args, **kw):
self.thread = threading.Thread(target=self.run)
self.handler = handler
def run(self):
while True:
if self.handler.need_stop():
break
img = self.cam.read()
self.handler.send_frame(img)
class ScanWindow(tk.Toplevel):
def __init__(self, parent, *args, **kw):
tk.Toplevel.__init__(self, master=parent, *args, **kw)
# a reference to parent widget if virtual events are to be sent
self.parent = parent
self.lock = threading.Lock()
self.stop_event = threading.Event()
self.frames = []
def start(self):
class Handler(object):
# note self and self_ are different
# self refers to the instance of ScanWindow
def need_stop(self_):
return self.stop_event.is_set()
def send_frame(self_, frame):
self.lock.acquire(True)
self.frames.append(frame)
self.lock.release()
# send an event to another widget
# self.parent.event_generate('<<ScannerFrame>>', when='tail')
def send_symbol(self_, data):
self.lock.acquire(True)
self.symbols.append(data)
self.lock.release()
# send an event to another widget
# self.parent.event_generate('<<ScannerSymbol>>', when='tail')
self.stop_event.clear()
self.scanner = Scanner(Handler())

PyQt5 log a signal

I am attempting to create a simple connection between a signal and slot but have found myself stuck trying to call an extra function. There are 2 options I have come to. Here is the code that calls my class.
(Option 1):
self.flip = MyClass("Flip Image")
self.flip.clicked.connect(self.do_flip)
The QWidget is then instantiated by my class, whose major purpose is to log the signal made. This is how I believe my class should be implemented:
class MyClass(QPushButton):
def __init__(self, name: str) -> None:
super().__init__(name)
self._name = name
def mousePressEvent(self, *args, **kwargs):
self.log_info()
def log_info(self):
log(self._name)
What I don't understand is why the do_flip slot is never called? This makes no sense to me, so I instead tried overriding the clicked signal via
(Option 2):
class MyClass(QPushButton):
def __init__(self, name: str) -> None:
super().__init__(name)
self._name = name
def clicked(self, *args, **kwargs):
self.log_info()
#add connect() here?
def log_info(self):
log(self._name)
But with this code I get a friendly AttributeError: 'function' object has no attribute 'connect'. I can't find any documentation on the clicked method and how it calls connect. Is there a way for me to make the connection for clicked to the slot or do I have to create my own signal? I find that this will become very useful when trying to override other signals that emit specific values too.
If so this leads me to:
Last Resort - My Own Signal
The class would be called like this:
self.flip = MyClass("Flip Image")
self.flip.mysig.connect(self.do_flip)
And implemented like this:
class MyClass(QPushButton):
mysig = pyqtSignal()
def __init__(self, name: str) -> None:
super().__init__(name)
self._name = name
def mousePressEvent(self, *args, **kwargs):
self.mysig.emit()
self.log_info()
def log_info(self):
log(self._name)
And this works. But I'm not sure my implementation makes sense. It feels kind of hacky. I'd prefer not to use this last resort option, and I'm hoping there is a cleaner option.
In Option 1 you are overriding the mousePressEvent method of QPushButton without calling the default implementation using super, and this I believe is the reason why the clicked signal is never emitted. For instance:
class MyClass(QPushButton):
def __init__(self, name: str) -> None:
super().__init__(name)
self._name = name
def mousePressEvent(self, event):
self.log_info()
super().mousePressEvent(event)
def log_info(self):
log(self._name)
In Option 2 you are declaring clicked as a normal python callable while instead it should be a pyqtSignal(). Also this is definitely not the way to go.
The correct way to achieve what you need is to use multiple slots for the same signal, i.e:
class MyClass(QPushButton):
def __init__(self, name: str) -> None:
super().__init__(name)
self._name = name
#pyqtSlot()
def log_info(self):
log(self._name)
self.flip = MyClass("Flip Image")
self.flip.clicked.connect(self.do_flip)
self.flip.clicked.connect(self.flip.log_info)

Categories

Resources