PyQt5 log a signal - python

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)

Related

Can I add specificity to a kwarg in a subclass' constructor?

I am trying to get PyCharm to understand that the subclass of my base controller class only takes a specific type of widget.
Minimal example:
import tkinter as tk
class BaseWidgetController:
def __init__(self, parent: 'tk.Widget'): # Parent is always __some__ kind of widget
self._parent = parent
class EntryWidgetController(BaseWidgetController):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._parent: 'tk.Entry' # On this class, I want Pycharm to understand _parent is only ever an Entry (a subclass of tk.Widget), but even adding this line doesn't change its mind.
def say_type(self) -> None:
print(type(self._parent)) # PyCharm still thinks _parent is a tk.Widget
ew = EntryWidgetController(parent=tk.Frame())
ew.say_type() # Obviously this works fine at runtime.
If you want to constrain the EntryWidgetController so that it only accepts tk.Entry or subclasses, the fix is rather simple - just do
class EntryWidgetController(BaseWidgetController):
def __init__(self, parent: 'tk.Entry', **kwargs):
super().__init__(parent=parent, **kwargs)
That way
ew = EntryWidgetController(parent=tk.Frame())
will make PyCharm complain that Expected type 'Entry', got 'Frame' instead.

Calling super() with arguments set in sub-class __init__()?

