Tkinter : Button in frame not visible - python

I'm trying to implement a TicTacToe program. I am an absolute beginner in python. After viewing many tutorials and reading a few books, I have understood the basics of Python. I'm trying to get the buttons to display in a frame, but all I get is a blank window.
link for image of the resultant window
This is the code I have so far:
from Tkinter import *
class Buttons(object):
def __init__(self,master):
frame = Frame(master)
frame.pack()
self.button1= Button(frame,text="1",height=4,width=8,command=self.move)
self.button1.pack(side=LEFT)
self.button2= Button(frame,text="2",height=4,width=8,command=self.move)
self.button2.pack(side=LEFT)
self.button3= Button(frame,text="3",height=4,width=8,command=self.move)
self.button3.pack(side=LEFT)
root = Tk()
root=mainloop()

You defined your Buttons class but you didn't create an instance of that class, so no buttons were actually constructed. Also, you had a typo / syntax error:
root=mainloop()
should be
root.mainloop()
Also, you didn't define the move callback method.
Here's a repaired version of your code:
from Tkinter import *
class Buttons(object):
def __init__(self,master):
frame = Frame(master)
frame.pack()
self.button1 = Button(frame, text="1", height=4, width=8, command=self.move)
self.button1.pack(side=LEFT)
self.button2 = Button(frame, text="2", height=4, width=8, command=self.move)
self.button2.pack(side=LEFT)
self.button3 = Button(frame, text="3", height=4, width=8, command=self.move)
self.button3.pack(side=LEFT)
def move(self):
print "click!"
root = Tk()
Buttons(root)
root.mainloop()
However, this still has a problem: The move method has no way of knowing which button called it. Here's one way to fix that. I've also changed
from Tkinter import *
to
import tkinter as tk
It's not a good idea to use "star" imports. They make code harder to read and they pollute your namespace with all the names defined in the imported module (that's 175 names in the case of Tkinter), which can lead to name collisions.
import Tkinter as tk
class Buttons(object):
def __init__(self,master):
frame = tk.Frame(master)
frame.pack()
self.buttons = []
for i in range(1, 4):
button = tk.Button(
frame, text=i, height=4, width=8,
command=lambda n=i:self.move(n)
)
button.pack(side=tk.LEFT)
self.buttons.append(button)
def move(self, n):
print "click", n
root = tk.Tk()
Buttons(root)
root.mainloop()

Okay the problem was I needed to add a variable at the end of the code. Something like b=Buttons(root). It's working now.

Related

Is there a way to display a class in python tkinter as a Frame, so other things can be added?

