Ahhh. So I believe the issue you’re having is that a SpriteNode is not a button. In the example, the class ButtonNode is created just to make the “menu button” (actually just a sprite) change appearance when you tap it.
Everything in the scene module can only respond to taps, and you have to implement the touch_began method to do hit-testing on your sprites. After determining which sprite you’re touching, you could do something like:
btn_node.run_action(Action.call(lambda: print('hello'))) # or btn_node.run_action(Action.call(some_func))
If you want to actually combine the UI module with the scene module, you would need to make a .pyui file and set it’s custom view attribute to your main scene class. Like here: Combine Scene and UI
Also, if you haven’t already, check out the docs. They’re super informative, and most things have examples. There’s also a snippet in the scene docs that explains how to integrate scene and UI if the above link didn’t help.
Apart from all this, if you’re making a game, I would recommend not trying to integrate the UI module, and do hit-testing with the sprites in the scene module. If you’re not doing a game, just go full UI. Integrating will complicate things while you’re still learning IMO.
I think you were exactly right. I was trying to use ui with scene and it wasn’t working how I wanted. So I switched to using ui with the visual editor, using .pyui. I believe I have everything working! Thank you for your help and also @JonB for helping me sort this out.
Unfortunately, I ve been looking through the documentation throughout the day and I have not yet been able to find how to have a button change the text for a label/text field/text view. I tried all of them to see if any would do what I wanted, but no luck. Do you have a suggestion for how to get this to work? Say I have a label/text field that is displaying the background text or maybe just shows a 0 to start. I’d like to be able to have my buttons change what that that label displays. For example, so far I’ve tried things like:
label1 = ui.Label(title=‘0’)
Change = ui.Button(title=‘Change label’)
Change.action = lambda sender: change_label()
Or, with a text view:
Original_text = ui.TextView(Title=‘0’)
If there is an easy way to do it with the .pyui, that would be useful as well!
I hope that’s clear. I’m just hoping to have a text that will update with a new value/text when I push the button (that now works)
@mcriley821 if you look above, i have a function there that has the code. What I’m really struggling with is getting a button to run it. I’m not sure if I am just putting it in the wrong place or what. I took the code that was in the game_menu.py example included with the app and used it as the skeleton to build my code. It works great and pulls up a very good looking menu, I was able to rename the buttons to what I wanted, they click and change to yellow when they are clicked but I haven’t yet been able to attach a function call to it yet. Here is a good portion of it:
#class from game menu example class ButtonNode (SpriteNode): def __init__(self, title, *args, **kwargs): SpriteNode.__init__(self, 'pzl:Button1', *args, **kwargs) button_font = ('Avenir Next', 20) self.title_label = LabelNode(title, font=button_font, color='black', position=(0, 1), parent=self) self.title = title #class from game menu example class MenuScene (Scene): def __init__(self, title, subtitle, button_titles): Scene.__init__(self) self.title = title self.subtitle = subtitle self.button_titles = button_titles def setup(self): button_font = ('Avenir Next', 20) title_font = ('Avenir Next', 36) num_buttons = len(self.button_titles) self.bg = SpriteNode(color='black', parent=self) bg_shape = ui.Path.rounded_rect(0, 0, 340, num_buttons * 64 + 140, 8) bg_shape.line_width = 4 shadow = ((0, 0, 0, 0.35), 0, 0, 24) self.menu_bg = ShapeNode(bg_shape, (1,1,1,0.9), '#15a4ff', shadow=shadow, parent=self) self.title_label = LabelNode(self.title, font=title_font, color='black', position=(0, self.menu_bg.size.h/2 - 40), parent=self.menu_bg) self.title_label.anchor_point = (0.5, 1) self.subtitle_label = LabelNode(self.subtitle, font=button_font, position=(0, self.menu_bg.size.h/2 - 100), color='black', parent=self.menu_bg) self.subtitle_label.anchor_point = (0.5, 1) self.buttons =  for i, title in enumerate(reversed(self.button_titles)): btn = ButtonNode(title, parent=self.menu_bg) btn.position = 0, i * 64 - (num_buttons-1) * 32 - 50 self.buttons.append(btn) self.did_change_size() self.menu_bg.scale = 0 self.bg.alpha = 0 self.bg.run_action(A.fade_to(0.4)) self.menu_bg.run_action(A.scale_to(1, 0.3, TIMING_EASE_OUT_2)) self.background_color = 'white' def norm_attack(self, sender): self.Timboslice.normal_attack()
This is the main portion of the ui portion that I am trying to learn. I got the rest of my code, that includes the classes and functions in the last 2 lines, to work in a normal python environment so I know that those should not be the issue. It’s learning how to incorporate the ui module to get the buttons to run the methods.
In your case, since you are presumably defining the button inside of init? It should be
def norm_attack(self, sender): self.Timboslice.normal_attack() # below is, I assume, inside of __init__??? self.normal = ButtonNode('Normal attack') self.normal.action = self.norm_attack self.buttons.append(self.normal)
In that case, since your method accepted sender, action can just point to the method. But of course it must be self.norm_attack, since norm_attack is an instance method.
This, I think, is one of my main problems. I had the above code under def setup in the Menuscene class. I moved the 3 lines after your comment into the Menuscene class init and now it is giving me an error that ‘Menuscene’ object has no attribute ‘norm_attack’. I am probably still not putting it in the correct spot. I have a Player class that has the methods such as self.norm_attack, I have the ButtonNode class and I have the Menuscene class. In the Menuscene class i have the methods setup (which the def norm_attack() is), did_change_size, update, touch_began and touch_ended.I have tried several different locations for these buttons with no success. I am sorry, but I must have really missed where it specifies this in the documentation. Thanks for your help so far.
The functions that I am calling with these buttons (self.Timboslice.normal_attack()) have a prints statement at the end of them and they return some information, though I haven’t used that returned info yet.
Not to sound condescending, but are your print statements before your returns, or accessible before returns?
Yes, if the function prints a statement then the print statement is before the return. The line above the return, actually.
Btw, in your example:
B=ui.Button(title='roll') B.action= lambda sender:roll_the_dice()
Is probably what you want.
Okay, I have taken what i think you were saying and applied it to my code:
def norm_attack(self, sender): self.Timboslice.normal_attack() self.normal = ButtonNode('Normal >attack') self.normal.action = lambda >sender:norm_attack() self.buttons.append(self.normal)
Now I can run my program without any errors, the buttons click and have the right titles, but nothing happens when i click them. The functions that I am calling with these buttons (self.Timboslice.normal_attack()) have a prints statement at the end of them and they return some information, though I haven’t used that returned info yet. But I was expecting it to print to the console. Currently I have the buttons defined, as above, in setup. I’m wondering if I need to move them to update or touch_began sections?
Again, I really appreciate your help, I’m really trying to get a better understanding and since I dont have a ton of experience sometimes what is clear to you doesn’t have the same context to me.
No, in a method, the second argument (first non-self argument) must be sender.
Now, you could do
def roll_the_dice(self, sender, size=6, number=1): #sender is first non self param, and other params have default kwargs listed
-- basically provide default arguments so that when the ui system calls fcn(sender), it will still work
Or, you can set your action using a lambda or partial. For instance, in your example, you could set your button action as
button.action=lambda sender:dice.roll_the_dice(5, 10, sender)
Basically, what the ui system will do is try to execute:
As long as you can call the action this way, you are all set.
As an aside, since you can set your own attribs on ui.Button, a common thing to do is set all the other parameters as attribs in your button:
button.dice_number=3 button.dice_size=6 # then your action would look like.. def roll_the_dice(self, sender): number=sender.dice_number size=sender.dice_size # rest of your original code
You could also have simple wrappers that call existing classes based on attribs in sender, and strip out the sender argument if the existing method doesn't need it.
def roll_action(sender): dice.roll_the_dice(sender.dice_number, sender.dice_size)
Okay maybe I am missing something. I went back to TKinter in Python on my laptop and was able to get the buttons working within minutes with this very code. For instance:
normal_roll = Button(root, padx=20, pady=20, command=lambda: roll_the_dice)
Then place the button in root with grid or pack, of course. But, this worked just fine. It prints the info to the console that it is supposed to and it is easy.
Hopefully there is an easy correlation from TKinter to the ui module. I’m thinking its just me not understanding the reasoning and placement of sender, but I could be wrong about that too. Any thoughts or advice you have would be appreciated, @JonB, or anyone else. Thanks again.
class myclass(object): def action(self, sender): # your code here
So it should look like this?
Class Dice(object): def roll_the_dice(self, sender): Roll_total = 0 Roll_list =  For i in range(self.number): Num = randint(1, self.size) Roll.list.append(num) For i in roll_list: Roll_total += i Return roll_total
And what if the function takes other parameters? Like...
Class Dice(object): def roll_the_dice(self, size, number, sender): Roll_total = 0 Roll_list =  For i in range(self.number): Num = randint(1, self.size) Roll.list.append(num) For i in roll_list: Roll_total += i Return roll_total
But I thought the documentation says a function like that can only have 1 parameter, 2 if it is a method in a class?
Thanks for the response, I appreciate the help.
Hi, I’m very new to Pythonista, new-ish to Python in general. I’m having trouble with getting my buttons working. I’m trying to have a button that prints a statement from the body of code. I keep getting a sender error that says I need to include sender, but when I add sender into the code for the button action it says ‘sender’ is not defined. I dont even know if I have it in the right place! So my code goes something like this (I took some parts from the game menu example included):
(Dice is a class I defined to give me a random number like rolling a die)
def touch_began(self, touch): touch_loc = self.menu_bg.point_from_scene(touch.location) for btn in self.buttons: if touch_loc in btn.frame: sound.play_effect(etc...) btn.texture = Texture(etc...) if btn.title == ‘Roll the Dice’: print(Dice.roll_the_dice(6, 1, sender)
I’m sure there’s multiple things I’m missing here but help would be appreciated!
Does anyone have any suggestions?