Function creates buttons but they can't trigger other functions - python

I'm Trying to build a dynamic layout in Kivy, my function generates buttons but they are not able to trigger any other function that would be responsible for the creation of labels:
def candidate_builder(self):
file = open('GSUCandidates.txt', 'r')
for names in file:
names = names.rstrip()
if 'President' in names:
cbl_layout = self.ids['cs_grid']
cn_label = Label(bold=True, text=names)
cn_button = Button(id='pr', bold=True, text='Vote')
cn_button.on_release = show()
cbl_layout.add_widget(cn_label)
cbl_layout.add_widget(cn_button)
cbl_layout.height = cbl_layout.height + 250
def show():
vp_label = Label(bold=True, text=names)
cpl_layout = self.ids['csp_grid']
cpl_layout.add_widget(vp_label)

You can use:
cn_button = Button(id='pr', bold=True, text='Vote', on_release=show)
...
def show(*args):
Or in case you want to send some variables to your method to should use lambda:
cn_button = Button(id='pr', bold=True, text='Vote', on_release=lambda event: show()) # <- here you should have parentheses, where you can put anything you want
...
def show():

You don't want show in quotes (that makes it a string, not a function reference). I think you need to to use
cn_button.on_release = show
and I think it needs to appear after the def show(). Also, the Button instance will be passed to the show() method, so you need to define it as:
def show(butt_instance):

Related

How to access to the return value from a function

I'm trying to make an application that can apply sound effects on audio file using PyQt5 module
i developed the module it contain function that apply the effect and return the new value of the signal y
I get stuck on how to get the return value from the module function
effrets.py
for example this function can apply reverberation effects on audio file
def revebration1(y,sr,retard,pert):
T = int(retard*sr)
print(T)
x = np.zeros(len(y)+T)
x[0:len(y)] = y
x[T::] += pert*y
sd.play(x,sr)
return x
app.py
When i click button1 i want to access to access to the value of x returned by revebration1
class AppDemo(QMainWindow):
def __init__(self):
super().__init__()
self.resize(1000,1000)
self.label = QLabel('const', self)
self.layout1=QVBoxLayout()
self.layout2=QVBoxLayout()
#buttons
self.button1 = QPushButton('importer',self)
self.button1.move(300,300)
self.layout1.addWidget(self.button1,1)
#self.button1.clicked.connect(self.lambda)
self.button1.clicked.connect(self.tele)
def tele(self):
# I don't know how to access to the return value of revebration1 function in order to continue building my app
revebrationReturnedValue = revebration1(y,sr,retard,pert)
To access returned value of the function you have to call that function
self.funcResult = revebration1(y,sr,retard,pert) - that's it

Declaring class methods in a function and accessing its values from another function

I've never really used classes before, I just simply went the easy way (global variables), and now I would like to make my code right to avoid future complications.
This is my code:
from dearpygui.core import *
class Engine:
def __init__(self,serial,type,profile):
self.serial = serial
self.type = type
self.profile = profile
def apply_selected_file():
res = []
html_name= "example.html"
path= "C:/"
#Function that reads data from a file and saves selected data in a list
res = html_imp(path + '/' + html_name)
#I would like to remove the code below and use a class for each file instead
setvalue(sn1,es[0]) #shows a label with this value
setvalue(type1,res[1]) #shows a label with this value
setvalue(profile1,res[2]) #shows a label with this value
return res
def button():
#This was my initial idea but it doesn't seem to work.
# res = apply_selected_file()
# E = Engine(res[0],res[1],res[2])
I have in mind reading multiple HTML files so using a class would be much easier than declaring variables for each file:
1- Use apply_selected_file to read a file and assign values (s/n,type,profile) to a new class (E1,E2,E3,...,E20,...)
2- Use another function button() to access those stored class values.

How to call function from external source from button. [Python]

I've been desperately trying to get this section of code to work in my program. I essentially want to read in several options from a file, and create Tkinter buttons from those options. Creating the buttons is no issue; currently, I just can't make the code run the functions I want.
from Lib import StegosaurMainCode as Steg
...
class App:
def __init__(self, master, menu):
buttons = []
for counter in range(0, len(menu[0])):
text = menu[0][counter]
func = menu[1][counter]
att = menu[2][counter]
buttons.append(Button(text=text, command=lambda: Steg.func(att)))
frame = Frame(master)
for item in buttons:
item.pack()
frame.pack()
In this class, func is the function I want to call, Steg is the external code in another file, and att are the attributes for the function. I can't seem to figure out why Steg.func won't tries to call a function in Steg called "func" rather than the one described in the variable func
Have your lambda rebind its att parameter at each call.
class App:
def __init__(self, master, menu):
buttons = []
for counter in range(0, len(menu[0])):
text = menu[0][counter]
func = menu[1][counter]
att = menu[2][counter]
buttons.append(Button(text = text, command = lambda att = att: Steg.func(att)))
frame = Frame(master)
for item in buttons:
item.pack()
frame.pack()
Assuming that menu[1][counter] contains a string rather than a reference to an actual function, you need to get a reference to the function which you can then use as the value for the command attribute. You can do that with getattr:
func = getattr(steg, menu[1][counter])
Once you've done that, you can use func as if it were an actual function. However, you need to bind the variables to their current values, so you need to pass them as arguments to the lambda:
button = Button(text=text, command=lambda func=func, attr=att: func(att)))

