Tkinter Button Binded to Keyboard Won't Count Class Parameter - python

I am trying to create the function 'count' that takes in an integer in the form of a variable, and adds 1 to it every time the return key is pressed, saving to the variable each time.
The argument needs to remain generic, because in the future this will run the same 'count' function on multiple variables depending on which button is pressed.
I've tried making messi a global variable by putting global messi at the top, but the same problem occurs.
import tkinter as tk
class PlayerStats:
def __init__(self, name, touches):
team = "Barcelona"
self.name = name
self.touches = touches
def count(number):
number = number + 1
print(number)
messi = PlayerStats("Messi",0)
root = tk.Tk()
root.bind('<Return>', lambda event :PlayerStats.count(messi.touches))
root.mainloop()
When I run this snippet, it iterates it once, from 0 to 1, and then resets always printing out 1.
Any thoughts on why this is happening and how to fix would be appreciated!!

You are not saving the result of the operation.
You're instanciating your PlayerStats class with a value of 0 for touches.
That value is then never mutated throughout your code.
When tkinter is calling your count method, it increments number but that variable never leaves the scope of the method, and is thus garbage collected.
To fix it, you should change your class to something like
import tkinter as tk
class PlayerStats:
def __init__(self, name, touches):
team = "Barcelona"
self.name = name
self.touches = touches
def count(self): # the first argument of a method is always a reference to the instance
self.touches += 1
print(self.touches)
messi = PlayerStats("Messi", 0)
root = tk.Tk()
root.bind('<Return>', lambda event: messi.count()) # You need to call the method on the instance you created.
root.mainloop()

Thanks for your help Dogeek. My functioning code from above now looks like this:
import tkinter as tk
class PlayerStats:
def __init__(self, name, touches):
team = "Barcelona"
self.name = name
self.touches = touches
def count(self, number):
self.touches += 1
print(number)
messi = PlayerStats("Messi",0)
root = tk.Tk()
root.bind('<Return>', lambda event :messi.count(messi.touches))
root.mainloop()
One thing this does not solve though is the ability to reuse that function for different variables. I am now trying to come up with an elegant way to do something like this:
import tkinter as tk
class PlayerStats:
def __init__(self, name, touches, shots):
team = "Barcelona"
self.name = name
self.touches = touches
self.shots = shots
def count(self, number):
self.number += 1
print(number)
messi = PlayerStats("Messi",0)
root = tk.Tk()
root.bind('<Return>', lambda event :messi.count(messi.touches))
root.bind('<s>', lambda event :messi.count(messi.shots))
root.mainloop()
where number represents either messi.shots or messi.touches depending on what key is pressed. I'd like to do this without recreating a bunch of nearly identical functions for each key.

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 get reference to an instance attribute, and modify its value from outside

I'm working on an application in tkinter. I have many Entry widgets in UI, and a few classes in app engine. I need to bind tkinter variables of those entries to instances attributes.
i.e.:
class Pipe(Variable):
"""class for pipes"""
def __init__(self):
self.diameter = 0
self.variables = {}
pipe1 = Pipe(self)
pipe2 = Pipe(self)
I want to bind value from one entry to pipe1.diameter, and value from another entry to pipe2.diameter. I'm doing it by a trace function, where is lambda statement, pointing to a function, which identifies entry, and, using a dictionary proper for each instance, pass a value from entry to dictionary value. Dictionaries are produced like here, and then passed as instance attribute:
def pipe1_vars(object_):
variables = {
'ui_variable_name_for_pipe1_diameter': [object_.diameter]
}
return variables
def pipe2_vars(object_):
variables = {
'ui_variable_name_for_pipe2_diameter': [object_.diameter]
}
return variables
pipe1.variables = pipe1_vars(pipe1)
pipe2.variables = pipe2_vars(pipe2)
Unfortunately, Variable class method, assigning value, isn't working properly.
class Variable():
def set_var_value(variable_name, value):
ui_variable = tkinterbuilder.get_variable(variable_name)
self.variables[variable_name][0] = value
if ui_variable.get() != value:
ui_variable.set(value)
Obviously self.variables[variable_name][0] is something different than self.diameter. The dictionary value is changing, but instance.diameter stays the same.
How can I pass a real instance attribute to this method, instead of a copy in a dictionary value?
I'm assuming it is important to my app, to build something working as those dictionaries, because i need to bind similar attributes of different pipes to different entries - so it's have to be defined outside of a Pipe() class. I don't know if I should change dictionary to something else, or maybe should I rebuild those functions, building dictionary. I've run out of ideas, what to ask google.
Code is much complex, I've posted only most important elements, but if any other details are important, please note in comment.
If the number of Pipe attributes is small, make them properties, and when you create a Pipe object, pass it the corresponding tk binded variable:
import tkinter as tk
from tkinter import Tk, ttk
root = Tk()
var_e1 = tk.StringVar()
def print_e1():
print(var_e1.get())
def inc_e1():
var_e1.set(int(var_e1.get())+1)
class Pipe():
def __init__(self, tkvar):
self.tkvar = tkvar
tkvar.set('')
#property
def diameter(self):
return self.tkvar.get()
#diameter.setter
def diameter(self, value):
self.tkvar.set(value)
e1 = tk.Entry(root, textvariable=var_e1)
b1 = tk.Button(root, text='Print e1', command=print_e1)
b2 = tk.Button(root, text='Increment e1', command=inc_e1)
e1.pack(side=tk.LEFT)
b1.pack()
b2.pack()
p1 = Pipe(var_e1)
p1.diameter = 200
root.mainloop()