I want to have a tkinter window that displays both a cronometer and a sudoku. The cronometer is a class, so how can I add it to the window that displays the sudoku?
I already managed to get two separate windows, but I couldn't make one with both things.
def GUI4x4(dif): #This function gets just called from other place
# What I want is to be able to display this class
# Cronometer in the main window that's created below
class Cronometer():
...
def __init__(self):
self.crono=Tk()
self.tiempo = StringVar()
self.tiempo.set("00:00:00")
self.label = Label(self.crono,textvariable=self.tiempo, bg="white")
self.label.grid(column=0,row=0)
self.label.configure(font=("Times 13 bold"))
self.btnI = Button(self.crono, bg="white", text="Start",command=self.iniciarT,font=("Times 11"))
self.btnI.grid(pady=3,column=0,row=1)
self.btnP = Button(self.crono, bg="white", text="Pause",command=self.pausarT,font=("Times 11"))
self.btnP.grid(pady=3,column=0,row=2)
self.btnR = Button(self.crono, bg="white", text="Restart",command=self.reiniciarT,font=("Times 11"))
self.btnR.grid(pady=3,column=0,row=3)
GUI = Tk() # This creates the main window, and places
# 34 buttons in it
...
# Defining the Buttons
btn00 = Button(GUI, text=Tablero[0][0], width=5, height=3, activebackground="lime")
btn01 = Button(GUI, text=Tablero[0][1], width=5, height=3, activebackground="lime")
btn02 = Button(GUI, text=Tablero[0][2], width=5, height=3, activebackground="lime")
...
btn33 = Button(GUI, text=Tablero[3][3], width=5, height=3, activebackground="lime")
#Placing the 34 buttons
btn00.grid(row=0, column=0)
btn01.grid(row=0, column=1)
btn02.grid(row=0, column=2)
...
btn33.grid(row=3, column=3)
The standard way to deal with this with tkinter is that each "widget" in the application is its own class based on the tkinter Frame widget, one class for the chrono, another for the sudoko game. There might even be a main app class.
Advantage of this method is that each widget frame can be created independently and then joined together later. These classes might also be split up in to separate code files.
A fairly simple example below
import tkinter as tk
class Chromometer(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.tiempo = tk.StringVar()
self.tiempo.set("00:00:00")
self.label = tk.Label(self,textvariable=self.tiempo, bg="white")
self.label.grid(column=0,row=0)
class Sudoko(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.label = tk.Label(self,text="Sudoko", bg="white")
self.label.grid(column=0,row=0)
class MainApp(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.chrono = Chromometer(master=self)
self.chrono.grid()
self.sudoko = Sudoko(master=self)
self.sudoko.grid()
if __name__ == '__main__':
root = tk.Tk()
app = MainApp(master=root)
app.grid()
root.mainloop()
Each class will have their own methods to perform the functionality needed by each. The chromo/chrono class will have a method to update the timer.

IntVar().trace() not working

I'm just getting started coding in Python/Tkinter for a small Pymol plugin. Here I'm trying to have a toggle button and report its status when it is clicked. The button goes up and down, but toggleAVA never gets called. Any ideas why?
from Tkinter import *
import tkMessageBox
class AVAGnome:
def __init__(self, master):
# create frames
self.F1 = Frame(rootGnome, padx=5, pady=5, bg='red')
# checkbuttons
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(self.F1, text='AVA', indicatoron=0, variable=self.AVAselected)
# start layout procedure
self.layout()
def layout(self):
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
#entry and buttons
self.AVAbutton.pack(side=LEFT)
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
def __init__(self):
open_GnomeUI()
def open_GnomeUI():
# initialize window
global rootGnome
rootGnome = Tk()
rootGnome.title('AVAGnome')
global gnomeUI
gnomeUI = AVAGnome(rootGnome)
I tested your code with Pymol.
Problem is because you use Tk() to create your window. You have to use Toplevel() and then it will work correctly with trace() or with command=.
Pymol is created with tkinter which can have only one window created with Tk() - it is main window in program. Every other window has to be created with Toplevel().
I have attached a working version of your code below. You can refer to it to learn where you went wrong. Generally, you have to mind how you structure your code if you are using a class format.This will help you visualize your code and debug better. You can read this discussion to help you.
from Tkinter import *
import tkMessageBox
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
if __name__ == '__main__':
rootGnome = Tk()
rootGnome.title('AVAGnome')
gnomeUI = AVAGnome(rootGnome)
gnomeUI.pack(fill="both", expand=True)
gnomeUI.mainloop()
Update: The above code structure is for standalone tkinter programme. I am attempting to convert this working code to follow Pymol plugin example. Revised code is posted below and is susceptible to further revision.
# https://pymolwiki.org/index.php/Plugins_Tutorial
# I adapted from the example in the above link and converted my previous code to
#
from Tkinter import *
import tkMessageBox
def __init__(self): # The example had a self term here.
self.open_GnomeUI()
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
# Note, I added a "self" term throughout function.
# Try w/ & w/o "self" to see which works.
def open_GnomeUI(self):
self.rootGnome = Tk()
self.rootGnome.title('AVAGnome')
self.gnomeUI = AVAGnome(self.rootGnome)
self.gnomeUI.pack(fill="both", expand=True)
self.gnomeUI.mainloop()

Python Tkinter, Number of Label/Entry based on IntVar

I was hoping someone could help me with something. I would like to make an Tkinter app that asks for a number then uses that number to draw the correct number of Labels and Entrys.
Here's basic brainstorm of what I'm trying to do (I know this is wrong)
from Tkinter import *
root = Tk()
numlines = IntVar()
Label(root, text='Number Of Lines').grid(row=0, column=0) #this is to always stay
Entry(root, textvariable=numlines).grid(row=0, column=1) #this is to stay always stay
Button(root, text='Apply Number', command=apply).grid(row=1, column=1)
def apply():
# this part to draw as a block based in numline(if munlines 5 then draw these two widget 5 times on 5 rows)
Label(root, text='Line 1').grid(row=2, column=0)# this part to draw as a block based in numline(if munlines 5 then draw these two widget 5 times)
Entry(root, textvariable=numlines).grid(row=2, column=1)
root.mainloop()
Realistically all Tkinter applications should be put inside of a class. In addition it is also general a bad idea for use import * from any package as you could have override issues with unknown values that you imported. As such, the following example is inside of a class and should give you an idea of how this would look. I believe this is what you're looking for:
import Tkinter as Tk
class App(Tk.Frame):
def __init__(self, master, *args, **kwargs):
Tk.Frame.__init__(self, *args, **kwargs)
self.existing_lines = 2
self.entry_lines = Tk.IntVar()
Tk.Label(self, text="Number Of Lines").grid(row=0, column=0)
Tk.Entry(self, textvariable=self.entry_lines).grid(row=0, column=1)
Tk.Button(self, text="Apply Number", command=self.add_rows).grid(row=1, column=1)
def add_rows(self):
for row in xrange(self.existing_lines, self.existing_lines+self.entry_lines.get()):
Tk.Label(self, text="Line %i" % (row-1)).grid(row=row, column=0)
Tk.Entry(self, textvariable=self.entry_lines).grid(row=row, column=1)
self.existing_lines+= 1
if __name__ == "__main__":
root = Tk.Tk()
App(root).pack()
root.mainloop()

Why are my tkinter window objects (OOP tkinter) not BOTH showing?

I am trying to learn about tkinter from an OOP point of view so that I can create multiple windows.
I have created two files (main.py and Humanclass.py).
Why are both windows not being created? I thought that I had created a Class and in the main program created 2 instance of that class with different data?
Main.py:
import humanclass
from tkinter import *
window = Tk()
human1 = humanclass.Human(window, "Jim", "78", "British")
human2 = humanclass.Human(window, "Bob", "18", "Welsh")
window.mainloop()
humanclass.py:
from tkinter import *
class Human():
def __init__(self, window, name, age, nation):
self.window=window
self.window.geometry("500x200+100+200")
self.window.title(name)
self.label1 = Label(self.window, text=age).grid(row=0, column=0, sticky=W)
self.label2 = Label(self.window, text=nation).grid(row=1, column=0, sticky=W)
self.button = Button(self.window, text="Close", width=5, command=self.clicked).grid(row=3, column=0, sticky=W)
def clicked(self):
self.window.destroy()
Any help to show me the errors in my limited understanding would be gratefully received.
It's because window is only one active window, i.e. the root window. If you want to create multiple windows you will need to spawn them off of that root window. Simply assigning things to that window would overwrite whatever was previously there. That's why only your bottom instance is showing. While technically you could get away with implementing threading and running two root windows with two mainloops, it is highly advised not to do that.
What you should do is create Toplevel instances off of the root window. Think of these as like popup windows that are independent. You can make them independent of the root window or have them anchored to it. That way if you close the root window all the Toplevels off of it will close. I suggest you look more into Toplevels and you'll find what you're looking for. You probably want something like this:
Main.py
import humanclass
from Tkinter import *
window = Tk()
# Hides the root window since you will no longer see it
window.withdraw()
human1 = humanclass.Human(window, "Jim", "78", "British")
human2 = humanclass.Human(window, "Bob", "18", "Welsh")
window.mainloop()
humanclass.py
from Tkinter import *
class Human():
def __init__(self, window, name, age, nation):
# Creates a toplevel instance instead of using the root window
self.window=Toplevel(window)
self.window.geometry("500x200+100+200")
self.window.title(name)
self.label1 = Label(self.window, text=age).grid(row=0, column=0, sticky=W)
self.label2 = Label(self.window, text=nation).grid(row=1, column=0, sticky=W)
self.button = Button(self.window, text="Close", width=5, command=self.clicked).grid(row=3, column=0, sticky=W)
def clicked(self):
self.window.destroy()

Python - Auto add widgets

So I am currently trying to create a button on a GUI that will let the user generate a new entry field.
I have no idea how to do this. I'm guessing that it will require a lambda function, but apart from that, I have no idea.
Here's the basic code I have so far:
from tkinter import *
class prac:
def autoAddWidget(self,frame,x,y):
self.entryField = Entry(frame,text="Entry Field")
self.entryField.grid(row=x, column=y)
#lambda function?
def __init__(self, master):
frame = Frame(master, width=60, height=50)
frame.pack()
x=1
self.addWidgetButton = Button(frame, text="Add new widget", command=self.autoAddWidget(frame, x,0))
self.addWidgetButton.grid(row=0, column=0)
x+=1
root = Tk()
app = prac(root)
root.mainloop()
Would appreciate the help.
Thanks
You're passing to the command argument result from the method self.autoAddWidget(frame, x,0) not method itself. You have to pass there a reference to a callable object, a function that will be called when the event occurs. Please check a documentation next time before you ask the question.
Ok, I fixed the code, now it works:
from tkinter import *
class Prac:
def autoAddWidget(self):
self.entryField = Entry(self.frame,text="Entry Field")
self.entryField.grid(row=self.x, column=0)
self.x+=1
def __init__(self, master):
self.frame = Frame(master, width=60, height=50)
self.frame.pack()
self.x=1
self.addWidgetButton = Button(self.frame, text="Add new widget", command=self.autoAddWidget)
self.addWidgetButton.grid(row=0, column=0)
root = Tk()
app = Prac(root)
root.mainloop()

Categories