Problems with a bind function from tkinter in Python

I am working on an application that is supposed to support both running from a console and from a GUI. The application has several options to choose from, and since in both running modes the program is going to have the same options obviously, I made a generalisation:
class Option:
def __init__(self, par_name, par_desc):
self.name = par_name
self.desc = par_desc
class Mode():
def __init__(self):
self.options = []
self.options.append(Option('Option1', 'Desc1'))
self.options.append(Option('Option2', 'Desc2'))
self.options.append(Option('Option3', 'Desc3'))
self.options.append(Option('Option4', 'Desc4'))
self.options.append(Option('Option5', 'Desc5'))
#And so on
The problem is that in GUI, those options are going to be buttons, so I have to add a new field to an Option class and I'm doing it like this:
def onMouseEnter(par_event, par_option):
helpLabel.configure(text = par_option.desc)
return
def onMouseLeave(par_event):
helpLabel.configure(text = '')
return
class GUIMode(Mode):
#...
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name, bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event, iOption))
iOption.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
#...
There is also a "help label" showing the description of the option every time a mouse hovers over it, so there I am binding those functions.
What is happening is that while I am indeed successfully adding a new field with a button, the bind function seems to mess up and the result is this:
Help label is always showing the description of the last option added, no matter over which button I hover. The problem seems to go away if I directly modify the Option class instead, like this:
class Option:
def __init__(self, par_name, par_desc):
self.name = par_name
self.desc = par_desc
self.button = Button(wrapper, text = self.name, bg = '#004A7F', fg = 'white')
self.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event, self))
self.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
But I obviously can't keep it that way because the console mode will get those fields too which I don't really want. Isn't this the same thing, however? Why does it matter if I do it in a constructor with self or in a loop later? I therefore assume that the problem might be in a way I dynamically add the field to the class?
Here is the full minimal and runnable test code or whatever it is called, if you want to mess with it: http://pastebin.com/0PWnF2P0
Thank you for your time
The problem is that the value of iOption is evaluated after the
for iOption in self.option:
loops are complete. Since you reset iOption on each iteration, when the loop is completed iOption has the same value, namely the last element in self.options. You can demonstrate this at-event-time binding with the snippet:
def debug_late_bind(event):
print(iOption)
onMouseEnter(event, iOption)
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name,
bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', debug_late_bind)
which will show that all events that iOption has the same value.
I split out the use of iOption to debug_late_bind to show that iOption comes in from the class scope and is not evaluated when the bind() call is executed. A more simple example would be
def print_i():
print(i)
for i in range(5):
pass
print_i()
which prints "4" because that is the last value that was assigned to i. This is why every call in your code to onMouseEnter(par_event, iOption) has the same value for iOption; it is evaluated at the time of the event, not the time of the bind. I suggest that you read up on model view controller and understand how you've tangled the view and the controller. The primary reason this has happened is that you've got two views (console and tk) which should be less coupled with the model.
Extracting the .widget property of the event is a decent workaround, but better still would be to not overwrite the scalar iOption, but instead use list of individual buttons. The code
for n, iOption in enumerate(self.options):
would help in creating a list. In your proposed workaround, you are encoding too much of the iOption model in the tkinter view. That's bound to bite you again at some point.
I don't know what the actual problem was with my original code, but I kind of just bypassed it. I added a dictionary with button as a key and option as a value and I just used the par_event.widget to get the option and it's description, which is working fine:
buttonOption = {}
def onMouseEnter(par_event):
helpLabel.configure(text = buttonOption[par_event.widget].desc)
return
def onMouseLeave(par_event):
helpLabel.configure(text = '')
return
class GUIMode(Mode):
def run(self):
#...
for iOption in self.options:
iOption.button = Button(wrapper, text = iOption.name, bg = '#004A7F', fg = 'white')
iOption.button.bind('<Enter>', lambda par_event: onMouseEnter(par_event))
iOption.button.bind('<Leave>', lambda par_event: onMouseLeave(par_event))
buttonOption[iOption.button] = iOption
#...

Executing different functions based on options selected

