I am using Python 2.7 and Tkinter. I am almost new to Object Oriented programs. I have a long program with many Tkinter windows and at some point I ask the user to load an Excel file that I read with Pandas, and want to permanently use and update that value (of a data variable). The way that I am doing it now is with global variables but I know that it is dangerous, inefficient and not elegant at all.
Even though I could do controller.show_frame(framename) given the way my gui class is built, I ended up building some of the frames myself just so the data variable would update itself.
I read and tried some answers in Stack Overflow but may have implemented them wrong:
Tried creating a dictionary inside the gui class, something like self.app_data = {data=[],filename=""} and updating it from other windows, the thing here is that I think that the class gui is instanced only once and it kind of creates all of the other window classes so this did not work. Maybe I did something wrong there. (not shown on the code).
Tried to do something as what was suggested here but I could just not make it work.
Main frame is some sort of intermediate step that I need for other purposes; the following code is a simplification of my program.
I know this is an awful nightmare code! Thank you :)
import Tkinter as tk
import pandas as pd
import tkFileDialog
import tkMessageBox
global data, strat_columns, filename
data = pd.DataFrame([])
strat_columns = []
filename = ""
class gui(tk.Tk):
data = pd.DataFrame([])
filename = ""
def __init__(self):
tk.Tk.__init__(self)
container = tk.Frame(self)
container.pack(side="top",fill="both",expand=True)
self.frames = {}
for F in (main_frame, first_frame):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(main_frame)
def show_frame(self,sel_frame):
frame = self.frames[sel_frame]
frame.tkraise()
def get_page(self, page_class):
return self.frames[page_class]
class main_frame(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.parent = parent
self.controller = controller
button_new = tk.Button(self,
text="New window",
command=lambda: self.button_new_callback())
button_new.pack()
def button_new_callback(self,*args,**kwargs):
self.controller.show_frame(first_frame)
class first_frame(tk.Frame):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.controller = controller
self.parent = parent
self.show_frame = controller.show_frame
statusText.set("Press Browse button and browse for file, then press the Go button")
label = tk.Label(self, text="Please load a file: ")
label.pack()
entry = tk.Entry(self, width=50)
entry.pack()
button_go = tk.Button(self,
text="Go",
command=lambda: self.button_go_callback(entry,statusText,message))
button_browse = tk.Button(self,
text="Browse",
command=lambda: self.button_browse_callback(entry))
button_go.pack()
button_browse.pack()
message = tk.Label(self, textvariable=statusText)
message.pack()
def button_browse_callback(self,entry):
global filename
filename = tkFileDialog.askopenfilename()
entry.delete(0, tk.END)
entry.insert(0, filename)
def button_go_callback(self,entry,statusText,message):
global data
input_file = entry.get()
data = pd.read_excel(filename)
sf = second_frame(self.parent, self)
sf.grid(row=0, column=0, sticky="nsew")
sf.tkraise()
class second_frame(tk.Frame):
pass
if __name__ == "__main__":
my_gui = gui()
my_gui.mainloop()
my_gui.title("TEST")
There are a few things that are causing issues with your program from running properly.
The first thing I noticed is the use of global variables. This can be avoided with the use of class attributes.
For the 2 variables you have just below the line class gui(tk.Tk): you need to move them to the __init__ section so those variables can be instantiated. Also you need to make them into class attributes so other methods or even other classes can interact with them. We can do this by adding the self. prefix to the variable names.
Something like the below:
self.data = pd.DataFrame([])
self.filename = ""
To access the methods/attributes of the gui class you need to pass the object of the gui class to the other classes working with it.
statusText in your code is not defined so set() wont work here. Just add self.statusText as a class attribute.
Some of your widgets do not need to be assigned to a variable name as no editing is being done to them. For example:
label = tk.Label(self, text="Please load a file: ")
label.pack()
This can be simply changed to:
tk.Label(self, text="Please load a file: ").pack()
This will help reduce the amount of code you are writing and keep the name space cleaner.
There are a few ways to correct all this but the easiest way is to move this code into one class. There is not a good reason with the code you have presented to have several frames separated from the main gui class.
The below code is a rewritten version of your code using one class to accomplish what appears to be the task your code is trying to accomplish and reducing the amount of code needed by around 30+ lines. I am sure it can be refined further but this example should be helpful.
import Tkinter as tk
import pandas as pd
import tkFileDialog
class gui(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.master = master
self.data = pd.DataFrame([])
self.filename = ""
self.strat_columns = []
self.main_frame()
self.first_frame()
self.mainframe.tkraise()
def get_page(self, page_class):
return self.frames[page_class]
def main_frame(self):
self.mainframe = tk.Frame(self.master)
self.mainframe.grid(row=0, column=0, sticky="nsew")
tk.Button(self.mainframe, text="New window",
command=lambda: self.firstframe.tkraise()).pack()
def first_frame(self):
self.firstframe = tk.Frame(self.master)
self.firstframe.grid(row=0, column=0, sticky="nsew")
self.statusText = tk.StringVar()
self.statusText.set("Press Browse button and browse for file, then press the Go button")
label = tk.Label(self.firstframe, text="Please load a file: ").pack()
self.first_frame_entry = tk.Entry(self.firstframe, width=50)
self.first_frame_entry.pack()
tk.Button(self.firstframe, text="Go", command=self.button_go_callback).pack()
tk.Button(self.firstframe, text="Browse", command=self.button_browse_callback).pack()
self.message = tk.Label(self.firstframe, textvariable=self.statusText)
self.message.pack()
def button_browse_callback(self):
self.filename = tkFileDialog.askopenfilename()
self.first_frame_entry.delete(0, tk.END)
self.first_frame_entry.insert(0, self.filename)
def button_go_callback(self):
self.data = pd.read_excel(self.filename)
if __name__ == "__main__":
root = tk.Tk()
root.title("TEST")
my_gui = gui(root)
root.mainloop()
in my opinion your are tying too much the data and the GUI. What if in the future you want to display something else? I would use a more generic approach: I would a create a DataProvider class that would read and return the data for you:
Data Provider
import pandas as pd
class DataProvider(object):
def __init__(self):
self._excel = {}
self._binary = {}
def readExcel(self, filename):
self._excel[filename] = pd.read_excel(filename)
def excel(self):
return self._excel
def readBinary(self, filename):
self._binary[filename] = open(filename,"rb").read()
def binary(self):
return self._binary
Using this class you can obtain the data that you can present in your GUI:
gui.py
from Tkinter import *
from DataProvider import *
import binascii
#example data
dp = DataProvider()
dp.readExcel("file.xlsx")
dp.readBinary("img.png")
root = Tk()
frame = Frame(root)
frame.pack()
bottomframe = Frame(root)
bottomframe.pack( side = BOTTOM )
w = Label(bottomframe, text=dp.excel()["file.xlsx"])
w.pack()
w = Label(bottomframe, text=binascii.hexlify(dp.binary()["img.png"][:5]))
w.pack()
root.mainloop()
If all works well you should have a small GUI showing the content of the Excel file and the first few bytes of the image.
Let me know if all works fine or if you need more info.
Thanks
Related
I'm trying to make it so whenever i select a radiobutton it will .insert() a value inside of an empty entry that is declared in another class and instanced in the application class.
import tkinter as tk
class DataDisplay(tk.Frame):
""" Provides the GUI, powered by the Tkinter module. """
def __init__(self, parent):
super().__init__()
tk.Frame.__init__(self, parent)
self.parent = parent
self.grid()
self.create_widgets(parent)
def create_widgets(self, parent):
print("Creating widgets... ")
# Radio buttons frame
radiobuttons = Radiobuttons(parent)
radiobuttons.grid(column=1, row=0, sticky=tk.NE)
# ID, calibration date, arrival date frame.
data_entry_frame = tk.Frame(parent, borderwidth=3, relief='ridge')
data_entry_frame.grid(column=0, row=0, sticky=tk.NE)
# Producer info frame
product_info = ProductInfo(data_entry_frame)
product_info.pack(side=tk.TOP)
class Radiobuttons(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
# Setting border-width and border-type
self.configure(borderwidth=3, relief='ridge')
# Setting self.radio to be an integer for function use.
self.radio = tk.IntVar()
# Defining radio-buttons
rb = tk.Radiobutton(self, text='Molybdenum-99', variable=self.radio, value=1, indicatoron=0,
width=15, overrelief='sunken',
command= lambda: DataDisplay.product_info.iso_half_life_value_label.insert(0, 'test'))
rb.pack(anchor=tk.W)
rb = tk.Radiobutton(self, text='Technetium-99M', variable=self.radio, value=2, indicatoron=0,
width=15, overrelief='sunken',
command=lambda: print('Radiobutton selected Technetium99M'))
rb.pack(anchor=tk.W)
def get(self):
return self.radio.get()
class ProductInfo(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.label = tk.Label(self, text='Insert here: ')
self.label.grid(column=0, row=5)
self.iso_half_life_value_label = tk.Entry(self)
self.iso_half_life_value_label.grid(column=1, row=5)
print('Finished creating widgets.')
if __name__ == "__main__":
root = tk.Tk()
app = DataDisplay(root)
root.title("DataDisplay")
root.geometry('800x800')
root.resizable(True, True)
root.mainloop()
The error i'm getting with my current attempt: AttributeError: type object 'DataDisplay' has no attribute 'product_info'
Expected result is to have a custom value based on radiobutton selection inserted into the product_info label.
As is the case with any python object, you need a reference to the object in order to change it. The simplest solution is to pass a reference to your objects to the other objects that need them.
For example, if you pass the instance of DataDisplay to Radiobuttons, Radiobuttons can then access the attributes of DataDisplay. For example:
class DataDisplay(...):
def create_widgets(...):
...
radiobuttons = Radiobuttons(parent, data_display=self)
...
class Radiobuttons(tk.Frame):
def __init__(self, parent, data_display):
...
rb = tk.Radiobutton(..., command= lambda: data_display.product_info.iso_half_life_value_label.insert(0, 'test'))
...
The other part of the problem is that you're not saving product_info as an attribute of the object. You need to do that when you create it:
self.product_info = ProductInfo(...)
Solved by making product_info a global variable. Not sure if this is best practice however.
I am learning Python and tkinter by going through several tutorials (python-course.eu & effbot.org). I have come across a pair of situations and am trying to learn the reasons behind the differences.
NOTE: I found many questions regarding __init__ in class, but not __init__ in frame.
Both call a class from the main, and use a constructor (def __init__) to create an instance of the class. Am I using correct terminology?
They both create a frame; however, one uses Frame.__init__(self,parent): and the other uses Frame(master):
Why does the first include __init__ in the construction of the frame?
Why does the second omit 'self' in the arguments when creating the frame?
Why does the first not use 'pack' to place, but the second does?
What other differences are noteworthy?
The following are MWE to produce the respective outputs.
import tkinter as tk
class Checkbar(tk.Frame):
def __init__(self, parent=None, picks=[], side=tk.LEFT, anchor=tk.W):
tk.Frame.__init__(self, parent)
self.vars = []
for pick in picks:
var = tk.IntVar()
chk = tk.Checkbutton(self, text=pick, variable=var)
chk.pack(side=side, anchor=anchor, expand=tk.YES)
self.vars.append(var)
def state(self):
return map((lambda var: var.get()), self.vars)
if __name__ == '__main__':
root = tk.Tk()
lng = Checkbar(root, ['Python', 'Ruby', 'Perl', 'C++'])
tgl = Checkbar(root, ['English','German'])
lng.pack(side=tk.TOP, fill=tk.X)
tgl.pack(side=tk.LEFT)
lng.config(relief=tk.GROOVE, bd=2)
def allstates():
print(list(lng.state()), list(tgl.state()))
tk.Button(root, text='Quit', command=root.destroy).pack(side=tk.RIGHT)
tk.Button(root, text='Peek', command=allstates).pack(side=tk.RIGHT)
root.mainloop()
print("You've selected ", list(lng.state()))
Language Widget
import tkinter as tk
class App:
def __init__(self, master):
frame = tk.Frame(master)
frame.pack()
self.button = tk.Button(frame, text="Quit", fg="red", command=master.destroy, padx=20)
self.button.pack(side='left', expand=10)
self.hi_there = tk.Button(frame, text="Hello", command=self.say_hi)
self.hi_there.pack(side='left')
def say_hi(self):
print("Hi there, everyone!")
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()
Hello Window
Thank you, kindly.
I'm having some trouble creating an entry widget with tkinter. I've imported the necessary modules and have already created several buttons and check boxes. However I cannot figure out how to properly initialize the Entry. Here is my relevant code:
# Necessary Modules.------------------------------------------------------------
import win32com.client as win32
import re
from tkinter import *
from tkinter.filedialog import askopenfilename
import tkinter.messagebox
# Class for selecting the file.-------------------------------------------------
class FilenameClass():
def __init__(self):
self.location = 'User Import.txt'
def getFile(self, identity):
self.file_opt = options = {}
options['defaultextension'] = '.txt'
options['filetypes'] = [('Text Document (.txt)', '.txt'),
('all files', '.*')]
self.filename = askopenfilename(**self.file_opt)
if self.filename:
if 'User Import' in identity:
self.location = self.filename
app.get_txt_File['bg'] = '#0d0'
user_file = open(self.filename, 'r')
user_total = user_file.read()
remove_lines = user_total.splitlines()
for user in remove_lines:
regex_tab = re.compile('\\t')
user_info = regex_tab.split(user)
app.users.append(user_info)
else:
app.loadButton['bg'] = '#e10'
# Main Class.-------------------------------------------------------------------
class Application(Frame, Tk):
def __init__(self, master=None):
Frame.__init__(self, master)
self.users = []
self.fileOBJtxt = FilenameClass()
self.createWidgets()
def createWidgets(self):
# Define the default values for the options for the buttons
# Grid layout options
self.rowconfigure(0, minsize=5)
self.width = 54
self.grid(padx=5)
self.loadButton_gopt = {'row':1,'column':1,'padx': 2, 'pady': 5}
self.loadButton_wopt = {'width': round(self.width),'bg':'#e10'}
self.loadButton()
self.trainingCheckBox()
self.signatureInput()
def loadButton(self):
'''Button that calls the filename class which allows the user to select
the text file they wish to use.'''
self.get_txt_File = Button(self, text="Load User List", \
command=lambda: self.fileOBJtxt.getFile('User Import'))
for key, value in self.loadButton_wopt.items():
self.get_txt_File[key] = value
self.get_txt_File.grid(**self.loadButton_gopt)
def trainingCheckBox(self):
self.training_var = IntVar()
self.training = Checkbutton(text="Include training video?", \
variable=self.training_var).grid(row=2, sticky=W)
def signatureInput(self):
Label(text="Signature Name").grid(row=4, sticky=W)
entry = Entry(bg='#fff', width=50)
entry.grid(row=4, column=1, columnspan=4)
# Initialization parameters.----------------------------------------------------
if __name__ == '__main__':
app = Application()
app.master.title('User Notification Tool')
app.master.geometry('405x550+100+100')
app.master.resizable(width=False, height=False)
app.mainloop()
I'm not seeing any tracebacks, but I can't seem to get my Entry box to show up. What am I doing wrong?
EDIT: added entire code.
The problem with your entry field is you have not told it what frame/window to be placed in.
Change:
entry = Entry(bg='#fff', width=50)
To:
entry = Entry(self, bg='#fff', width=50)
Make sure you always provide the window/frame that a widget is going to be placed in as the first argument. In this case it is self as self refers to a frame.
Keep in mind that your program will not be able to get() the string inside of your entry field because you have not defined it as a class attribute. So most likely you will need to change
This:
entry = Entry(bg='#fff', width=50)
entry.grid(row=4, column=1, columnspan=4)
To This:
self.entry = Entry(self, bg='#fff', width=50)
self.entry.grid(row=4, column=1, columnspan=4)
This change will be necessary in order for the rest of your application to be able to read or write to the entry widget.
Change
entry = Entry(bg='#fff', width=50)
to
entry = tk.Entry(bg='#fff', width=50)
I've read a few threads all over the internet regarding clearing a text box on tkinter. Basically everyone says it's simple:
text.delete("1.0", END)
However, perhaps it has something to do with the way I structured it, or the way I'm calling it, but for some reason, this does not work for me. It simply does nothing.
I've tried re-positioning the def, and re-writing the text.delete("1.0", END) in a number of ways, most of which lead me to other errors, but I cannot seem to get this to work.
Ultimately, what I'm trying to accomplish is that when I click a button, the text box will clear, before populating with new information.
Below is my code.
from tkinter import *
from PIL import Image, ImageTk
import functions
class MainWindow(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("pyTicket")
# TOOLBAR ####################################################
toolbar = Frame(self.parent, bd=1, relief=RAISED)
self.img = Image.open("Icons\startupcheck.png")
eimg = ImageTk.PhotoImage(self.img)
startupButton = Button(toolbar, text="Re-Check ", image=eimg, compound="left", relief=RAISED, command=self.StartUpChecker)
startupButton.image = eimg
startupButton.pack(side=RIGHT, padx=2, pady=2)
toolbar.pack(side=TOP, fill=X)
self.parent.config(menu=menubar)
self.pack(anchor=N, side=TOP, fill=X, expand=False)
# TOOLBAR ####################################################
# TEXTBOX ####################################################
self.textbox = Text(self, wrap="word", height=5)
self.textbox.pack(side="bottom", fill="both", expand=True)
self.textbox.tag_configure("TextBox", foreground="#b22222")
self.pack(anchor=S, side=BOTTOM, fill=BOTH, expand=True)
# TEXTBOX ####################################################
# Functions ###################################################
def StartUpChecker(self):
self.clear_text()
functions.StartUpChecker()
def clear_text(self):
self.textbox.delete("1.0", END)
class TextRedirector(object):
def __init__(self, widget, tag="stdout"):
self.widget = widget
self.tag = tag
def write(self, str):
self.widget.configure(state="normal")
self.widget.insert("end", str, (self.tag,))
self.widget.configure(state="disabled")
def main():
root = Tk()
#Width X Height
root.geometry("500x300+300+300")
root.update()
root.minsize(400,200)
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
You don't appear to actually use the TextRedirector class in the code you posted, but if you're using it in your actual code, note that its .write() method leaves the textbox in a disabled state - which prevents ALL modifications, even those resulting from code instead of direct user action. Your .clear_text() method needs to temporarily enable the textbox so that you can modify it, exactly as .write() does.
i need to send one variable from one tkinter script to other python script.
my code is like this.
import os
import sys
from Tkinter import *
import Tkinter as tk
class SeaofBTCapp(tk.Tk):
def __init__(self,*args,**kwargs):
## class parent and parameter
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class PageDigital(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
entry = Entry(self, width=13, font=("Helvetica", 20))
entry.place(x=50, y=300)
def OK():
os.system('pri.py')
button21 = tk.Button(self, text="OK", font=("Helvetica", 15), width=8,command=OK)
button21.place(x=300, y=300)
write1=entry1["text"]
import pri
app = SeaofBTCapp()
app.mainloop()
i need to send string inside entry to pri.py and print it
pri.py script is like this
from __main__ import *
print write1
thanks
Try making the OK function pass entry.get() to the pri.py script, this should pass the value of the StringVar to your script.
If you need to close your frame before passing/printing it, turn the entry into a member variable (self.entry = Entry(..)) and access it outside of the class/functions with app.entry.get().
EDIT 06/27: New code example. Here you can see how I'm able to use the class structure to access the variable obtained from the first Entry field and display it in the Label of the Toplevel. I hope this is able to help as I'm not 100% sure on what you're trying to do.
from tkinter import *
class Window():
def __init__(self, root):
self.f = Frame(root)
self.f.pack()
self.e = Entry(self.f, width = 20)
self.e.pack(side = LEFT)
self.b = Button(self.f, width = 15, text = "OK", command = lambda: self.OK(root))
self.b.pack(side = RIGHT)
def OK(self, root):
print ("Accessing entry inside class: %s" %self.e.get()) #Could pass to pri.py here
self.word = self.e.get()
new = self.newWindow()
root.wait_window(new)
root.destroy()
def newWindow(self):
def done(newWindow):
newWindow.destroy()
newWindow = Toplevel()
newWindow.tkraise() # <- Personally never needed this, but it's in
l = Label(newWindow, text = "Here is the variable in a top window: %s" %self.word) #Using the variable in another window
l.pack(side = LEFT)
b = Button(newWindow, text = "Done", command = lambda: done(newWindow))
b.pack(side = RIGHT)
return newWindow
root = Tk()
w = Window(root)
w.f.mainloop()
print ("Accessing entry outside class: %s" %w.word) #Or here