random TKinter Dice Roller

import Tkinter
import random
win = Tkinter.Tk()
win.title('Dice Roller')
def mainloop():
class Die:
def __init__(self,ivalue, parent):
self.value = ivalue
self.display = Tkinter.Label(parent,relief='ridge',borderwidth=4, text=str(self.value))
def roll(self):
self.value = random.randint(1,6)
self.display.config(text=str(self.value))
class diceRoller:
def rolldice():
d1.roll()
d2.roll()
d3.roll()
def __init__(self):
self.diceList = []
self.win = Tkinter.Tk("Dice Roller")
for i in range(3):
di = Die(self.win)
self.dieList.append(di)
rolldice()
row1 = Tkinter.Frame(win)
row2 = Tkinter.Frame(win)
d1.roll.display.pack(side="left")
d2.roll.display.pack(side="left")
d3.roll.display.pack(side="left")
row1.pack()
rolldice = Tkinter.Button(row2, command=rolldice(), text = "Roll")
rolldice.pack()
row2.pack()
win.mainloop()
I'm having trouble with my python code with Tkinter. I'm trying to get it to produce a window with three buttons that present the numbers rolled on the dice and another that lets me re roll the dice.
The code you have inside class diceRoller under the method rolldice has to be in some method. It cannot just stay in a class. Create a method using def and add that code to it.
If a name of a method is __init__, it is called a constructor. A constructor is a method that will be called when the object is created (i.e. when you do diceRoller()). You perhaps should put the code, that's now just laying inside diceRoller into such a method/constructor, I don't know (you should know if that's the case).
Note: In Python we usually write first characters of class names uppercase.

button command option in tkinter

In the little GUI app below. When I use button's command option to call a function. It doesn't work like this: self.update() rather it works like this: self.update. Why so? Is is some special way that command option of a button works? I think a method or a function should be called with those braces (), unless it's a property:
i.e.
#name.setter:
def setter(self, name):
self.name = name
#main
object.name = "New_obj"
Note: The above is just a template so you might get my point. I didn't write the complete valid code. Including class and everything.
from tkinter import *
class MuchMore(Frame):
def __init__(self, master):
super(MuchMore,self).__init__(master)
self.count =0
self.grid()
self.widgets()
def widgets(self):
self.bttn1 = Button(self, text = "OK")
self.bttn1.configure(text = "Total clicks: 0")
self.bttn1["command"] = self.update # This is what I am taking about
self.bttn1.grid()
def update(self):
self.count += 1
self.bttn1["text"] = "Total clicks" + str(self.count)
#main
root = Tk()
root.title("Much More")
root.geometry("324x454")
app = MuchMore(root)
It is a high order function, meaning you are referencing a function as an object. You are not calling the function and assigning the command to the return value of the function. See here for more information.
The command parameter takes a reference to a function -- ie: the name of the function. If you add parenthesis, you're asking python to execute the function and give the result of the function to the command parameter.

Categories

Resources