I wanted to know if there is a way of populating a option menu with choices and then each of the different choice gives the build button a different function
for eg:
Type = cmds.optionMenu('type',w = 300 ,label = 'Type of crowd:')
cmds.menuItem( label='Walking' )
cmds.menuItem( label='Running' )
cmds.menuItem( label='Cheering' )
cmds.button('Build',command = bld)
def walk(*args):
print (walking)
def run(*args)
print (running)
def cheer(*args)
print (cheer)
so if the menu item selected would be walking the button command would execute the command wak
and if the menu item selected would be running then the button command would execute the command run and so on....
is this even possible in maya python...????
There's three parts to the problem.
First, you want your options to be callables. I like functools.partial for that, so that you could give the same command different parameters and have it be treated as two different actions:
from functools import partial
bigcircle = functools.partial ( cmds.circle, radius = 10)
littleCircle = functools.partial (cmds.circle, radius = 1)
the second problem is that menuItems in OptionMenus don't fire their commands directly. They trigger the -cc change command on the owning optionMenu. So we need something that will turn the label back into a callable object. A little class will do:
class menuMgr(object):
'''call the function associated with a key in the **callables dictionary'''
def __init__(self, **callables):
self.Callables = callables
def __call__(self, *args):
self.Callables[args[-1]]()
The third part is to match these with a label. You can do this elegantly with the **kwargs syntax, where you can either pass in a whole dictionary or named keywords:
def menu_of_functions(**callables):
mmgr = menuMgr(**callables)
Main = cmds.optionMenu('type3',w = 300 ,label = 'Type of crowd:', cc = mmgr)
for key, partial in callables.items():
cmds.menuItem(label = key)
cmds.setParent("..")
Heres the whole thing in working form to inspect:
import maya.cmds as cmds
import functools
bigCircle = functools.partial ( cmds.circle, radius = 10)
littleCircle = functools.partial (cmds.circle, radius = 1)
class menuMgr(object):
def __init__(self, **callables):
self.Callables = callables
def __call__(self, *args):
self.Callables[args[-1]]()
def menu_of_functions(**callables):
mmgr = menuMgr(**callables)
Main = cmds.optionMenu('type3',w = 300 ,label = 'Type of crowd:', cc = mmgr)
for key, partial in callables.items():
cmds.menuItem(label = key)
cmds.setParent("..")
q = cmds.window()
cmds.columnLayout()
menu_of_functions(big = bigCircle, small = littleCircle)
cmds.showWindow(q)
Sure you can, since functions are first class objects in python.
Let's say you have a list of functions.
fns = [walk, run, cheer]
1) You'll need a mapping from a string key to the python function.
Let's use a dictionary comprehension.
options = dict((fn.__name__, fn) for fn in fns)
Alternatively you can build the dictionary with arbitrary keys.
options = {"Walking": walk, "Running": run, "Cheering": cheer}
2) Get a reference to the function by accessing the dictionary item with the functions name.
options['run'](*args)
3) ???
4) Profit
Had a chance to do a small bit of research on this today, but I don't actually use Maya Python so this may not be viable code -- at least it should be close!
def walk(*args):
print ("walking")
def run(*args)
print ("running")
def cheer(*args)
print ("cheer")
fncdict = {"Walking":walk,"Running":run,"Cheering":cheer}
def dofunc(funcname):
try: fncdict[funcname]()
except KeyError: print("adsmith doesn't really know how this crap works,
and gave you some really shoddy advice. Go downvote
his answer.")
Type = cmds.optionMenu('type',w = 300 ,label = 'Type of crowd:')
# Please don't name things this way! naming conventions in PEP 8
# make this look like a class not an instance of optionMenu
cmds.menuItem( label='Walking', parent = Type )
cmds.menuItem( label='Running', parent = Type )
cmds.menuItem( label='Cheering', parent = Type )
cmds.button('Build',command = lambda x: dofunc(Type.value))
# I'm assuming this is the button you want to use to say "GO", right?
From the little I've read, it looks like optionMenu.value refers to the text in the active menuItem, but I can't say for sure -- it may just be the text for that optionMenu in which case the button will call dofunc('Type of crowd:') which will return the exception I built to shame myself.
Here's an alternative that I KNOW will work, but it's ugly and unnecessary.
# all
# those
# functions
# go
# here
# including
# the dict and dofunc
activeMenuItem = None
Type = cmds.optionMenu('type',w = 300 ,label = 'Type of crowd:',
changeCommand = lambda x: activeMenuItem = x)
# this fails because you can't do assignments in a lambda -- OOPS!!
cmds.menuItem( label='Walking', parent = Type )
cmds.menuItem( label='Running', parent = Type )
cmds.menuItem( label='Cheering', parent = Type )
cmds.button('Build',command = lambda x: dofunc(activeMenuItem))
The changeCommand option in optionMenu gets called every time you change items. I've assigned it to a lambda that updates the variable activeMenuItem to the value in the newly-active menuItem, then had the button reference the variable instead of querying the optionMenu for its currently selected button, but let's be honest -- this is what menus are MADE to do. There is DEFINITELY a way to do it without storing every single selection.
EDIT: THIS LAST ONE WON'T WORK BECAUSE YOU CAN'T DO ASSIGNMENTS WITHIN LAMBDA EXPRESSIONS. MY FAULT!
I have kind of figured out an easier way to this problem
cmds.optionMenu('Greetings',Label = 'Greet')
cmds.menuItem(label= hi,parent = 'Greetings)
cmds.menuItem(label = Hello,parent = 'Greetings')
def Greet(*args)
menuItems = cmds.optionMenu('Greetings',q=True,v=True)
if menuItems = hi
print "hi"
menuItemsnew = cmds.optionMenu('Greetings',q=True,v=True)
if menuItemsnew = hello
print "hello"
This should work,it worked for me

Categories