Kivy Python - Affecting Buttons Inside a Different Function - python

I am trying to change the attribute of a button inside of a different method. This is what I am currently using:
def _finish_init(self, dt):
self.ids.grid_1.cols = 5
def callfun(self, *args):
self.ChangePic()
# Creating a variable to use to determine if the buttons should be editable
binded = 0
# Loop to make 15 different buttons on screen
for x in range(15):
self.buttons.append(Button())
self.ids.grid_1.add_widget(self.buttons[x])
self.buttons[x].background_normal = 'YOUTUBE.png'
self.buttons[x].background_down = 'opacity.png'
self.buttons[x].bind(on_release=partial(self.HoldButtonNum, x))
# Make the button switch screens to input from calling the function above
if edit_mode is True:
self.buttons[x].bind(on_release=partial(self.SwitchScreenInput, x))
def ChangePic(self, *args):
_finish_init().buttons[1].background_normal = 'folder.png'
When ChangePic is called I get the error:
NameError: name '_finish_init' is not defined
Any suggestions?

It looks like both functions are members of a class, you would need to use
self._finish_init()...

Related

Python self.attribute cannot be seen when calling class method

I have a problem related to a TKinter GUI I am creating, but the problem is not necessarily specific to this library.
Background
I am currently in the advanced stage of a python self-learning course. The learning module I am on is covering TKinter for creating interactive GUI's. I am making a game whereby randomly generated numbered buttons must be clicked in succession in the quickest time possible.
Brief: https://edube.org/learn/pcpp1-4-gui-programming/lab-the-clicker
Problem
Under my class, game_grid, I have created an instance variable; 'self.holder', a 25 entry dictionary of {Key : TkinterButtonObject} form
When calling this instance variable for use in a class method, I get the following error:
AttributeError: 'game_grid' object has no attribute 'holder'
I have a print statement under class init which proves this attribute has been successfully created. I have made sure my spacing and tabs are all OK, and have tried every location for this variable, including using as a class variable, and a global variable to no avail - as it is an semi-complex object. I don't see what difference it should make, but any ideas would be much appreciated. I am also aware this could be done without classes, but I am trying to adopt DRY principles and orthogonality in all of my programs.
Thanks in advance.
Full Code:
import tkinter as tk
from tkinter import*
import random
from tkinter import messagebox
import time
win = tk.Tk()
class game_grid:
def __init__(self, win):
self.last_number = 0
self.number_buttons = {}
self.row_count = 0
self.column_count = 0
#Generate a list of 25 random numbers
self.number_list = random.sample(range(0, 999), 25)
#Puts the numbers in a dictionary (number : buttonobject)
self.holder = {i: tk.Button(win, text = str(i), command = game_grid.select_button(self, i)) for i in self.number_list}
#pack each object into window by iterating rows and columns
for key in self.holder:
self.holder[key].grid(column = self.column_count, row = self.row_count)
if self.column_count < 4:
self.column_count += 1
elif self.column_count == 4:
self.column_count = 0
self.row_count += 1
print(self.holder)
def select_button(self, number):
if number > self.last_number:
self.holder[number].config(state=tk.DISABLED)
self.last_number = number
else:
pass
class stopclock():
def __init__(self):
#Stopclock variable initialisation
self.time_begin = 0
self.time_end = 0
self.time_elapsed= 0
def start(self):
if self.time_begin == 0:
self.time_begin = time.time()
return("Timer started\nStart time: ", self.time_begin)
else:
return("Timer already active")
def stop(self):
self.time_end = time.time()
self.time_elapsed = time_end - time_begin
return("Timer finished\nEnd time: ", time_begin,"\nTime Elapsed: ", time_elapsed)
play1 = game_grid(win)
win.mainloop()
Perhaps you meant:
command = self.select_button(self, i)
Update:
Though from research:How to pass arguments to a Button command in Tkinter?
It should be:
command = lambda i=i: self.select_button(i)
You call select_button from inside the dict comprehension of holder. select_button then tries to use holder, but it is not yet defined. You don't want to actually call select_button, but assign a function to the button, like that:
self.holder = {i: tk.Button(window, text=str(i), command=lambda i=i: self.select_button(i)) for i in self.number_list}

Issue retrieving values from objects created by button function