So I have a class (let's call it ParamClass) which requires a parameter for initialization, and that parameter is something that should be available to the user to configure via some option-setting interface.
ParamClass knows nothing about the configuration interface or how to read them. So I made another class, called Configurator, which does all of that. When a class inherits from Configurator and tells it what configuration keys to read, Configurator's __init__() method will read those keys and assign their values to the correct attributes in self.
The problem I run into, however, is that when I try to pass arguments to super(), including the parameters to be read by Configurator, those parameters have no value yet. But they are passed as constants in the argument list to the super(). Example shown below. MyClass.__init__() can't even get started because self.server_param doesn't exist yet.
class ParamClass:
"""Just some class that needs a parameter for init"""
def __init__(self, param1, **kwargs) -> None:
super().__init__(**kwargs)
self.value = param1
class Configurator:
"""Reads parameters from a configuration source and sets appropriate class
variables.
"""
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.server_param = 2
class MyClass(Configurator, ParamClass):
def __init__(self, **kwargs) -> None:
super().__init__(param1=self.server_param, **kwargs)
# <-- Gives AttributeError: 'MyClass' object has no attribute 'server_param'
MyClass()
The only way I can get this to work is to break MRO in Configurator.init() and force the order of initilization. This is bad for obvious reason - I plan to use Configurator throughout my code and can't break MRO with it.
class ParamClass:
"""Just some class that needs a parameter for init"""
def __init__(self, param1, **kwargs) -> None:
super().__init__(**kwargs)
self.value = param1
class Configurator:
"""Reads parameters from a configuration source and sets appropriate class
variables.
"""
def __init__(self, **kwargs) -> None:
# super().__init__(**kwargs)
self.server_param = 2
class MyClass(Configurator, ParamClass):
def __init__(self, **kwargs) -> None:
Configurator.__init__(self, **kwargs)
# <-- After this call, self.server_param is defined.
ParamClass.__init__(self, param1=self.server_param, **kwargs)
MyClass()
How do I accomplish configuration of parameters in while user super? How do I do this in a generalized way that doesn't require Configurator to know little details about ParamClass?
Note: In my particular case, I don't "own" the ParamClass() code. It is library code that I'm using.

How to defer execution of a function?

I am implementing the classes in Python 2.7 as below:
class MyClass(object):
def do_something(self):
"""
do something here
"""
class MyClassManager(object):
def get_my_class_obj(self):
"""read my_class_obj from db"""
return instance # instance has type MyClass
class MyClassUser(object):
my_class_obj = new MyClass() # this is a class variable
In main:
MyClassUser.my_class_obj = MyClassManager().get_my_class_obj()
"""
do a lot of different things else in main
"""
From somewhere else:
"""only when some condition happens"""
MyClassUser.my_class_obj.do_something()
Is there a way I can defer the read obj (read from db inside get_my_class_obj) process in MyClassManager until obj.do_something method is actually invoked? Provided that I have to call MyClassManager.get_my_class_obj for some setup at the beginning. Suppose the situation is in the context of a web server and do_something will only be invoked when there is some request but I need to set up it first
A very quick&dirty, dumbed down possible solution (and certainly not how I'd design this but anyway). This example assume your object only has an id (db primary key) and a name (stored in db).
import functools
class MyClassManager(object):
def get(self, object_id):
return MyClass(object_id)
def load(self, object):
obj.name = self.get_from_db(object.id)
def autoload(func):
#functools.wraps(func)
def wrapper(self, *args, **kw):
self._load()
return func(self, *args, **kw)
return wrapper
class MyClass(object):
def __init__(self, id, name=None):
self.id = id
self._name = name
self._loaded = name is not None
def _load(self):
if self._loaded:
return
MyManager.load(self)
#property
#autoload
def name(self):
return self._name
#autoload
def do_something(self):
# your code here
But really, don't do this if you're a beginner (and you wouldn't ask if you were not), use an existing working ORM instead. Unless it's for educational purpose of course, in which case, well, you might either learn a lot or give up in despair - or both ;-)

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())

How can I fix the error on this Python code?

I have this superclass:
import wx
class Plugin(wx.Panel):
def __init__(self, parent, *args, **kwargs):
wx.Panel.__init__(self, parent, *args, **kwargs)
self.colorOver = ((89,89,89))
self.colorLeave = ((110,110,110))
self.SetBackgroundColour(self.colorLeave)
self.SetForegroundColour(self.colorLeave)
self.name = "plugin"
wx.StaticText(self, -1, self.getName(), style=wx.ALIGN_LEFT)
self.Bind(wx.EVT_ENTER_WINDOW, self.onMouseOver)
self.Bind(wx.EVT_LEAVE_WINDOW, self.onMouseLeave)
def onMouseOver(self, event):
self.SetBackgroundColour(self.colorOver)
self.Refresh()
def onMouseLeave(self, event):
self.SetBackgroundColour(self.colorLeave)
self.Refresh()
def OnClose(self, event):
self.Close()
app.Destroy()
def getName(self):
return self.name
and this subclass:
import plugin
import wx
class noisePlugin(plugin.Plugin):
self.name = "noise"
and it gives me this error compiling the subclass:
Traceback (most recent call last):
File "C:\Users\André Ferreira\Desktop\Tese\Código Python\SoundLog\Plugins\noisePlugin.py", line 4, in <module>
class noisePlugin(plugin.Plugin):
File "C:\Users\André Ferreira\Desktop\Tese\Código Python\SoundLog\Plugins\noisePlugin.py", line 5, in noisePlugin
self.name = "noise"
NameError: name 'self' is not defined
What can I do to fix this error?
I want getName() method to return the name of the instanciated class!
Thanks in advance :)
Make the subclass
class noisePlugin(plugin.Plugin):
def __init__(self, *a, **k):
plugin.Plugin.__init__(self, *a, **k)
self.name = "noise"
Whenever you want to use self.something you have to be within a method, not at class level outside of methods!
What makes you think this works?
class noisePlugin(plugin.Plugin):
self.name = "noise"
Why didn't you copy the
class Plugin(wx.Panel):
def __init__(self, parent, *args, **kwargs):
That comes before self.name=?
For the pattern you seem to be trying for (where the name is associated more with the class than with the instance), this is often a better idiom to follow:
class A(object):
name = 'parent'
def __init__(self, ...):
... etc
class B(A):
name = 'child'
def __init__(self, ...):
A.__init__(self, ...)
... etc
Even though the name attribute is stored on the class rather than the instance, you can access it using self.name in all the instances. Generally if you find yourself assigning a static (unchanging) attribute that is the same in all instances of a given class you should just use a static class attribute like this.
On a slightly different topic, were you aware that all wxPython widgets already have a name attribute, which can be assigned using the name keyword argument at initialization time, and accessed using either GetName() or (in recent versions of wxPython) the property Name? If you don't assign it, it will default to some fairly generic class-specific value (like "text" or "textctrl" for wx.TextCtrl). Depending on what you're trying to do, maybe you can just use it instead of your own name and getName(). wxPython itself does not make any use of this value, as it's intended for you the programmer to use as you see fit.

Categories

Resources