Okay, so I've got a basic window with an EDIT and VIEW button. As my code stands, EDIT and VIEW both return a message "this button is useless". I created these under the class "main_window". I created another class "edit_window" that I'm hoping to call when the EDIT button is clicked. Essentially, clicking the edit button should change display the new window with buttons ADD and REMOVE. Here's my code so far...what would be the next logical step?
from Tkinter import *
#import the Tkinter module and it's methods
#create a class for our program
class main_window:
def __init__(self, master):
frame = Frame(master)
frame.pack(padx=15,pady=100)
self.edit = Button(frame, text="EDIT", command=self.edit)
self.edit.pack(side=LEFT, padx=10, pady=10)
self.view = Button(frame, text="VIEW", command=self.view)
self.view.pack(side=RIGHT, padx=10, pady=10)
def edit(self):
print "this button is useless"
def view(self):
print "this button is useless"
class edit_window:
def __init__(self, master):
frame = Frame(master)
frame.pack(padx=15, pady=100)
self.add = Button(frame, text="ADD", command=self.add)
self.add.pack()
self.remove = Button(frame, text="REMOVE", command=self.remove)
self.remove.pack()
def add(self):
print "this button is useless"
def remove(self):
print "this button is useless"
top = Tk()
top.geometry("500x500")
top.title('The Movie Machine')
#Code that defines the widgets
main = main_window(top)
#Then enter the main loop
top.mainloop()
Just create a Toplevel instead of using a Frame:
class MainWindow:
#...
def edit(self):
EditWindow()
class EditWindow(Toplevel):
def __init__(self):
Toplevel.__init__(self)
self.add = Button(self, text="ADD", command=self.add)
self.remove = Button(self, text="REMOVE", command=self.remove)
self.add.pack()
self.remove.pack()
I've changed the class names according to the CapWords convention (see PEP 8). This is not mandatory, but I recommend you to use it in all your Python projects to keep an uniform style.
Related
I'm currently using tkinter to create a GUI for my program. If I open the golf quiz window and open the help window, then close the golf quiz window and re-open it, I am able to click the help window button and open another instance of the help button. How do I set the Help button to be disabled while the Help window is open?
from tkinter import *
from functools import partial
class Welcome_Screen:
def __init__(self, parent):
self.welcome_screen_frame = Frame(width=200, height=200, pady=10)
self.welcome_screen_frame.grid()
self.quiz_welcome_screen_label = Label(self.welcome_screen_frame, text = "quiz game", font="Arial 20 bold", padx=10)
self.quiz_welcome_screen_label.grid(row=0)
self.welcome_screen_buttons_frame = Frame(self.welcome_screen_frame)
self.welcome_screen_buttons_frame.grid(row=2)
self.golf_quiz_welcome_screen_button = Button(self.welcome_screen_buttons_frame, text="Golf Quiz", font="Arial 10 bold", command=self.golf_quiz_game, padx=10, pady=10)
self.golf_quiz_welcome_screen_button.grid(row=2, column=0, padx=5)
def golf_quiz_game(self):
get_golf_quiz_game = golf_quiz_game(self)
class golf_quiz_game:
def __init__(self, partner):
partner.golf_quiz_welcome_screen_button.config(DISABLED)
self.golf_quiz_box = Toplevel()
self.golf_quiz_box.protocol('WM_DELETE_WINDOW', partial(self.close_golf_quiz_game, partner))
self.golf_quiz_frame = Frame(self.golf_quiz_box)
self.golf_quiz_frame.grid()
self.golf_quiz_heading = Label(self.golf_quiz_frame, text="Golf Quiz game",
font="arial 18 bold", padx=10, pady=10)
self.golf_quiz_heading.grid(row=0)
self.golf_quiz_history_help_dismiss_buttons_frame = Frame(self.golf_quiz_frame)
self.golf_quiz_history_help_dismiss_buttons_frame.grid(row=6, pady=10)
self.help_button = Button(self.golf_quiz_history_help_dismiss_buttons_frame, text="Help", font="Arial 10 bold",command=self.Help, padx=10, pady=10)
self.help_button.grid(row=6, column=1, padx=5)
def close_golf_quiz_game(self, partner):
partner.golf_quiz_welcome_screen_button.config(state=NORMAL)
self.golf_quiz_box.destroy()
def Help(self):
get_help = Help(self)
class Help:
def __init__(self, partner):
partner.help_button.config(state=DISABLED)
self.help_box = Toplevel()
self.help_box.protocol('WM_DELETE_WINDOW', partial(self.close_Help, partner))
self.help_frame = Frame(self.help_box)
self.help_frame.grid()
self.help_heading = Label(self.help_frame, text="Help", font="arial 18 bold")
self.help_heading.grid(row=0)
self.help_text = Label(self.help_frame, text="Test",
width=60, wrap=400)
self.help_text.grid(row=1)
self.help_button = Button(self.help_frame, text="Dismiss", width=10, font="Arial 10 bold", command=partial(self.close_Help, partner), padx=10, pady=10)
self.help_button.grid(row=2, pady=10)
def close_Help(self, partner):
if partner.help_button.winfo_exists():
partner.help_button.config(state=NORMAL)
self.help_box.destroy()
# main routine
if __name__ == "__main__":
root = Tk()
root.title("quiz game")
something = Welcome_Screen(root)
root.mainloop()
Here is how you can do that (this is also the size (linewise (approx.)) of the minimal reproducible example which you should have provided):
from tkinter import Tk, Toplevel, Button
def close_top(top):
btn.config(state='normal')
top.destroy()
def open_help():
btn.config(state='disabled')
top = Toplevel(root)
top.protocol('WM_DELETE_WINDOW', lambda: close_top(top))
top.focus_force()
# put the rest of help stuff here
root = Tk()
btn = Button(root, text='Help', command=open_help)
btn.pack()
root.mainloop()
Class based approach:
from tkinter import Tk, Toplevel, Button
# this would be the window from where you open the help window
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.btn = Button(self, text='Help',
command=lambda: self.open_help(self.btn))
self.btn.pack()
def open_help(self, btn):
HelpWindow(self, btn)
# this would be the help window
class HelpWindow(Toplevel):
def __init__(self, master, button):
Toplevel.__init__(self, master)
self.button = button
self.button.config(state='disabled')
self.focus_force()
self.protocol('WM_DELETE_WINDOW', self.close)
def close(self):
self.button.config(state='normal')
self.destroy()
MainWindow().mainloop()
Few things:
First of you can simply inherit from container and window classes that way you don't have to separately create them in the class and you can easily reference them in the class using just self
.focus_force() does what it says, it forces focus on the widget
Important (suggestions)
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.
You need to make use of class variables inside golf_quiz_game in order to have only one instance of Help window:
class golf_quiz_game:
# class variables
_help_button = None # reference to instance "Help" button
_help_win = None # reference to instance "Help" window
def __init__(self, partner):
...
# set button state based on whether "Help" window is open or not
self.help_button.config(state="normal" if self.__class__._help_win is None else "disabled")
# update class reference of "Help" button
self.__class__._help_button = self.help_button
def close_golf_quiz_game(self, partner):
partner.golf_quiz_welcome_screen_button.config(state=NORMAL)
self.golf_quiz_box.destroy()
# update class reference of "Help" button
self.__class__._help_button = None
def Help(self):
if self.__class__._help_win is None:
# no "Help" window is open, create one
self.__class__._help_win = Help(self)
def help_closed(self):
if self.__class__._help_button:
# enable the "Help" button
self.__class__._help_button.config(state="normal")
# update "Help" window status
self.__class__._help_win = None
class Help:
...
def close_Help(self, partner):
self.help_box.destroy()
# notify partner that "Help" window is closed
partner.help_closed()
For homework, I have to create an application that creates a text field everytime a user clicks a button, and then get values from the fields when "submit" button is pressed.
The trace method shows up repeatedly, but I do not know how to use it. I know it requires a callback function, but what should that callback function be?
from tkinter import *
from tkinter import ttk
import sqlite3
import getpass
import wipComingIn
class Application(object):
def __init__(self,master):
self.master=master
self.ScanWIPIn = Button(master, text="Scan WIP In", width=25,
font='Calibri 12
bold',background='snow',command=self.scanWIPIn).grid(row=0, column=0,
padx=10)
def scanWIPIn(self):
incomingInventory=wipComingIn.scanIn()
def main():
root = Tk()
app=Application(root)
root.title("Main Menu")
root.configure(background="light cyan")
root.resizable(0, 0)
root.geometry('230x230+300+80')
root.mainloop()
if __name__=='__main__':
main()
class scanIn(Toplevel):
def __init__(self):
Toplevel.__init__(self)
self.geometry('300x100+350+100')
self.title('Scan In')
self.resizable(0,0)
self.num_rows=1
self.LocationLb = Label(self,text='Scan Location:',font='Arial
12').grid(row=1,column=1)
self.LocationBCText = Entry(self).grid(row=1,column=2)
self.AddLotBtn= Button(self,text="Scan
Lot",command=self.addField).grid(row=2,column=1)
self.CompleteTransaction =
Button(self,text="Complete",command=self.AddEntry).grid(row=2,column=4)
global listOfLots
listOfLots=[]
listOfLocation=[]
global rowNum
rowNum=2
def addField(self):
height =Toplevel.winfo_height(self)
height=height+25
global rowNum
rowNum=rowNum+1
listOfLots.append(StringVar())
newLot = Entry(self, textvariable=listOfLots[rowNum - 2])
newLot.grid(row=rowNum,column=2, pady=1)
listOfLots.append(StringVar())
geometryText='300'+str(height)+'350+100'
print(geometryText)
self.geometry('300x'+str(height)+'+350+100')
newLot.focus_set()
You could try just making a class that does it, for example:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.geometry('200x200')
class EntryListWidget(ttk.Frame):
"""Widget that creates a column of entry boxes."""
def __init__(self, master):
super().__init__(master)
self.entries = []
def add_entry(self):
"""Creates a new entry box and keeps reference to respective variable."""
entry_var = tk.StringVar()
self.entries.append(entry_var)
ttk.Entry(self, textvariable=entry_var).pack()
def get_entries(self):
"""Gets each entrybox text and returns as list."""
return [entry.get() for entry in self.entries]
entry_widget = EntryListWidget(root)
entry_widget.pack()
# Buttons to control adding new entry and getting their values
ttk.Button(root, text='Add Entry', command=entry_widget.add_entry).pack()
ttk.Button(root, text='Get Entries', command=entry_widget.get_entries).pack()
root.mainloop()
Just using the variable classes and not trace; I actually wouldn't use trace in this situation because I believe trace uses the callback every time the variable changes and here you have a one time "submit" button that collects all the values. You could extend this class idea to get what you're looking to do I bet.
I'm building a desktop application that lets you insert some data into a form and then the data is displayed in a series (3) of Treeview widgets.
This is the form that I'm using to enter new data:
It's in a Toplevel widget. When the Add button is pressed the new data is stored in a file and it also should insert the new data in the corresponding Treeview Widget.
This is the root window:
It's comprised of 3 Treeview widgets. The purpose of the application is to give the user the opportunity to sort candidates into the right Treeview widget.
The issue that I'm facing is that when the Add button is pressed the new data is not shown in the Treeview widget and no errors are given. I think it may be an issue of class instantiation. This is an excerpt from my app, please see below a Minimal, Complete and Verifiable example
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
...
# frame and menu classes are instantiated here
self.FrameList = {ViableCandidates: ViableCandidates(self),
NotViableCandidates: NotViableCandidates(self),
InProgressCandidates: InProgressCandidates(self)}
...
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
...
# menu code is here
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
...
# code for the small Toplevel window
...
# this is the code that I use to add the new item to Treeview when the Add button is pressed
if CandidateInfo['status'] == 'Viable':
app.InstanceLinker(ViableCandidates).AddtoList()
elif CandidateInfo['status'] == 'Not Viable':
app.InstanceLinker(NotViableCandidates).AddtoList()
else:
app.InstanceLinker(InProgressCandidates).AddtoList()
# ViableCandidates, NotViableCandidates, InProgressCandidates are created with the same pattern
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
global Counter
tk.Frame.__init__(self, parent)
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
title = tk.Label(self, text="Candidates In Progress", font="Verdana 10 bold")
title.grid(row=0, column=0, sticky='nesw')
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns=('Name', 'Date'), selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('Name', width=150, minwidth=10, stretch=tk.YES)
self.tree.column('Date', width=80, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('Name', text='Name', anchor=tk.W)
self.tree.heading('Date', text='Date', anchor=tk.W)
if Counter < 4:
Counter += 1
self.PopulateList()
def PopulateList(self):
selection = Database().SelectFromDB('name, date', "status = 'In progress'")
for i in range(len(selection)):
name = list(selection[i])[0]
date = adjusttotimezone(list(selection[i])[1])
self.tree.insert("", i, name, text=i + 1)
self.tree.set(name, 'Name', name)
self.tree.set(name, 'Date', date)
CandidateCounter['InProgressCandidates'] = i
def AddtoList(self):
CandidateCounter['InProgressCandidates'] += 1
print('I was here')
self.tree.insert("", CandidateCounter['InProgressCandidates'], CandidateInfo['name'],
text=CandidateCounter['InProgressCandidates'])
self.tree.set(CandidateInfo['name'], 'Name', CandidateInfo['name'])
selection = Database().SelectFromDB('date', "name = '" + CandidateInfo['name'] + "'")
date = adjusttotimezone(list(selection[0])[0])
self.tree.set(CandidateInfo['name'], 'Date', date)
app = MainApp()
app.mainloop()
When the "Add" button is pressed there are no errors and "I was here" is printed so the AddtoList method is instantiated, but there are no new items added to Treeview. I did check if the variables that I'm using to create the new Treeview item hold the correct data and they do.
EDIT: This is a Minimal, Complete and Verifiable example:
import tkinter as tk
from tkinter import ttk
Bigbadtext = ''
Counter = 0
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
self.MainWindow = tk.Tk.__init__(self, *args, **kwargs)
menu = GUIMenu(self)
self.config(menu=menu)
frame = InProgressCandidates(self)
frame.grid(row=0, column=1, sticky='nesw')
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
tk.Menu.__init__(self, parent)
addcandidates = tk.Menu(self, tearoff=0)
self.add_cascade(label='Add Candidates', menu=addcandidates)
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
saysomething = tk.Entry(self)
saysomething.grid(row=1, column=0)
def addbutton():
global Bigbadtext
Bigbadtext = saysomething.get()
app.InstanceLinker(InProgressCandidates).AddtoList()
okbutton = ttk.Button(self, text='Add', command=addbutton)
okbutton.grid(row=2, column=0)
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns='something', selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('something', width=150, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('something', text='Say something', anchor=tk.W)
def AddtoList(self):
global Counter
Counter += 1
print('I was here')
self.tree.insert("", Counter, Bigbadtext, text=Counter)
self.tree.set(Bigbadtext, 'something', Bigbadtext)
app = MainApp()
app.mainloop()
The problem is that you are creating two treeview widgets, and then adding items to the one that is invisible.
You create one here:
frame = InProgressCandidates(self)
Then you create another one here:
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
Since you've already created one, the one you created should be what goes in self.FrameList:
self.FrameList = {InProgressCandidates:frame}
It is not really an answer but I up voted the question because it solved me a problem. I wanted to add items to the widget but did not want to show it to the user until I finished to populate the tree. But each insert showed right away. Now I create 2 identical widgets, one visible and the other is not, and once it is populated I change between them. Thus even a mistake can have a benefit.
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()
What I am trying to do is to link a GUI from one class in a separate file to another.
My first class is a main menu which will display a few buttons that will link to another window.
The second class displays a different window, but the problem I am having at the moment is that I don't know how to link the button in the first class to call the second class.
Here's the code I have so far:
First file, the main menu:
from tkinter import *
import prac2_link
class main:
def __init__(self,master):
frame = Frame(master, width=80, height=50)
frame.pack()
self.hello = Label(frame, text="MAIN MENU").grid()
self.cont = Button(frame,text="Menu option 1", command=prac2_link.main2).grid(row=1)
root = Tk()
application = main(root)
root.mainloop()
second file:
from tkinter import *
class main2:
def __init__(self):
frame1 = Frame(self, width=80, height=50)
frame1.pack()
self.hello = Label(frame1, text="hello, its another frame").grid(row=0,column=0)
To create a new window, you have to use a Toplevel widget. You can use it as a superclass for your main2 class:
class main2(Toplevel):
def __init__(self):
Toplevel.__init__(self)
self.frame= Frame(self, width=80, height=50)
self.label = Label(self.frame, text='this is another frame')
self.frame.grid()
self.label.grid()
Then you only have to create an instance in the event handler of the Button in the other class:
class main1:
def __init__(self, master):
# ...
self.cont = Button(frame,text="Menu option 1", command=self.open_main2).grid(row=1)
def open_main2(self):
prac2_link.main2()