I'm working on a small project and I'm having issues retrieving the values stored in combo boxes. The program has a "plus" button that creates additional boxes beneath the existing ones. They are created by calling a "create" function that makes a new instance of the ComboBox class, where the box is created and put onto the screen. A separate "submit" function is then supposed to loop through and retrieve all of the box values and store them in a list. My main flaw is that I used data in the variable names, but I have no clue how else to do this in this scenario. Does anyone have an alternative solution?
(there are some off screen variables that are show used here as parameters, but there are definitely not the source of the issue)
class ComboBox:
def __init__(self, master, counter, fields):
self.master = master
self.counter = counter
self.fields = fields
self.field_box = ttk.Combobox(width=20)
self.field_box["values"] = fields
self.field_box.grid(row=counter + 1, column=0, pady=5)
def get_value(self):
value = self.field_box.get()
return value
def create():
global entry_counter
name = "loop"+str(entry_counter-1)
name = ComboBox(window, entry_counter, fields)
values.append(name.get_value())
entry_counter += 1
def submit():
for i in range(1, entry_counter):
name = "loop" + str(entry_counter-1)
values.append(name.get_value())
For example, if I created 2 boxes and selected the options "test1" and "test2" I would want the my values list to contain ["test1, "test2"]
Not sure I understand the question right, but I guess you are asking about how to loop throw all instances of ComboBox. You can just create an global array, append new instance into it in create() method:
comboboxes = []
def create():
...
comboboxes.append(new_instance)
def submit():
for combobox in comboboxes:
...
You're on the right track with .get(). I believe your solution is that your get_value function also needs an event parameter:
def get_value(self, event):
value = self.field_box.get()
return value
See the following:
Getting the selected value from combobox in Tkinter
Retrieving and using a tkinter combobox selection

How to delete (or de-grid) Tkinter GUI objects stored in a list (Python)

I'm trying to make a program that creates some random amount of GUI objects in Tkinter and stores them in a list. Here (in the code below) I have a for loop that creates a random amount of radio buttons. Each time a radio button object is created it is stored in the list 'GUIobjects'. I do this because otherwise I have no way of accessing the GUI objects later on. What I need to know, now, is how to delete or de-grid the objects.
I have tried self.radioButton.grid_forget(), but this only de-grids the last object created. I'm not sure of there's a way to access each object in the list and use .grid_forget(). If there is, that would be an option.
For now all I need to know is how to delete or de-grid the GUI objects after I create all of them.
from tkinter import *
import random
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.addButton()
def addButton(self)
GUIobjects = []
randInt = random.randint(3, 10)
self.radVar = IntVar()
for x in range(2, randInt):
self.radioButton = Radiobutton(self, text = "Button", variable = self.RadVar, value = x)
self.radioButton.grid(row = x)
print(GUIobjects)
# This is to show that one more object has been created than appears on the screen
self.radioButton.grid_forget()
# This de-grid's the last object created, but I need to de-grid all of them
def main():
a = App()
a.mainloop()
if __name__ == "__main__":
main()
As of right now, I try to de-grid the objects right after creating all of them. Once I find out how to de-grid each object, I will somehow need to make a button that de-grid's them (as compared to de-griding them right after they have been created). This button should be able to be put in a method other than 'addButtons' but still in the 'App' class.
Thanks!
You need to store references to each object. Create an empty list and append the reference to the list inside your loop.
self.radioButtons = []
for x in range(2, randInt):
self.radioButtons.append(Radiobutton(self, text = "Button", variable = self.RadVar, value = x))
self.radioButtons[-1].grid(row = x) # layout the most recent one
They won't be garbage collected unless you delete the reference as well.
for button in self.radioButtons:
button.grid_forget()
del self.radioButtons

Maya Python UI - Show an Objects ClassParent Class When Selected

Hello I need some help I'm a novice when it comes to advanced python coding, I've been trying to solve this problem but I'm unable to find an answer. I'm trying to find a way so that when someone clicks on an object inside Maya for example a basic sphere it will print the object class and parent class into each of the textFields; then should a user select something else like a cube it will instead show that object class&parent class. I know I need a function which will activate when an object is selected then place the value into the textFields, but I can't figure out how to do it. If anyone has a solution it would be greatly appreciated :)
import maya.cmds as cmds
from functools import partial
class drawUI(): #Function that will draw the entire window
#check to see if window exists
if cmds.window("UI_MainWindow", exists = True):
cmds.deleteUI("UI_MainWindow")
#create actual window
cmds.window("UI_MainWindow", title = "User Interface Test", w = 500, h = 700, mnb = False, mxb = False, sizeable = False)
cmds.columnLayout("UI_MainLayout", adjustableColumn=True)
cmds.text(label="Object's Class:")
ObjClass = cmds.textField(text = cmds.objectType,editable = False)
cmds.text(label="Object's Parent Class:")
ObjParClass = cmds.textField(editable = False)
cmds.showWindow("UI_MainWindow") #shows window
The main stumbling block is that you're using the class setup in a very unusual way. The usual pattern is to have an __init__ method that creates a new copy ("instance") of the class; in you case you're executing the code when the class is defined rather than when you invoke it. The usual class shell looks like:
class Something (object) # derive from object in python 2.7, ie, in maya
def __init__(self):
self.variable = 1 # set persistent variables for the object
do_something() # other setup runs now, when a new Something is created
def method(self):
print self.variable # you can use any variables defined in 'self'
The usual way to create a reactive UI in maya is to use a scriptJob. These will fire a script callback when certain events happen inside of Maya. The easiest event for your purpose is SelectionChanged which fires when the selection changes.
The other thing you want to do is figure out how to pack this into a class. Sometimes it's a good idea to create your class object and then give it a show() or layout() method that creates the actual UI -- a lot depends on how much you need to set the object up before showing the window.
In this case I put the behavior into the __init__ so it will create the UI when you make the object. The __init__ also creates the scriptJob and parents it to the window (so it will disappear when the window does).
class drawUI(object):
def __init__(self):
if cmds.window("UI_MainWindow", exists = True):
cmds.deleteUI("UI_MainWindow")
#create actual window
self.window = cmds.window("UI_MainWindow", title = "User Interface Test", w = 500, h = 700, mnb = False, mxb = False, sizeable = False)
cmds.columnLayout("UI_MainLayout", adjustableColumn=True)
cmds.text(label="Object's Class:")
self.ObjClass = cmds.textField(text = cmds.objectType,editable = False)
cmds.text(label="Object's Parent Class:")
self.ObjParClass = cmds.textField(editable = False)
cmds.showWindow(self.window)
cmds.scriptJob (e = ("SelectionChanged", self.update_UI), p= self.window)
def update_UI(self, *_, **__):
sel = cmds.ls(selection=True) or []
c = ["-none-", "-none-"]
if sel:
c = cmds.nodeType(sel[0], i = True)
cmds.textField(self.ObjClass, e=True, text = c[-1])
cmds.textField(self.ObjParClass, e=True, text = ", ".join(c[:-1]))
test = drawUI()
By using self.update_UI without arguments in the scriptJob, we make sure that the function that fires knows which object it's updating. Using the self variables for the textfields lets us update the UI that goes with the window without worrying about remembering names in some other scope.
More details here

tkinter button commands with lambda in Python

ive tried searching for a solution but couldn't find one that works. I have a 2d list of tkinter Buttons, and i want to change their Text when it is clicked by the mouse. I tried doing this:
def create_board(number):
print(number)
for i in range (0,number):
buttonList.append([])
for j in range(0,number):
print(i,j)
buttonList[i].append(Button(root, text = " ", command = lambda: update_binary_text(i,j)))
buttonList[i][j].pack()
Then when it is clicked it calls this function:
def update_binary_text(first,second):
print(first,second)
buttonList[first][second]["text"] = "1"
When i click a button, it simply does nothing, i had the program display the indexes of the button that was clicked, and they ALL show 4, 4 (this is when the variable number=5) Is there an solution to this?
this is my first python attempt for a class.
Thanks
You can fix this problem by creating a closure for i and j with the creation of each lambda:
command = lambda i=i, j=j: update_binary_text(i, j)
You could also create a callback factory with references to the button objects themselves:
def callback_factory(button):
return lambda: button["text"] = "1"
And then in your initialization code:
for j in range(0, number):
new_button = Button(root, text=" ")
new_button.configure(command=callback_factory(new_button))
new_button.pack()
buttonList.append(new_button)
Whenever I need a collection of similar widgets, I find it's simplest to enclose them in an object and pass a bound-method as callback rather than playing tricks with lambda. So, instead of having a list like buttonList[] with widgets, create an object:
class MyButton(object):
def __init__(self, i, j):
self.i = i
self.j = j
self.button = Button(..., command = self.callback)
def callback(self):
. . .
Now, you have a list buttonList[] of these objects, rather than the widgets themselves. To update the text, either provide a method for that, or else access the member directly: buttonList[i].button.configure(. . .) And when the callback is activated, it has the entire object and whatever attributes you might need in self.

Categories

Resources