I am trying to recreate the boardgame monopoly using python and tkinter. I know how to place a label on a canvas or a frame, but how should I do this command is being run from another function in the class? I tried it using some function within the class Board, but then the error rises that the label, canvas, etc. are not defined as this happens in __init__(self,parent). How can I solve these errors? Or should I take a different approach to this? Hope I made my problem clear.
import tkFileDialog
from random import randint
class Board(Frame):
def __init__(self,parent):
##create the board
frame = Frame(parent)
frame.pack()
Frame.__init__(self,parent)
frame2 = Frame(frame)
frame2.pack()
c=Canvas(frame2,width=480,height=480)
c.pack(expand=YES,fill=BOTH)
c.background=PhotoImage(file='Board.gif')
c.create_image(0,0,image=c.background,anchor='nw')
##Add player 1
player1=PhotoImage(file='plane.gif')
label_player1 = Label(c,image=player1)
label_player1.image=player1
label_player1.place(x=430,y=420)
##Add player 2
player2=PhotoImage(file='car.gif')
label_player2 = Label(c,image=player2)
label_player2.image=player2
label_player2.place(x=430,y=450)
button = Button(frame, text="Next turn", command=self.next_turn)
button.pack()
button = Button(frame, text="Roll the dice", command=self.roll)
button.pack()
def roll(self):
number=randint(2,12)
if b==0:
self.place_player_down()
return number
def place_player_down(self):
for i in range(number+1):
h=int(430-i*30)
while h>=0:
player2=PhotoImage(file='car.gif')
label_player2 = Label(c,image=player2)
label_player2.image=player2
label_player2.place(x=h,y=420)
root = Tk()
board = Board(root)
board.pack()
root.mainloop()
The approach is correct (wrap your Tkinter widgets in a class with the event handler functions as methods), but you forgot to set the widgets as attributes of the class using the reference to self:
class Board(Frame):
def __init__(self,parent):
# ...
self.c = Canvas(frame2,width=480,height=480)
self.c.pack(expand=YES,fill=BOTH)
# ...
def place_player_down(self):
# Use 'self.c', not just 'c'
I think you want to do something similar with the value number, but it that case I would send it as an argument to place_player_down:
def roll(self):
number=randint(2,12)
if b==0:
self.place_player_down(number)
return number # Keep in mind that this value is returned but not used anymore
def place_player_down(self, number):
# Use 'number'
Related
Introduction
I'm trying to create a GUI application with tkinter in python that has 2 frames that relate to each other. The general idea is to create a character selection menu (as in games). Everything was going well until it reached the part of changing the text of the buttons that are in different classes.
Goals
The first frame, represented by class A, will be the main page of the application and should display 10 buttons on the screen;
The second frame, represented by class B, will only be displayed to the user when any button in class A is clicked;
Class B should display a list of buttons. When any of the Class B buttons are clicked, the text of this button that was clicked must be passed to the text of the Class A button.
Details of the Project
This application should work as if it were a game character selection menu. Imagining this way, we commonly see this type of interaction between frames;
There is a screen that shows the number of possible players for each game (In the case of this application, the buttons of class A will represent this) and there is a screen that shows all the characters available to be chosen (Buttons of class B);
In the class A frame, there will be the player options: "Player 1, Player 2, Player 3 ...". So when clicking on one of these buttons (Player 1, Player 2, Player 3 ...) the window showing all the characters should be displayed (class B);
When selecting the desired character (by clicking on one of the class B buttons), the chosen character must be passed to the main screen and shown on the button selected in class A. As I am not using images yet, I want to represent the characters by the text of the buttons;
So if I click on the "Player 1" option on the main screen, and then select "Character 4" on the selection screen, the text for "Player 1" should be changed to "Character 4" on the main screen, and so on;
Generic Code
I made a generic representation of how I am building the program and detailing how I wanted it to work.
import tkinter as tk
# Creates the main window
class A(tk.Frame):
"""The class A frame is the main page of the application,
when running the program, it will be the first thing shown to the user."""
def __init__(self, master):
tk.Frame.__init__(self, master)
self.bt_identities_a = [] # this list will be used to save the identities of each button created in class A
# Creates multiple buttons
for i in range(10):
self.bt_a = tk.Button(self, text=f"Player A{i}", command=lambda x=i: self.open_window_of_class_b(x))
self.bt_a.grid()
self.bt_identities_a.append(self.bt_a) # adds the button identity to the list
def open_window_of_class_b(self, n):
"""This is the method responsible for executing class B
and for recognizing which button was clicked in class A
All actions to be performed by the buttons in class B
from now on must be associated with exactly that one
button that was clicked in class A.
"""
# Run class B
B()
# get the button id that was clicked
bt_id = self.bt_identities_a[n]
...
# Creates the secondary window
class B(tk.Toplevel):
"""The class B frame is a secondary page that will only be opened if one of the Class A buttons is clicked."""
def __init__(self):
tk.Toplevel.__init__(self)
self.bt_identities_b = [] # this list will be used to save the identities of each button created in class B
# Creates multiple buttons
for j in range(10):
self.bt_b = tk.Button(self, text=f"Character B{j}",
command=lambda x=j: self.changes_the_text_of_a_button_in_class_a(x))
self.bt_b.grid()
self.bt_identities_b.append(self.bt_b) # adds the button identity to the list
def changes_the_text_of_a_button_in_class_a(self, n):
"""This method should recognize which of the Class B buttons that was clicked,
take the text from this exact button and pass the text to the Class A button
that was clicked just before."""
# get the button id that was clicked
bt_id = self.bt_identities_b[n]
...
root = tk.Tk()
root.geometry("300x300")
app = A(root)
app.pack(fill="both", expand=True)
app.mainloop()
My real code
And here is the complete code I have made so far from my application in case it is needed.
import tkinter as tk
from itertools import product
# Creating main page
class MainApplication(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
# produce the set of coordinates of the main page buttons
self.row_amount = 2
self.column_amount = 5
self.main_positions = product(range(self.row_amount), range(self.column_amount))
self.main_buttons_identities = []
# Creating main page header
self.lb = tk.Label(self, width=111, height=4, bg="#2c3e50", text="Champions", fg="white", font=50,
justify=tk.CENTER)
self.lb.grid(row=0, column=0, columnspan=5, pady=(0, 50), sticky="snew")
# Creating Done button
self.button = tk.Button(self, width=30, height=3, bg="#2c3e50", relief=tk.RIDGE, text="Done",
fg="white", font=20, command=root.destroy)
self.button.grid(row=3, columnspan=5, pady=(0, 150))
# Creating multiple buttons
for i, item in enumerate(self.main_positions):
self.button_main = tk.Button(self, width=16, height=8, bg="#2c3e50", relief=tk.RIDGE, fg="white",
justify=tk.CENTER, text=f"Champion {i +1}",
command=lambda c=i: [ChampionWindow(), self.clicked_main(c)])
self.button_main.grid(row=item[0] + 1, column=item[1], pady=(0, 50))
self.main_buttons_identities.append(self.button_main)
def clicked_main(self, current_index):
current = self.main_buttons_identities[current_index]
print(current["text"])
# Creating champion select window
class ChampionWindow(tk.Toplevel):
def __init__(self, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
# produce the set of coordinates of the char selection page buttons
self.row_amount = 30
self.column_amount = 5
self.champion_position = product(range(self.row_amount), range(self.column_amount))
self.champions_buttons_identities = []
# scroll bar
self.ch_canvas = tk.Canvas(self, bg="blue", width=470, height=500)
self.ch_frame = tk.Frame(self.ch_canvas, bg="#273c75")
self.vscrollbar = tk.Scrollbar(self, orient="vertical", command=self.ch_canvas.yview)
self.ch_canvas.configure(yscrollcommand=self.vscrollbar.set)
self.ch_canvas.grid(sticky="snew")
self.vscrollbar.grid(row=0, column=3, sticky="sn")
self.ch_canvas.create_window((0, 0), window=self.ch_frame, anchor="nw")
self.ch_frame.bind("<Configure>", self.scroll)
# Creating multiple buttons
for i, itm in enumerate(self.champion_position):
self.button_champion = tk.Button(self.ch_frame, width=12, height=6, bg="#2c3e50",
relief=tk.RIDGE, fg="white", justify=tk.CENTER,
command=lambda c=i: [self.clicked_champion(c), self.destroy()],
text=f"Pick champ {i+1}")
self.button_champion.grid(row=itm[0], column=itm[1])
self.champions_buttons_identities.append(self.button_champion)
def scroll(self, ch_event):
self.ch_canvas.configure(scrollregion=self.ch_canvas.bbox("all"))
def clicked_champion(self, champ_index):
champ = self.champions_buttons_identities[champ_index]
print(champ["text"])
if __name__ == "__main__":
root = tk.Tk()
root.title("Champion")
root.geometry("1000x570+450+200")
root.resizable(False, False)
app = MainApplication(root)
app.configure(background="#34495e")
app.pack(fill="both", expand=True)
app.mainloop()
GUI images
To make it easier to understand what I'm trying to do, I'll link the images from the main window and the character selection window.
Main Window (Displays the players)
Character Selection Window (Displays available characters)
You can simply pass the instance of clicked button to ChampionWindow class:
class MainApplication(tk.Frame):
def __init__(self, master, *args, **kwargs):
...
# Creating multiple buttons
for i, item in enumerate(self.main_positions):
button_main = tk.Button(self, width=16, height=8, bg="#2c3e50", relief=tk.RIDGE, fg="white",
justify=tk.CENTER, text=f"Champion {i +1}",
command=lambda c=i: self.clicked_main(c))
button_main.grid(row=item[0] + 1, column=item[1], pady=(0, 50))
self.main_buttons_identities.append(button_main)
def clicked_main(self, current_index):
current = self.main_buttons_identities[current_index]
print(current["text"])
ChampionWindow(current) # pass clicked button to ChampionWindow
Then update the text of the passed button in ChampionWindow when one of its buttons is clicked:
# Creating champion select window
class ChampionWindow(tk.Toplevel):
def __init__(self, button, *args, **kwargs):
tk.Toplevel.__init__(self, *args, **kwargs)
self.button = button # save the button for later use
...
def clicked_champion(self, champ_index):
champ = self.champions_buttons_identities[champ_index]
print(champ["text"])
self.button["text"] = champ["text"] # update passed button
you can try something like this. You can apply changes to class variables externally once the class has been initialized.
Here is a simple example.
class A():
def __init__(self):
self.champ = None
def get_name(self):
B()
def test(self):
print(self.champ)
class B():
def __init__(self):
print("Assingin name")
f.champ = "Hero1"
return
if __name__ == "__main__":
f = A()
f.test()
f.get_name()
f.test()
In your scenario, you would change app.whatever as your main program is globally initialized as app. So simply creating a list to fill or independent variables or dicts can then be passed by assigning values to them from the secondary class.
I believe extending to fit your exact use case is quite possible and I can provide a much more robust solution at a later time.
MainTicTacToe.py
import tkinter as tk
import MenubarCommand as mbc
class Game(tk.Frame):
def __init__(self,parent):
tk.Frame.__init__(self,parent)
self.parnt=parent
# self.parnt.geometry('500x300')
self.parnt.title("Tic Tac Toe")
# self.pack()
menubar=tk.Menu(parent)
# 'settings' menu
settingsOption=tk.Menu(menubar, tearoff=0)
settingsOption.add_command(label='Player Settings', command=self.doit)
settingsOption.add_command(label='Board Settins', command=self.doit)
menubar.add_cascade(label='Setings', menu=settingsOption)
# without using this method, menubar isn't shown in Frame
self.parnt.config(menu=menubar)
def doit(self):
root=self.win()
set=mbc.playerSettings(root)
# print(set.p1name)
root.mainloop()
def win(self):
return tk.Tk()
def main():
root=tk.Tk()
Game(root)
root.mainloop()
main()
MenubarCommand.py
import tkinter as tk
class playerSettings(tk.Frame):
def __init__(self,parent=tk.Frame):
tk.Frame.__init__(self,parent)
self.parnt=parent
self.parnt.title("Player Setting")
self.p1name='Player 1'
self.p2name='Player 2'
self.p1symbol='x'
self.p2symbol='o'
# **********************************#
self.p1NameLabel = tk.Label(parent, text='Player 1: Name ')
self.p1NameLabel.grid(row=0, column=0)
self.p1NameEntry = tk.Entry(parent)
self.p1NameEntry.insert(0,self.p1name)
self.p1NameEntry.bind('<FocusIn>', lambda event:self.p1NameEntry.delete(0,'end'))
self.p1NameEntry.grid(row=0, column=1)
apply=tk.Button(parent, text="Apply Settings", fg='white', bg='gray', command=self.saveStat)
apply.grid(row=2, rowspan=1, columnspan=4)
def saveStat(self):
print('Settings Window Destroyed')
self.p1name=self.p1NameEntry.get()
print(self.p1name)
self.parnt.destroy()
I want to change the value of attribute of an instance in one file from the instance of another class in another file already created.
When I change default Player name in MenubarComman.py file, I want to access the changed name from MainTicTacToe.py class. How can I do this?
I'm new new in Python.
Thanks in Advance.
You problems stem from 2 instances of Tk(), and sloppy programming, i.e. sometimes you use parent, and sometimes self.parnt which is a bad habit to get into, so everything is changed to self.top so an error will pop up if any of those two remains.. You also have to have a way to signal when PlayerSetting() is finished. The way the program is structured, the only way that I know of is to check for "alive" using recursion. You could also have PlayerSettings call a function in Game() when finished, which would print the value. I have cleaned up your code and it works as I understand it should.. Note that the 2 classes are in the same file to make it easier to test and post here.
import tkinter as tk
##import MenubarCommand as mbc
class Game():
def __init__(self,parent):
self.parnt=parent
# self.parnt.geometry('500x300')
self.parnt.title("Tic Tac Toe")
# self.pack()
menubar=tk.Menu(parent)
# 'settings' menu
settingsOption=tk.Menu(menubar, tearoff=0, bg="yellow")
settingsOption.add_command(label='Player Settings', command=self.doit)
settingsOption.add_command(label='Board Settins', command=self.doit)
menubar.add_cascade(label='Setings', menu=settingsOption)
# without using this method, menubar isn't shown in Frame
self.parnt.config(menu=menubar)
def doit(self):
## do not open more than one Tk() instance
##root=self.win()
self.top=tk.Toplevel(self.parnt)
self.set_it=PlayerSettings(self.top)
self.get_variable()
##root.mainloop()
def get_variable(self):
""" check continuously if PlayerSettings has exited and
if so, get the Entry's value
"""
if self.set_it:
self.parnt.after(1000, self.get_variable)
else:
print("from Game", self.set_it.p1name)
def win(self):
return tk.Tk()
class PlayerSettings():
def __init__(self, parent):
self.top=parent
self.p1name='Player 1'
self.p2name='Player 2'
self.p1symbol='x'
self.p2symbol='o'
# **********************************#
self.p1NameLabel = tk.Label(self.top, text='Player 1: Name ', bg="lightblue")
self.p1NameLabel.grid(row=0, column=0)
self.p1NameEntry = tk.Entry(self.top)
self.p1NameEntry.focus_set()
self.p1NameEntry.insert(0,self.p1name)
##self.p1NameEntry.bind('<FocusIn>', lambda event:self.p1NameEntry.delete(0,'end'))
self.p1NameEntry.grid(row=0, column=1, sticky="nsew")
apply=tk.Button(self.top, text="Apply Settings", fg='white', bg='gray', command=self.saveStat)
apply.grid(row=2, rowspan=1, columnspan=4)
def saveStat(self):
self.p1name=self.p1NameEntry.get()
print(self.p1name)
print('Settings Window Destroyed')
self.top.destroy()
root=tk.Tk()
Game(root)
root.mainloop()
So I know what the problem is, I just don't know how to fix it:
self.health gets stored in the variable once and doesn't re-read after that. I've tried using: #property. But I only just learnt about #property yesterday, so either 1: I'm not using it properly, or 2: it can't be used in this situation.
import tkinter as tk
class button_health:
def __init__(self):
self.health = 5
def hit(self, event):
self.health -= 1
bob = button_health()
window = tk.Tk()
button = tk.Button(window, text = bob.health #I want this to update)
button.bind("<Button-1>", bob.hit)
button.pack()
window.mainloop()
What I'm aiming for is for the code to produce a simple tkinter button on screen which starts off saying "5", then when you click it, says "4", then click "3" etc.
Use a Tkinter IntVar variable to track changes to the health value. Hook that variable up to the button label using the textvariable attribute.
import tkinter as tk
class button_health:
def __init__(self, health=5):
self.health = tk.IntVar()
self.health.set(health)
def hit(self, event=None):
if self.health.get() >= 1: # assuming that you can't have negative health
self.health.set(self.health.get() - 1)
window = tk.Tk()
bob = button_health(8)
button = tk.Button(window, textvariable=bob.health, command=bob.hit)
#button = tk.Button(window, textvariable=bob.health)
#button.bind('<Button-1>', bob.hit)
button.pack()
window.mainloop()
Another way is to create your own button class as a subclass of Button and hook up an IntVar as a member of the class. This way you can easily create multiple independent buttons with different health values:
import tkinter as tk
class HealthButton(tk.Button):
def __init__(self, window, health=5, *args, **kwargs):
self.health = tk.IntVar()
self.health.set(health)
super(HealthButton, self).__init__(window, *args, textvariable=self.health, command=self.hit, **kwargs)
def hit(self):
if self.health.get() >= 1:
self.health.set(self.health.get() - 1)
window = tk.Tk()
buttons = [HealthButton(window, i) for i in range(10,15)]
for b in buttons:
b.pack()
window.mainloop()
Use button['text'] or button.config(text={text})
class button_health:
def __init__(self):
self.health = 5
def hit(self, event):
self.health -= 1
button['text'] = str(self.health)
or
class button_health:
def __init__(self):
self.health = 5
def hit(self, event):
self.health -= 1
button.config(text= str(self.health))
There is an argument called command in tk.Button. Binding the button-1 to a function isn't the best way to check if the user clicked the button. And even if you used bind, it should be binded to a function, not a class.
To change a button's text, you can set button['text'] to something.
import tkinter as tk
def button_health():
global health
health -= 1
button['text'] = str(health)
health = 5
window = tk.Tk()
button = tk.Button(window, text = health , command = button_health)
button.pack()
window.mainloop()
You can also avoid using a global statement by doing this:
import tkinter as tk
def button_health(but):
but.health -= 1
but['text'] = str(but.health)
window = tk.Tk()
button = tk.Button(window)
button.health = 5
button['text'] = str(button.health)
button['command'] = lambda: button_health(button)
button.pack()
window.mainloop()
another advantage for doing it this way is that it can keep the health of the button independent, so if you have multiple buttons, this will keep the counters for all the buttons different.
I apologize in advance if this is a stupid simple question, but i am really bad att python classes and can't seem to get it to work!
Here is my code:
from tkinter import *
a = Tk()
class toolsGUI():
def __init__(self, rootWin):
pass
def frame(self):
frame = Frame(rootWin)
frame.configure(bg = 'red')
frame.grid()
def button(self, binding, text):
btn = Button(rootWin, text=text)
btn.configure(bg = 'orange', fg = 'black')
btn.bind('<'+binding+'>')
btn.grid(row=1, sticky = N+S+E)
I simply want the button() or frame() to understand that rootWin is the same as in __init__, in this case rootWin should be variable a, thus placing the button in the Tk() window. After looking around, I understand that this is not the way to do it. Do anyone have another suggestion that might work?
You're pretty close. You are passing a to the toolsGUI initializer which is the right first step. You simply need to save this as an instance variable, then use the variable whenever you need to reference the root window:
def __init__(self, rootWin):
...
self.rootWin = rootWin
...
def frame(self):
frame = Frame(self.rootWin)
...
An alternative is to have toolsGUI inherit from Frame, in which case you can put all of the widgets in the frame instead of the root window. You then need the extra step of putting this frame inside the root window.
class toolsGUI(Frame):
def __init__(self, rootWin):
Frame.__init__(self, rootWin)
def frame(self):
frame = Frame(self)
...
a = Tk()
t = toolsGUI(a)
t.pack(fill="both", expand=True)
a.mainloop()
As a final bit of advice: don't user variables that are the same name as methods if you can avoid it. "frame" is a poor choice of function names. Instead, call it "create_frame" or something, otherwise it could be confused with class Frame and the local variable frame
I want to create a popup message box which prompts user to enter an input. I have this method inside a class. I am basing my code on this guide by java2s.
class MyDialog:
def __init__(self, parent):
top = self.top = Toplevel(parent)
Label(top, text="Value").pack()
self.e = Entry(top)
self.e.pack(padx=5)
b = Button(top, text="OK", command=self.ok)
b.pack(pady=5)
def ok(self):
print "value is", self.e.get()
self.top.destroy()
root = Tk()
d = MyDialog(root)
root.wait_window(d.top)
But in this, top = self.top = Toplevel(parent) doesn't work for me.
I have a mockup of what I am trying to accomplish.
My program structure looks something like this:
class MainUI:
def__int__(self):
...
self.initUI()
def initUI(self):
.......
Popup = Button(self, text="Enter Value", command=self.showPopup)
def showPopup(self):
#create the popup with an Entry here
How can I create a message box in Python which accepts user input?
I'm a little confused about your two different blocks of code. Just addressing the first block of code, nothing happens because you never enter the mainloop. To do that, you need to call root.mainloop(). The typical way of doing this is to add a button to root widget and bind a callback function to the Button (which includes d=MyDialog() and root.wait_window(d.top))
Here's some basic code which I hope does what you want ...
from Tkinter import *
import sys
class popupWindow(object):
def __init__(self,master):
top=self.top=Toplevel(master)
self.l=Label(top,text="Hello World")
self.l.pack()
self.e=Entry(top)
self.e.pack()
self.b=Button(top,text='Ok',command=self.cleanup)
self.b.pack()
def cleanup(self):
self.value=self.e.get()
self.top.destroy()
class mainWindow(object):
def __init__(self,master):
self.master=master
self.b=Button(master,text="click me!",command=self.popup)
self.b.pack()
self.b2=Button(master,text="print value",command=lambda: sys.stdout.write(self.entryValue()+'\n'))
self.b2.pack()
def popup(self):
self.w=popupWindow(self.master)
self.b["state"] = "disabled"
self.master.wait_window(self.w.top)
self.b["state"] = "normal"
def entryValue(self):
return self.w.value
if __name__ == "__main__":
root=Tk()
m=mainWindow(root)
root.mainloop()
I get the value from the popupWindow and use it in the main program (take a look at the lambda function associated with b2).
Main window:
"Click me" window:
Main window while "click me" is open:
import tkinter as tk
from tkinter import simpledialog
ROOT = tk.Tk()
ROOT.withdraw()
# the input dialog
USER_INP = simpledialog.askstring(title="Test",
prompt="What's your Name?:")
# check it out
print("Hello", USER_INP)
Enjoy ...
I did it in Tkinter without any classes. I created a function that starts a new window.
popup.Tk()
popup.mainloop()
In that window there is an Entry field from where I get the text with a variable which value is: entry.get()
Then you can use that variable for whatever you need and it will take the text from that Entry field.
I just tried this:
def get_me():
s = simpledialog.askstring("input string", "please input your added text")
Source: https://www.youtube.com/watch?v=43vzP1FyAF8