I have a askopenfilename() function inside my main python file which gets a file directory from the user when a button is pressed. My main file is called payroll.py.
I have another file called dataframes.py which has a function that imports excel files using the read_excel() function and then manipulates the data.
This code is in payroll.py:
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import dataframes
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.title("SportClips Automation 0.0.1")
self.geometry('500x500')
self.resizable(width=False, height=False)
names = ['Instructions', 'Payroll']
self.nb = self.create_notebook(names)
self.menu = self.create_menus()
tab = self.nb.tabs['Instructions']
tk.Label(tab, text='-Select the "Payroll" tab to run Stylist
Compensation Worksheets').pack()
image = tk.PhotoImage(file="1.png")
tk.Label(tab, image=image).pack()
self.mainloop()
def create_notebook(self, names):
nb = MyNotebook(self, names)
nb.pack()
def add_label(parent, text, row, column):
label = ttk.Label(parent, text=text)
label.grid(row=row, column=column, sticky=tk.N, pady=10)
return label
def payroll():
nb.pr = dataframes.runpayroll(nb.name)
def getname():
nb.name = filedialog.askopenfilename(title="Select file",
filetypes=(("excel files",
"*.xls"), ("all files", "*.*")))
tab = nb.tabs['Payroll']
add_label(tab, 'Click this button to process payroll', 1, 8)
b1 = ttk.Button(tab, text="Run Payroll", command= payroll())
b1.grid(row=2, column=8)
b2 = ttk.Button(tab, text="Select SAR file", command=lambda:
getname())
b2.grid(row=3, column=8)
return nb
def create_menus(self):
menu = tk.Menu(self, tearoff=False)
self.config(menu=menu)
sub_menu = tk.Menu(menu, tearoff=False)
menu.add_cascade(label="File", menu=sub_menu)
sub_menu.add_separator()
sub_menu.add_command(label='Exit', command=self.destroy)
return menu
class MyNotebook(ttk.Notebook):
def __init__(self, master, names):
super().__init__(master, width=795, height=475)
# Create tabs & save them by name in a dictionary
self.tabs = {}
for name in names:
self.tabs[name] = tab = ttk.Frame(self)
self.add(tab, text=name)
GUI()
But I guess this error: AttributeError: 'MyNotebook' object has no attribute 'name'
If you want runpayroll to be able to access name, you have to pass it as an argument.
But the bigger problem is that you don't seem to have a name to pass it in the first place.
The only thing you have called name anywhere is a local variable in that getname() function, which isn't usable outside that function. All you do with it is return name, but the caller isn't doing anything with the return value, because it's just a Tkinter command callback.
You need to think about where you want to store these things. The usual answer is to create a class—either a subclass of Notebook, or a "controller" class that owns a Notebook—and store things as instance variables. As an inferior alternative, you can sometimes get away with storing things as global (or nonlocal) variables. But, however you decide to do it, someone has to assign the value to that instance or global variable somewhere. And then, you can pass it to run_payroll.
In this case, you seem to already have a MyNotebook class, and you're creating an instance of that, so maybe that's the right place to store things.
While we're at it, there's no reason to return anything from a function whose caller is just going to ignore the results. And also, you don't need lambda: payroll(); you can just use payroll for the same effect, but more readably (and even more efficiently).
So:
def create_notebook(self, names):
nb = MyNotebook(self, names)
nb.pack()
# …
def payroll():
nb.pr = dataframes.runpayroll(nb.name)
def getname():
nb.name = filedialog.askopenfilename(title="Select file",
filetypes=(("excel
files", "*.xls"), ("all files", "*.*")))
# …
And now, just change runpayroll to use that value:
def runpayroll(name):
df_sar = pd.read_excel(name,
sheet_name=0, header=None, skiprows=4)
Related
I am looking to override the Tkinter Frame, Button, Lable and Entry widgets to have them creted and packed in the same line of code. (IMO 2 lines to do this and get a reference for the obj is ugly, inefficient and harder to read even if it provides more flexability).
My previous code was this:
def cFrame(self, element, bg="white", borderwidth=0, relief="groove", side=None, padx=0, pady=0, height=0, width=0, expand=0, fill=None, image=None, highlightbackground=None, highlightcolor=None, highlightthickness=0, ipadx=0, ipady=0):
f = self.TkUtil.Frame(element, bg=bg, borderwidth=borderwidth, relief=relief, height=height, width=width, image=image, highlightbackground=highlightbackground, highlightcolor=highlightcolor, highlightthickness=highlightthickness)
f.pack(side=side, padx=padx, pady=pady, ipadx=ipadx, ipady=ipady, expand=expand, fill=fill)
return f
My proposed class would look like this:
class cFrame(Frame):
def __init__(self, master, **kwargs):
Frame.__init__(*(self, master), **kwargs)
self.pack(**kwargs)
The issue with this being **kwargs does not care if the keyword is valid and returns 'bad option -"kwarg"'.
I have tried expanding the function a bit more but I keep having issues:
class cButton(Button):
def __init__(self, master, **kwargs):
Button.__init__(*(self, master))
self.conf(**kwargs)
def conf(self, **kwargs ):
__pk_ops = {}
for k, v in kwargs.items():
try:
self.configure({k:v})
except:
__pk_ops[k] = v
self._pk(__pk_ops)
def _pk(self, __ops):
self.pack(__ops)
In this code, I have to loop through all kwargs before and indervidually configure them which is a lot of wasted time over the 1000's of widgets needed. Additionally, I have issues where self.configure(text="sometext") errors so 'text' is passed through to the self.pack() method because for some magic reason it doesn't like that option in particular (same for compound in this example too).
My end goal is to have x = Frame(root, bg="black") \n x.pack(side=TOP) be replaced with x = cFrame(root, bg="black", side=TOP) without default any of the options like my current code making any changes difficult and any sort of theam almost impossible.
You can create a custom class to override the pack() as below:
def cWidget:
def pack(self, **kw):
super().pack(**kw) # call original pack()
return self # return widget itself
# can do the same for grid() and place()
Then create custom widget class as below:
import tkinter as tk
class cButton(cWidget, tk.Button): pass
# do the same for other widgets you want
Now you can use the custom button class as below:
...
root = tk.Tk()
def clicked():
print('Button clicked')
btn['text'] = 'Clicked' # can use 'btn' to change its text as well
btn = cButton(root, text='Click me!', command=clicked, bg='gold').pack(padx=10, pady=10)
print(f'{btn=}') # here you won't get None
print(isinstance(btn, tk.Button)) # here you get True
...
Note that for Python 3.8+, you can simply use the Walrus Operator (:=):
(btn := tk.Button(root, text='Click me!')).pack(padx=5, pady=5)
Hello I'm trying to make a notepad written in python using tkinter. I'm having trouble making the edit menu work. I'm currently trying to implement copy, cut, and paste but I'm having issues. When I try to press the cut button I get a message like this "TypeError: cut() missing 2 required positional arguments: 'self' and 'event'" I'm honestly new to the whole class thing in Python so this is my first attempt at using that. I've shortened my code for convenience. Any help would be appreciated!
class Notepad:
#Functions
def __init__(self, master, **kw):
Text.__init__(self, master, **kw)
self.bind('<Control-c>', self.copy)
self.bind('<Control-x>', self.cut)
self.bind('<Control-v>', self.paste)
def copy(self, event=None):
self.clipboard_clear()
text = self.get("sel.first", "sel.last")
self.clipboard_append(text)
def cut(self, event):
self.copy()
self.delete("sel.first", "sel.last")
def paste(self, event):
text = self.selection_get(selection='CLIPBOARD')
self.insert('insert', text)
root = Tk()
menu = Menu(root)
root.config(menu=menu)
root.title('Written in Python')
root.minsize(width=100, height=100)
root.geometry('800x500+350+150') #Height, Width, X, Y coordinates of the program
#NotePad
textArea = ScrolledText.ScrolledText(root, width=1000, height=100)
#Height and width of notepad
textArea.pack()
root = Tk()
menu = Menu(root)
root.config(menu=menu)
editMenu = Menu(menu)
menu.add_cascade(label="Edit",menu=editMenu)
editMenu.add_separator()
editMenu.add_command(label="Cut", command=cut)
editMenu.add_command(label="Copy", command=copy)
editMenu.add_command(label="Paste", command=paste)
root.mainloop()
The function cut is defined with two arguments: self and event, but the menu command invokes cut() without argument, hence the error message.
In addition, your class structure looks strange to me, especially putting the last block of code directly in the class, not inside a class methods. I suggest you to create instead a Notepad class inheriting from Text (or ScrolledText), with your custom methods and bindings and put the root = Tk() ... outside the class, like that:
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
class Notepad(ScrolledText):
def __init__(self, master, **kw):
ScrolledText.__init__(self, master, **kw)
self.bind('<Control-c>', self.copy)
self.bind('<Control-x>', self.cut)
self.bind('<Control-v>', self.paste)
def copy(self, event=None):
self.clipboard_clear()
text = self.get("sel.first", "sel.last")
self.clipboard_append(text)
def cut(self, event=None):
self.copy()
self.delete("sel.first", "sel.last")
def paste(self, event=None):
text = self.selection_get(selection='CLIPBOARD')
self.insert('insert', text)
if __name__ == '__main__':
root = tk.Tk()
menu = tk.Menu(root)
root.config(menu=menu)
root.title('Written in Python')
root.minsize(width=100, height=100)
root.geometry('800x500+350+150') #Height, Width, X, Y coordinates of the program
#NotePad
notepad = Notepad(root, width=1000, height=100)
#Height and width of notepad
notepad.pack()
editMenu = tk.Menu(menu)
menu.add_cascade(label="Edit", menu=editMenu)
editMenu.add_separator()
editMenu.add_command(label="Cut", command=notepad.cut)
editMenu.add_command(label="Copy", command=notepad.copy)
editMenu.add_command(label="Paste", command=notepad.paste)
root.mainloop()
In the above code, the functions cut, copy and paste are methods of the Notepad class, you can invoke them with notepad.cut(event). Since, you don't use the event argument in the methods, it is just here for the binding, I suggest you to do def cut(self, event=None) so that event becomes optional, with default value None. This way you can directly use notepad.copy as a command in the editMenu.
I am completely new to Python and I wanted to create a simple script that will find me a specific value in a file and than perform some calculations with it.
So I have a .gcode file (it is a file with many thousands of lines for a 3D printer but it can be opened by any simple text editor). I wanted to create a simple Python program where when you start it, simple GUI opens, with button asking to select a .gcode file. Then after file is opened I would like the program to find specific line
; filament used = 22900.5mm (55.1cm3)
(above is the exact format of the line from the file) and extract the value 55.1 from it. Later I would like to do some simple calculations with this value. So far I have made a simple GUI and a button with open file option but I am stuck on how to get this value as a number (so it can be used in the equations later) from this file.
I hope I have explained my problem clear enough so somebody could help me :) Thank you in advance for the help!
My code so far:
from tkinter import *
import re
# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(Frame):
# Define settings upon initialization. Here you can specify
def __init__(self, master=None):
# parameters that you want to send through the Frame class.
Frame.__init__(self, master)
#reference to the master widget, which is the tk window
self.master = master
#with that, we want to then run init_window, which doesn't yet exist
self.init_window()
#Creation of init_window
def init_window(self):
# changing the title of our master widget
self.master.title("Used Filament Data")
# allowing the widget to take the full space of the root window
self.pack(fill=BOTH, expand=1)
# creating a menu instance
menu = Menu(self.master)
self.master.config(menu=menu)
# create the file object)
file = Menu(menu)
# adds a command to the menu option, calling it exit, and the
# command it runs on event is client_exit
file.add_command(label="Exit", command=self.client_exit)
#added "file" to our menu
menu.add_cascade(label="File", menu=file)
#Creating the button
quitButton = Button(self, text="Load GCODE",command=self.read_gcode)
quitButton.place(x=0, y=0)
def get_filament_value(self, filename):
with open(filename, 'r') as f_gcode:
data = f_gcode.read()
re_value = re.search('filament used = .*? \(([0-9.]+)', data)
if re_value:
value = float(re_value.group(1))
else:
print 'filament not found in {}'.format(root.fileName)
value = 0.0
return value
print get_filament_value('test.gcode')
def read_gcode(self):
root.fileName = filedialog.askopenfilename( filetypes = ( ("GCODE files", "*.gcode"),("All files", "*.*") ) )
self.value = self.get_filament_value(root.fileName)
def client_exit(self):
exit()
# root window created. Here, that would be the only window, but
# you can later have windows within windows.
root = Tk()
root.geometry("400x300")
#creation of an instance
app = Window(root)
#mainloop
root.mainloop()
You could use a regular expression to find the matching line in the gcode file. The following function loads the whole gcode file and does the search. If it is found, the value is returned as a float.
import re
def get_filament_value(filename):
with open(filename, 'r') as f_gcode:
data = f_gcode.read()
re_value = re.search('filament used = .*? \(([0-9.]+)', data)
if re_value:
value = float(re_value.group(1))
else:
print('filament not found in {}'.format(filename))
value = 0.0
return value
print(get_filament_value('test.gcode'))
Which for your file should display:
55.1
So your original code would look something like this:
from tkinter import *
import re
# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(Frame):
# Define settings upon initialization. Here you can specify
def __init__(self, master=None):
# parameters that you want to send through the Frame class.
Frame.__init__(self, master)
#reference to the master widget, which is the tk window
self.master = master
#with that, we want to then run init_window, which doesn't yet exist
self.init_window()
#Creation of init_window
def init_window(self):
# changing the title of our master widget
self.master.title("Used Filament Data")
# allowing the widget to take the full space of the root window
self.pack(fill=BOTH, expand=1)
# creating a menu instance
menu = Menu(self.master)
self.master.config(menu=menu)
# create the file object)
file = Menu(menu)
# adds a command to the menu option, calling it exit, and the
# command it runs on event is client_exit
file.add_command(label="Exit", command=self.client_exit)
#added "file" to our menu
menu.add_cascade(label="File", menu=file)
#Creating the button
quitButton = Button(self, text="Load GCODE",command=self.read_gcode)
quitButton.place(x=0, y=0)
# Load the gcode file in and extract the filament value
def get_filament_value(self, fileName):
with open(fileName, 'r') as f_gcode:
data = f_gcode.read()
re_value = re.search('filament used = .*? \(([0-9.]+)', data)
if re_value:
value = float(re_value.group(1))
print('filament value is {}'.format(value))
else:
value = 0.0
print('filament not found in {}'.format(fileName))
return value
def read_gcode(self):
root.fileName = filedialog.askopenfilename(filetypes = (("GCODE files", "*.gcode"), ("All files", "*.*")))
self.value = self.get_filament_value(root.fileName)
def client_exit(self):
exit()
# root window created. Here, that would be the only window, but
# you can later have windows within windows.
root = Tk()
root.geometry("400x300")
#creation of an instance
app = Window(root)
#mainloop
root.mainloop()
This would save the result as a float into a class variable called value.
I'm writing a python script that requires the user to enter the name of a folder. For most cases, the default will suffice, but I want an entry box to appear that allows the user to over-ride the default. Here's what I have:
from Tkinter import *
import time
def main():
#some stuff
def getFolderName():
master = Tk()
folderName = Entry(master)
folderName.pack()
folderName.insert(END, 'dat' + time.strftime('%m%d%Y'))
folderName.focus_set()
createDirectoryName = folderName.get()
def callback():
global createDirectoryName
createDirectoryName = folderName.get()
return
b = Button(master, text="OK and Close", width=10, command=callback)
b.pack()
mainloop()
return createDirectoryName
getFolderName()
#other stuff happens....
return
if __name__ == '__main__':
main()
I know next to nothing about tkInter and have 2 questions.
Is over-riding the default entry using global createDirectoryName within the callback function the best way to do this?
How can I make the button close the window when you press it.
I've tried
def callback():
global createDirectoryName
createDirectoryName = folderName.get()
master.destroy
but that simply destroys the window upon running the script.
I don't know how experienced are you in Tkinter, but I suggest you use classes.
try:
from tkinter import * #3.x
except:
from Tkinter import * #2.x
class anynamehere(Tk): #you can make the class inherit from Tk directly,
def __init__(self): #__init__ is a special methoed that gets called anytime the class does
Tk.__init__(self) #it has to be called __init__
#further code here e.g.
self.frame = Frame()
self.frame.pack()
self.makeUI()
self.number = 0 # this will work in the class anywhere so you don't need global all the time
def makeUI(self):
#code to make the UI
self.number = 1 # no need for global
#answer to question No.2
Button(frame, command = self.destroy).pack()
anyname = anynamehere() #remember it alredy has Tk
anyname.mainloop()
Also why do you want to override the deafult Entry behavior ?
The solution would be to make another button and bind a command to it like this
self.enteredtext = StringVar()
self.entry = Entry(frame, textvariable = self.enteredtext)
self.entry.pack()
self.button = Button(frame, text = "Submit", command = self.getfolder, #someother options, check tkitner documentation for full list)
self.button.pack()
def getfolder(self): #make the UI in one method, command in other I suggest
text = self.enteredtext.get()
#text now has whats been entered to the entry, do what you need to with it
I'd like to have a generic method that will change the value of a tkinter variable. I want to be able to do this from a menu option that brings up a new dialog box which is from a generic method that can be used for different variables. the code I have so far is below:
import sys
from tkinter import *
from tkinter import ttk
from tkinter import filedialog
import sequencer as seq
class View(ttk.Frame):
"""Main Gui class"""
def __init__(self, master = None):
ttk.Frame.__init__(self, master, borderwidth=5, width=450, height=500)
self.master = master
self.grid(column=0, row=0, sticky=(N, S, E, W))
self.columnconfigure(0, weight=1)
###############################
### User editable variables ###
self.precision = IntVar(value=4, name='precision')
self.sensitivity = IntVar(value = 50, name='sensitivity')
### User editable variables ###
###############################
self.create_menus()
def create_menus(self):
"""Produces the menu layout for the main window"""
self.master.option_add('*tearOff', FALSE)
self.menubar = Menu(self.master)
self.master['menu'] = self.menubar
# Menu Variables
menu_file = Menu(self.menubar)
menu_edit = Menu(self.menubar)
# Add the menus to the menubar and assign their variables
self.menubar.add_cascade(menu=menu_file, label="File")
self.menubar.add_cascade(menu=menu_edit, label = "Edit")
### ADD COMMANDS TO THE MENUES ###
### File ###
menu_file.add_command(label="Quit", command=self.master.destroy)
### Edit ###
menu_edit.add_command(label="Backbone", command=lambda : self.edit_backbone())
menu_edit.add_command(label="Precision", command=lambda : self.precision.set(self.set_value_int("Precision")))
menu_edit.add_command(label="Sensitivity", command=lambda : self.sensitivity.set(self.set_value_int("Sensitivity")))
menu_edit.add_command(label="print precision", command=lambda : print(self.precision.get()))
menu_edit.add_command(label="print sensitivity", command=lambda : print(self.sensitivity.get()))
def set_value_int(self, name):
"""Standards dialog that return a user define value of a specific type"""
t = Toplevel(self)
t.title("Set " + name)
label = ttk.Label(t, text="Set "+name)
label.grid(row=0)
entry = ttk.Entry(t)
entry.grid(row=1)
cancel = ttk.Button(t, text="Cancel", command=lambda : t.destroy())
cancel.grid(column=0, row=2)
okey = ttk.Button(t, text="Okey", command=lambda : okey(entry.get()))
okey.grid(column=1, row=2)
def okey(value):
"""return value according to type"""
try:
t.destroy()
return int(value)
except:
self.error_box("value must be and integer")
def error_box(self, error_message="Unknown error"):
"""(Frame, String) -> None
Opens an window with an Okey button and a custom error message"""
t=Toplevel(self)
t.title("Error")
label = ttk.Label(t, text=error_message)
label.grid(row=0)
okey = ttk.Button(t, text="Okey", command=lambda : t.destroy())
okey.grid(row=1)
if __name__ == "__main__":
root = Tk()
root.title("Sequencer")
view = View(root)
root.mainloop()
print("End")
The Edit-> print xxxxxx commands are purely for testing purposes to check if the values have changed. If these are executed before trying to change the values of precision or sensitivity then they work as expected.
If you try to change either of the tkinter variables in the way I have tried to do they become None types and I can't see why. I can only assume that you are not allowed to change them in the way that I have but I can't think of another way to do it without having a separated method for each variable which I'd like to avoid.
Baicly I'd like the user to be able to customise the variables precision and sensitivity and use the same method in the code to change the values.
Extra but not necessarily vital:- If there is a way to define which type the variable should be in the methods arguments as well that would be even better as I will have other variables for the user to change later and they will be of different types.
Any help would be much appreciated. I have tried to be as clear as I can but let me know if anything is not.
Thanks
self.set_value_int always returns None, so it's always going to set your variable to none.
Instead of trying to write a complex lambda that is hard to debug, put all of your logic inside the function. Have the function set the value. All you need to do is tell it what variable to set:
menu_edit.add_command(label="Precision",
command=lambda name="Precision", var=self.precision: self.set_value_int(name, var))
...
def set_value_int(self, name, var):
...
def okey():
s = entry.get()
try:
var.set(int(s))
...