Gio SimpleAction to call a function - python

I have made menu using Gio action in a Gtk3 app.
The menu item is created as:
#in main file
MenuElem = menu.MenuManager
# Open Menu
action = Gio.SimpleAction(name="open")
action.connect("activate", MenuElem.file_open_clicked)
self.add_action(action)
The file_open_clicked is in menu.py, class MenuManager, defined as:
import gi
import pybib
import view
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
class MenuManager:
def __init__(self):
self.parsing = pybib.parser()
self.TreeView = view.treeview()
#file_open_clicked
#in menu.py
def file_open_clicked(self, widget):
dialog = Gtk.FileChooserDialog("Open an existing fine", None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
response = dialog.run()
if response == Gtk.ResponseType.OK:
filename = dialog.get_filename()
dialog.destroy()
self.TreeView.bookstore.clear()
self.TreeView.viewer(self.parsing.booklist)
# self.TreeView.view.set_model()
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dialog.destroy()
I am getting error:
Traceback (most recent call last):
File "/home/rudra/Devel/mkbib/Python/src/menu.py", line 81, in file_open_clicked
self.TreeView.bookstore.clear()
AttributeError: 'SimpleAction' object has no attribute 'TreeView'
I know SimpleAction takes one more option, and TreeView should be called.
But I dont know how.
Kindly help

Let me break down your code for you.
#in main file
MenuElem = menu.MenuManager
Here you set MenuElem to point to menu.MenuManager class. Probably you meant to initialize the object here such that MenuElem become an instance of the menu.MenuManagerclass. Such that the __init__ function of the MenuManager class was called. Thus the code should be:
#in main file
MenuElem = menu.MenuManager()
Then the next part where something goes wrong is in here:
def file_open_clicked(self, widget):
If we check the docs for the activate signal we see that it has 2 parameters. So currently without initializing the object self is set to the first parameter namely the SimpleAction and the widget is set to the activation parameter.
But as we now have initialized the MenuManager object, the file_open_clicked function will get 3 input parameters namely self, SimpleAction and parameter. Thus we need to accept them all like this:
def file_open_clicked(self, simpleAction, parameter):
Now the code will work as self is actually an object with the attribute TreeView. (Just for your information in Python variables and attributes are normally written in lowercase)

Your problem is that the TreeView attribute only exists on the MenuManager class, whereas, when you call the file_open_clicked method, the first argument (self) is the SimpleAction object created. Using the file_open_clicked method of a MenuManager instance would fix this.
menu_manager = MenuManager()
action = Gio.SimpleAction(name="open")
action.connect("activate", menu_manager.file_open_clicked)

Related

What is function binding in python?

I was trying to understand the kivy library in python. Below is the complete code.
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.inside = GridLayout()
self.inside.cols = 2
self.inside.name = TextInput(multiline=False)
self.inside.add_widget(self.inside.name)
self.inside.add_widget(Label(text="Name: "))
self.inside.name = TextInput(multiline=False)
self.inside.add_widget(self.inside.name)
self.inside.add_widget(Label(text="Email: "))
self.inside.name = TextInput(multiline=False)
self.inside.add_widget(self.inside.name)
self.inside.add_widget(Label(text="Phno: "))
self.add_widget(self.inside)
self.submit = Button(text="Submit", font_size=40)
self.submit.bind(on_press=self.pressed)
self.add_widget(self.submit)
def pressed(self, instance):
print("pressed")
class MyApp(App):
def build(self):
return MyGrid()
if __name__ == "__main__":
MyApp().run()
Here, while in MyGrid class, under init method, when I try to bind the submit button(self.submit.bind) with the pressed method, the argument with on_press doesn't include parentheses.
self.submit.bind(on_press=self.pressed)
Why does it work? Is it because it's a python convention to not include parentheses while passing it to a method?
And what is the use of the instance argument in the pressed method?
If you pass the function without parentheses, you are passing the function object itself
self.submit.bind(on_press=self.pressed)
This is generally used for callback mechanisms, among other things. If you added parentheses, self.pressed() would be invoked immediately at that very statement, instead of on_press saving that function to call later, in response to a button click.
Parentheses call the method. In the cases where you omit them, you're passing the method itself as a callback to be invoked later. When the method is called, parentheses are involved, but not until then.
Python is an Object Oriented Language in the sense that everything is an object (even methods). In the example above the bind method takes a callable object as a parameter (so another method).
To get the callable object rather than call the method you omit the brackets.
It helps to see what self.pressed actually evaluates to.
The instance self doesn't have an attribute named pressed. Its class, MyGrid, has a function-valued attribute by that name, though.
So self.pressed evaluates to MyGrid.pressed. But, because the function class implements the descriptor protocol, you don't get a reference to that function object. Instead, you get the result of MyGrid.pressed.__get__(self, MyGrid). And that returns a callable object of type method. That object, when called, will take whatever arguments it receives and immediately pass self and those arguments to a call to MyGrid.pressed.
In short, the value of self.pressed looks a lot like a function that might have been defined like
def bound_method(*args, **kwargs):
return MyGrid.pressed(self, *args, **kwargs)
self.pressed == MyGrid.pressed
== MyGrid.__dict__['pressed'].__get__(self, MyGrid)
and when self.pressed is eventually called,
self.pressed(ins) == MyGrid.__dict_['pressed'].__get(self, MyGrid)(self, ins)

Method from another class does not work entirely when called

I have this code:
class Matplotlib_figure(QMainWindow):
minimumCoords = None
maximumCoords = None
initial_marker = None
final_marker = None
limite = None
def __init__(self):
#A lot of stuff to draw a matplotlib figure
def minimumLimit(self):
self.cMinL = self.figure_canvas.mpl_connect("button_press_event", self.select_minimumLimit)
self.limite = "minimum"
def select_minimumLimit(self, event):
if event.button == 1:
self.clearMarker() #This is another method that i call
Matplotlib_figure.minimumCoords = None
Matplotlib_figure.minimumCoords = event.xdata
if Matplotlib_figure.minimumCoords <= Matplotlib_figure.maximumCoords or Matplotlib_figure.maximumCoords == None:
marker = self.axes.axvline(event.xdata,0,1, linestyle='dashed',
linewidth = 2, color = "green" )
self.figure_canvas.draw_idle()
Matplotlib_figure.initial_marker = marker
class Data(QDialog):
minimum = None
maximum = None
def __init__(self, parent):
QDialog.__init__(self, None, QWindowsStayOnTopHint)
uic.loadUi("", self)
def show_lines(self):
SelectData.minimo = self.lineEdit.text()
SelectData.maximo = self.lineEdit_2.text()
Matplotlib_figure.minimumCoords = float(SelectData.minimo)
Matplotlib_figure.maximumCoords = float(SelectData.maximo)
#Here is where i want to call a method in the Matplotlib_figure class
view = Matplotlib_figure()
view.minimumLimit()
view.maximumLimit()
The problem comes in the Data class. When i want to call the minimumLimitmethod in the Matplotlib_figureclass (from show_lines in Data class), it does not trigger the figure_canvas.mpl_connectmethod, and select_minimumLimitmethod does not work.
What am i doing wrong? Hope you can help me.
I think the key issue comes from this note in the matplotlib event handling docs:
The canvas retains only weak references to the callbacks. Therefore if a callback is a method of a class instance, you need to retain a reference to that instance. Otherwise the instance will be garbage-collected and the callback will vanish.
So you have created a new view in the show_lines method, but this is a local variable. When the function returns, the variable goes out of scope and python will probably try to delete it. Normally if you save a reference to a method off, then the method retains the object it is a method for, and this would not happen, but because mpl_connect only takes a weak reference to the function it does not retain view, and therefore when the show_lines returns, the method is lost too, and so the callback will revert to doing nothing.
You can probably fix this by rewriting show_lines to save the view off, something like:
def show_lines(self):
SelectData.minimo = self.lineEdit.text()
SelectData.maximo = self.lineEdit_2.text()
Matplotlib_figure.minimumCoords = float(SelectData.minimo)
Matplotlib_figure.maximumCoords = float(SelectData.maximo)
#Here is where i want to call a method in the Matplotlib_figure class
self.view = Matplotlib_figure()
self.view.minimumLimit()
self.view.maximumLimit()
Now the Matplotlib_figure instance will be retained as long as the Data instance is.
[Previous answer based on error in question kept below]
I don't know the QT framework or the matplotlib APIs very well, but it looks to me that you've created an instance of ViewWidget, which is an entirely separate class (subclass of the QT QMainWindow class, if I recognise that, which is an entirely different python module) from Matplotlib_figure. Therefore I would expect when you call minimumLimit() that you'd get an AttributeError exception, and I wouldn't expect it to call your method. If you want that you'll have to create an instance of it and call that:
view = Matplotlib_figure()
view.minimumLimit()
view.maximumLimit()
Without more context of where your ViewWidget comes from it is hard to understand how you think this should work. It's also a bit odd that you are creating a matplotlib figure that is a subclass of the unrelated QMainWindow class. What are you trying to achieve with this? Could you provide some more context for the code?

StackOverflowError when using getattr in Jython

I'm writing a text editor in Jython. This text editor has a toolbar which is displayed with a ToolbarView class and handled by a ToolbarController class. Some actions can't be dealt with by the ToolbarController on its own, so these are delegated to the MainController class.
To avoid repeating code since there are many actions delegated from the ToolbarController to the MainController, I've used getattr as suggested in a previous question I asked here. I have also realised I can use the same mechanism in the ToolbarView code for the actions of the buttons, but I can't get it to work and I end up getting an infinite loop and a Java StackOverflowError.
This is an extract of the relevant code:
ToolbarView class:
from javax.swing import JToolBar, ImageIcon, JButton
class ToolbarView(JToolBar):
def __init__(self, controller):
#Give reference to controller to delegate action response
self.controller = controller
options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']
for option in options:
methods[option] = "on" + option + "Click"
print methods[option]
for name, method in methods.items():
button = JButton(name, actionPerformed=getattr(self, method))
self.add(button)
def __getattr__(self, name):
return getattr(self.controller, name)
ToolbarController class:
from .ToolbarView import ToolbarView
class ToolbarController(object):
def __init__(self, mainController):
#Create view with a reference to its controller to handle events
self.view = ToolbarView(self)
#Will also need delegating to parent presenter
self.mainController = mainController
def __getattr__(self, name):
return getattr(self.mainController, name)
MainController class:
from .ToolbarController import ToolbarController
class MainController(object):
def __init__(self):
self.toolbarController = ToolbarController(self)
def onNewFileClick(self, event):
print("MainController: Creating new file...")
def onEditFileClick(self, event):
print("MainController: Editting new file...")
def onSaveFileClick(self, event):
print("MainController: Saving new file...")
def onCloseFileClick(self, event):
print("MainController: Closing new file...")
So what I expect is when I click the button, MainController.onNewFileClick gets executed and prints out that message in console. It works if I want to delegate from the ToolbarView to the ToolbarController, but it doesn't work when I pass that delegation from the ToolbarController to the MainController. It seems to call itself on an infinite loop. The error I get is:
Traceback (most recent call last):
File "main.py", line 3, in <module>
MainController()
File "/home/training/Jython/controller/MainController", line 8, in __init__
self.toolbarController = ToolbarController(self)
File "/home/Jython/controller/ToolbarController.py", line 8, in __init__
self.view = ToolbarView(self)
File "/home/Jython/controller/ToolbarView.py", line 44, in __init__
button = JButton(name, actionPerformed=getattr(self, method))
File "/home/Jython/controller/ToolbarView.py", line 54, in __getattr__
return getattr(self.controller, name)
File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
return getattr(self.mainController, name)
File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
return getattr(self.mainController, name)
[...]
File "/home/Jython/controller/ToolbarController.py", line 15, in __getattr__
return getattr(self.mainController, name)
RuntimeError: maximum recursion depth exceeded (Java StackOverflowError)
What am I doing wrong? I've tried something similar in python (delegating from a class to another class to another class) and it works if a put () after the getattr, but here I get confused because of the actionPerformed in the JButton. I've tried it but results are the same.
it seems you are using Jython, which i don't really know. anyways, in python, you override __getattr__, then you should expect getattr to use your overridden hook instead. so i think you really mean:
class ToolbarView(JToolBar):
def __init__(self, controller):
#Give reference to controller to delegate action response
self.controller = controller
options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']
for option in options:
methods[option] = "on" + option + "Click"
print methods[option]
for name, method in methods.items():
button = JButton(name, actionPerformed=super(ToolbarView, self).__getattr__(method))
self.add(button)
def __getattr__(self, name):
return getattr(self.controller, name)
watch how buttons are created.
in terms of why you have a SO problem, it is because how getattr is handled. if you override __getattr__, this hook will only get called if you try to reference to a undefined field:
>>> class A(object):
defined = True
def __getattr__(self, name):
print "referenced :" + name
>>> a = A()
>>> a.defined
True
>>> a.undefined
referenced :undefined
hope it's clear how the hook work now.
so the SO is actually caused by you were referencing something that does not belong to MainController.
in your MainController, only onNewFileClick is defined, but you defined 3 other options:
options= ['NewFile', 'OpenFile', 'SaveFile', 'CloseFile']
so, this will happen at the second round of iteration. since MainController has no onOpenFileClick, an AttributeError will be raised, but captured by ToolbarController, and therefore the overridden __getattr__ is invoked and on and on. that's why your call stack explodes.
I was blaming this to getattr since I'm not that confident using it yet, but it turns out it was something rather basic.
I was assigning the mainController to the ToolbarController AFTER creating the ToolbarView which then calls ToolbarView.__getatrr__, which calls ToolbarController.__getattr__ which tries to access self.mainController which doesn't exist yet!
This is the change I needed to make in the ToolbarController class.
Before
from .ToolbarView import ToolbarView
class ToolbarController(object):
def __init__(self, mainController):
#Create view with a reference to its controller to handle events
self.view = ToolbarView(self)
#Will also need delegating to parent presenter
self.mainController = mainController
def __getattr__(self, name):
return getattr(self.mainController, name)
After:
from .ToolbarView import ToolbarView
class ToolbarController(object):
def __init__(self, mainController):
#Needs to delegate to main presenter.
#Note self.mainController needs to exist before creating the ToolbarView
#since it needs delegating actions to it!
self.mainController = mainController
#Create view with a reference to its controller to handle events
self.view = ToolbarView(self)
def __getattr__(self, name):
return getattr(self.mainController, name)
Thanks a lot to #HuStmpHrrr and #ArtOfWarfare for their help.

Python Attribute Error with multiple classes and Tkinter

I'm trying create a gui using Tkinter that grabs a username and password and connects to a remote server and does a function. I slapped together some messy code and it more or less worked, but when I tried to recreate it in a tidy module, it broke. Its probably a newbie python error, but I can't spot it. EDIT: to clarify, when it worked, the only class was setupGui and any methods were under that class. Now that I've separated the gui from the methods, its not working.
class setupGui(object):
def __init__(self, parent):
##omited general frame stuff
self.userIn = ttk.Entry(self.topFrame, width = 20)
self.userIn.grid(row = 1, column = 1)
self.passIn = ttk.Entry(self.topFrame, width = 20, show ="*")
self.passIn.grid(row = 2, column = 1)
#Buttons
self.setupbtn = ttk.Button(self.topFrame, text = "Start Setup", command = setup().startSetup())
self.setupbtn.grid(row = 3, column = 0, pady = 10)
class setup(object):
def__init__(self):
self.userName = setupGui.userIn.get()
self.userPass = setupGui.passIn.get()
def startSetup(self):
self.another_related_fucntion # about 4 related functions actually
if __name__ == '__main__':
root = Tk()
gui = setupGui(root)
root.mainloop()
And if I don't have the command attached to the button, everything works fine (but obviously does diddly squat except look pretty). And when I attached the command, I get this error:
Traceback (most recent call last):
File "macSetup.py", line 211, in <module>
gui = setupGui(root)
File "macSetup.py", line 45, in __init__
self.setupbtn = ttk.Button(self.topFrame, text = "Start Setup", command = setup().startSetup())
File "macSetup.py", line 69, in __init__
self.userName = setupGui.userIn.get()
AttributeError: type object 'setupGui' has no attribute 'userIn'
In your code, userIn is set up as an instance variable of setupGui objects, not as an attribute of the setupGui class itself.
The simplest solution would be to merge the setupGui and setup classes to move startSetup in as a method of setupGui, then use command=self.startSetup when you initialize setupbtn—this calls startSetup as a bound method, and self should thus refer to the setupGui object, which you can then use e.g. self.userIn.get() and self.passIn.get() on.
If you'd rather keep the logic you have in the setup class out of the setupGui class, you can separate it out like this:
class setup(object):
def __init__(self, username, userpass):
self.userName = username
self.userPass = userpass
def startSetup(self):
# as before
then add this method to the setupGui class:
def dosetup(self):
setup(self.userIn.get(), self.passIn.get()).startSetup()
and instantiate the Button with command=self.dosetup. (I would personally make the setup class a standalone function, but I don't know how complicated your startSetup routine actually is, so I assume you have a good reason for making it a class.)
The command attribute takes a reference to a function, but you're calling the function and giving the result to the command attribute. The net result is that you're calling the setup function at the time that you create the button, not at the time that you click the button. Things aren't fully initialized yet, so you get the error.
You're doing this:
self.setupbtn = ttk.Button(self.topFrame, text = "Start Setup", command = setup().startSetup())
... when you should be doing something like this:
self.setupbtn = ttk.Button(self.topFrame, text = "Start Setup", command = setup().startSetup)
Note the lack of the trailing () on startSetup.
If you don't want to instantiate setup until the button is clicked, you have a couple of choices. The best, arguably, is to create a method:
def _launch_setup(self):
setup().setupGui()
...
self.setupbtn = ttk.Button(..., command=self._launch_setup)
You could also use a lambda, but in this case I recommend a named method.
The class setupGui itself doesn't have the attribute userIn.
In the __init__ method of setupGui you give the attribute to the instance, not the class.

Python TypeError when using Bind for a wx.Button

I have a class called WxFrame which creates a wxPython frame. I added a method called createRunButton which receives self and pydepp, which is the object of a PyDEPP class
import wx
class WxFrame(wx.Frame):
def __init__(self, parent, title):
super(WxFrame, self).__init__(parent, title=title)
self.Maximize()
self.Show()
def createRunButton(self,pydepp):
#pydepp.run()
self.runButton = wx.Button(self, label="Run")
self.Bind(wx.EVT_BUTTON, pydepp.run, self.runButton
This is the PyDEPP class:
class PyDEPP:
def run(self):
print "running"
I instantiate and run it with:
import wx
from gui.gui import WxFrame
from Depp.Depp import PyDEPP
class PyDEPPgui():
"""PyDEPPgui create doc string here ...."""
def __init__(self,pydepp):
self.app = wx.App(False)
##Create a wxframe and show it
self.frame = WxFrame(None, "Cyclic Depp Data Collector - Ver. 0.1")
self.frame.createRunButton(pydepp)
self.frame.SetStatusText('wxPython GUI successfully initialised')
if __name__=='__main__':
#Launch the program by calling the PyDEPPgui __init__ constructor
pydepp = PyDEPP()
pydeppgui = PyDEPPgui(pydepp)
pydeppgui.app.MainLoop()
The error I get when running the above code is:
TypeError: run() takes exactly 1 argument (2 given)
However, if I comment out the bind and uncomment the line pydepp.run(), then it works fine.
The answer is obvious I'm sure, but I have never studied CompSci or OO coding.
The event gets passed as an argument to the callback function. This should work:
class PyDEPP:
def run(self, event):
print "running"
When the event is triggered two arguments are passed to the callback function run(): the object which has triggered the event, and a wxEvent object. Since run only accepts one argument in your code the interpreter is giving that error which tells you that you've provided too many arguments.
Replace
run(self): # Expects one argument, but is passed two. TypeError thrown
with
run(self, event): # Expects two arguments, gets two arguments. All is well
and it should work.
This is one instance where the error tells you a lot about what's wrong with the code. Given that "run() takes exactly 1 argument (2 given)", you immediately know that either you've accidentally passed an extra argument, or run should be expecting another.

Categories

Resources