I need to create a program that has two buttons and a label that displays the current value of a counter. One button, bearing the text +1, should add one to the counter, while the other button, labelled -1, should subtract one from it (there is no minimum and maximum value). The counter should start at zero.
When designing the functions for each button, we need to get the current value, change the value depending on the button press, then set the new value.
Hint: as in the button example above you will need two global variables: one for the current count and one for the label widget.
I worked and after much trial and error, I got it working as follows:
from tkinter import *
from tkinter.ttk import *
def plus_one():
"""Increments counter by 1 """
global click_counter, counter_label
click_counter += 1
counter_label["text"] = str(click_counter)
def minus_one():
"""Reduces counter value by 1"""
global click_counter, counter_label
click_counter -= 1
counter_label["text"] = str(click_counter)
def main():
"""Program"""
global click_counter, counter_label
click_counter = 0
window = Tk()
counter_label = Label(window, text=str(click_counter))
counter_label.grid(row=0, column=0)
plus_one_button = Button(window, text="+1", command=plus_one)
plus_one_button.grid(row=2, column=0)
minus_one_button = Button(window, text="-1", command=minus_one)
minus_one_button.grid(row=2, column=1)
window.mainloop()
main()
I was wondering if it was possible to encapsulate the GUI code into a class Countergui like this:
from tkinter import *
from tkinter.ttk import *
# Write your code here
def main():
"""Set up the GUI and run it"""
window = Tk()
counter_gui = CounterGui(window)
window.mainloop()
main()
Additional Information:
Recreate the program but encapsulate the GUI code in a class CounterGui. This program should have all the same functionality as the program in Question 4: The program has two buttons and a label that displays the current value of a counter. One button, bearing the text +1, should add one to the counter, while the other button, labelled -1, should subtract one from it. The counter should start at zero.
It is best to create a class for the counter, and further, best not to create globals if you can avoid it.
class Counter(object):
def __init__(self, start=0):
self.count = start
def increment(self):
self.count += 1
def decrement(self):
self.count -= 1
It is best not to create a mixed use class - this class will take care of handling the storage, increment and decrement of your values.
You can then create a separate class to draw the buttons and basic component interface and pass it an instance of your Counter class
Good luck
Related
Whatever I do to my checkbutton, it does not seem to set the variable.
Here's the parts of the code that are involved:
class Window:
def __init__(self):
self.manualb = 0 #to set the default value to 0
def setscreen(self):
#screen and other buttons and stuff set here but thats all working fine
manual = tkr.Checkbutton(master=self.root, variable=self.manualb, command=self.setMan, onvalue=0, offvalue=1) #tried with and without onvalue/offvalue, made no difference
manual.grid(row=1, column=6)
def setMan(self):
print(self.manualb)
#does some other unrelated stuff
It just keeps printing 0. Am I doing something wrong? Nothing else does anything to manual.
You're looking for IntVar()
IntVar() has a method called get() which will hold the value of the widget you assign it to.
In this particular instance, it will be either 1 or 0 (On or off).
You can use it something like this:
from tkinter import Button, Entry, Tk, Checkbutton, IntVar
class GUI:
def __init__(self):
self.root = Tk()
# The variable that will hold the value of the checkbox's state
self.value = IntVar()
self.checkbutton = Checkbutton(self.root, variable=self.value, command=self.onClicked)
self.checkbutton.pack()
def onClicked(self):
# calling IntVar.get() returns the state
# of the widget it is associated with
print(self.value.get())
app = GUI()
app.root.mainloop()
This is because you need to use one of tkinter's variable classes.
This would look something like the below:
from tkinter import *
root = Tk()
var = IntVar()
var.trace("w", lambda name, index, mode: print(var.get()))
Checkbutton(root, variable=var).pack()
root.mainloop()
Essentially IntVar() is a "container" (very loosely speaking) which "holds" the value of the widget it's assigned to.
My Requirement
I am developing a program in which I handle space and KeyPress-space Events to handle time based event.
What I am doing is that on the start of program a window gets open to display a time interval, say "5" which depicts 5 Second. Once the time lapses(5Sec) the text "5" gets disappear.
After that the user is required to press space key, when user press space key the same number will display until user don't release space key.
I have given 2 second gap to show next interval(once user releases the space) and in that interval the number should not appear which was appearing in previous set time interval space Event.
Problem Statement:
The issue that I am facing is that I have managed to display number(text) on key press but when I key-release, time interval is not disappearing for 2 second.
Here is my Code:
from tkinter import Label, Tk
import time
times = [3, 4, 5]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
print('called', loop)
self.lbl.pack()
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
Here you go..Please let me know if you have any further query -
from tkinter import Label, Tk
import time
times = [3, 4, 5]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
print('called', loop)
self.lbl.pack()
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
self.parent.update()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
You appear to have found a "crevice" in the way Python interacts with the Tk main loop. At least on my system (Python 2.7, Fedora 22, x86_64) none of the operations in showLabel() have any effect on the screen until the function (including time.sleep(2)) completes.
If you modify the code as follows for troubleshooting, I think you can see what I mean.
def hideLabel(self, event):
global loop, times
# self.lbl.config(text=times[loop])
self.lbl.config(bg = "red")
self.lbl.pack()
def showLabel(self, event):
global loop
# self.lbl.pack_forget()
self.lbl.config(fg = "blue")
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
# self.lbl.config(text= '')
self.lbl.config(bg = "green")
The second label now shows up below the first. The first label gets a blue foreground, but not on the keypress, as we would expect. It apparently only gets it when the function returns, after time.sleep().
I don't pretend to completely understand this - someone who has a deep understanding of Python and Tk might explain. One workaround for this particular program is to add update_idletasks(). This (warning: sloppy terminology ahead) forces the Tk main loop to immediately process pending events.
def showLabel(self, event):
global loop
# self.lbl.pack_forget()
self.lbl.config(fg = "blue")
master.update_idletasks()
time.sleep(2)
loop += 1
Demo(self.parent)
On my system, that made the first label turn blue before time.sleep(). I didn't experiment beyond that.
p.s showLabel() hides the label and hideLabel() shows it?
EDIT: The following slight mod of the original posted code works on my system almost as I would expect. I'm not 100% sure what it is intended to do. But the current label does disappear when I tap the space bar, and the next one shows up 2 seconds later, only to be blanked a variable time after that.
The only puzzle I ran into was that my system basically went crazy when I tried to hold down the space bar for more than 2 seconds. It turned out that the key auto-repeat was kicking in on my system, giving me dozens of keystrokes and causing dozens of subscript out-of-range errors. I gave up at that point so I still don't know how the program behaves if given a single space-down followed 3 seconds later by a single space-up.
from Tkinter import Label, Tk
import time
times = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
loop = 0
class Demo:
def __init__(self, master):
global times, loop
self.parent = master
self.lbl = Label(master, text=times[loop])
master.after(times[loop]*1000, self.hideLabelAfterSomeTime)
master.bind("<space>", self.hideLabel)
master.bind("<KeyRelease-space>", self.showLabel)
master.bind("<h>", self.showLabel)
print('called', loop)
self.lbl.pack()
for child in master.children: print child
def hideLabel(self, event):
global loop, times
self.lbl.config(text=times[loop])
self.lbl.pack()
def showLabel(self, event):
global loop
self.lbl.pack_forget()
master.update_idletasks()
time.sleep(2)
loop += 1
Demo(self.parent)
def hideLabelAfterSomeTime(self):
self.lbl.config(text= '')
self.lbl.config(bg = "green")
master = Tk()
app = Demo(master)
master.geometry("400x300+400+200")
master.mainloop()
If the pack_forget has no effect on your system even with the update_idletasks(), I'd have to guess that the Tk main loop is more platform dependent than is widely known.
I am trying to make a button that when clicked updates the number on a label. What I am trying to accomplish is that when someone scores a goal, you can click the Goal! button and it will update the teams score.
import sys
from tkinter import *
root = Tk()
class team1:
score = 0
def goal(self):
self.score += 1
team1_attempt.set(text = self.score)
team1 = team1()
team1_attempt = Label(text = team1.score).pack()
team1_button = Button(text="Goal!", command = team1.goal).pack()
Hope someone can help! New to python.
You have two problems with your code.
First problem:
team1_attempt = Label(text = team1.score).pack()
This sets team1_attempt to None, because pack(0 returns None. If you want to save a reference to a widget so you can interact with it later you must do widget creation and widget layout in two steps.
Second problem:
team1_attempt.set(text = self.score)
To change an attribute of a widget, use the configure method. I don't know what documentation you read that says to call set on a label widget, but that documentation is wrong. Use configure, like so:
test1_attempt.configure(text=self.score)
Instead of using a label, try using an Entry widget that inserts the score into the Entry widget. For example:
class test:
def __init__(self, master):
self.goalButton = Button(master,
text = "goal!",
command = self.goalUpdate)
self.goalButton.pack()
self.goalDisplay = Entry(master,
width = 2)
self.goalDisplay.pack()
self.score = 0
def goalUpdate(self):
self.goalDisplay.delete(1.0, END) # Deletes whatever is in the score display
score = str(self.score)
self.goalDisplay.insert(0, score) # Inserts the value of the score variable
I am having a problem with a fairly simple app.
It performs properly, but I would like it to perform a little slower.
The idea is to randomly generate a name from a list, display it, then remove it fromthe list every time a button is clicked.
To make it a little more interesting, I want the program to display several names before
picking the last one. I use a simple for loop for this. However, the code executes so quickly, the only name that winds up displaying is the last one.
using time.sleep() merely delays the display of the last name. no other names are shown.
here is my code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
import random
import time
class Application(Frame):
def __init__(self, master):
""" Initialize the frame. """
super(Application, self).__init__(master)
self.grid()
self.name_list = ["Thorin","Tyler","Jose","Bryson","Joe"]
self.create_widget()
def create_widget(self):
self.lbl = Label(self)
self.lbl["text"] = "Click to spin"
self.lbl["font"] = ("Arial", 24)
self.lbl.grid()
self.bttn = Button(self)
self.bttn["text"]= "Spin"
self.bttn["command"] = self.spin
self.bttn.grid()
def spin(self):
if self.name_list:
for i in range(5):
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
self.name_list.pop(index)
else:
self.lbl["text"] = "No more names"
self.lbl.grid()
def main():
root = Tk()
root.title("Click Counter")
root.geometry("600x600")
app = Application(root)
root.mainloop()
if __name__ == '__main__':
main()
This is a pretty common class of problems related to GUI programming. The heart of the issue is the window drawing manager. As long as your function is executing, the drawing manager is frozen; updating the label's text will have no apparent effect until your function ends. So if you have a for loop with a sleep(1) command inside, all it will do is freeze everything for five seconds before updating with your final value when the function finally ends.
The solution is to use the after method, which tells Tkinter to call the specified function at some point in the future. Unlike sleep, this gives the drawing manager the breathing room it requires to update your window.
One possible way to do this is to register six events with after: five for the intermediate name label updates, and one for the final name change and pop.
def spin(self):
def change_name():
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
def finish_spinning():
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
self.name_list.pop(index)
if self.name_list:
name_changes = 5
for i in range(name_changes):
self.after(100*i, change_name)
self.after(100*name_changes, finish_spinning)
else:
self.lbl["text"] = "No more names"
self.lbl.grid()
(disclaimer: this is only a simple example of how you might use after, and may not be suitable for actual use. In particular, it may behave badly if you press the "spin" button repeatedly while the names are already spinning. Also, the code duplication between change_name and finish_spinning is rather ugly)
The code as it is can show the same item twice since it chooses a new random number each time and so will choose the same number part of the time. Note that you do not pop until after the loop which means that each time you run the program you will have one less name which may or may not be what you want. You can use a copy of the list if you want to keep it the same size, and/or random.shuffle on the list and display the shuffled list in order. Also you only have to grid() the label once,
class Application():
def __init__(self, master):
""" Initialize the frame. """
self.master=master
self.fr=Frame(master)
self.fr.grid()
self.name_list = ["Thorin","Tyler","Jose","Bryson","Joe"]
self.ctr=0
self.create_widget()
def create_widget(self):
self.lbl = Label(self.master width=30)
self.lbl["text"] = "Click to spin"
self.lbl["font"] = ("Arial", 24)
self.lbl.grid()
self.bttn = Button(self.master)
self.bttn["text"]= "Spin"
self.bttn["command"] = self.spin
self.bttn.grid()
def change_label(self):
self.lbl["text"] = self.name_list[self.ctr]
self.ctr += 1
if self.ctr < 5:
self.master.after(1000, self.change_label)
else:
self.ctr=0
def spin(self):
if self.name_list and 0==self.ctr: # not already running
random.shuffle(self.name_list)
self.change_label()
else:
self.lbl["text"] = "No more names"
if __name__ == '__main__':
root = Tk()
root.title("Click Counter")
root.geometry("600x600")
app = Application(root)
root.mainloop()
I am trying to create a Tkinter program that will store an int variable, and increase that int variable by 1 each time I click a button, and then display the variable so I can see that it starts out as 0, and then each time I click the button it goes up by 1. I am using python 3.4.
import sys
import math
from tkinter import *
root = Tk()
root.geometry("200x200")
root.title("My Button Increaser")
counter = 0
def nClick():
counter + 1
def main_click():
mLabel = Label(root, text = nClick).pack()
mButton1 = Button(text = "Increase", command = main_click, fg = "dark green", bg = "white").pack()
root.mainloop()
Ok so there are a few things wrong with your code so far. My answer basically changes what you have already into the easiest way for it to do what you want.
Firstly you import libraries that you don't need/use (you may need them in your whole code, but for this question include a minimal example only). Next you must deifne the counter variable as a global variable, so that it will be the same in the function (do this inside the function as well). Also you must change counter + 1 to counter += 1 so it increments the variable
Now the code will be able to count, but you have set variables as Buttons, but then made them None type objects, to change this .pack() the variable on the next line. You can get rid of the second function as you only need one, then you change the command of the button and its text to counter. Now to update the text in the button, you use .config(text = counter) which configures the button.
Here is the final solution (changes button value and has no label, but this is easily changed):
from tkinter import *
root = Tk()
root.geometry("200x200")
root.title("My Button Increaser")
global counter
counter = 0
def nClick():
global counter
counter += 1
mButton1.config(text = counter)
mButton1 = Button(text = counter, command = nClick, fg = "darkgreen", bg = "white")
mButton1.pack()
root.mainloop()
hope that helps!
You could use Tkinter variables. They are specially useful when you need to modify a data that other widgets might interact with. Here is a look alike code to the one in the question, but instead of defining counter as a normal variable, it is a variable from Tkinter.
import tkinter
import sys
root = tkinter.Tk()
root.geometry("200x200")
root.title("His Button Increaser")
counter = tkinter.IntVar()
def onClick(event=None):
counter.set(counter.get() + 1)
tkinter.Label(root, textvariable=counter).pack()
tkinter.Button(root, text="Increase", command=onClick, fg="dark green", bg = "white").pack()
root.mainloop()
Instead of passing the value this variable holds to the text attribute of the Label, we assign the variable to textvariable attribute, so when the value of the variable gets updated, Label would update the displayed text accordingly.
When you want to change the value of the variable, you'd need to call the set() method of the variable object (see onClick) instead of assigning the value directly to it.