I have a python script for teachers that works very well on my terminal and I'm trying to use Tkinter so I can share this script with other people. In the script, the user uploads a file and other information to an SQLite database.
Below is part of the script that shows just the content for the 1st tab (of 3). The problem is that when people interact with the database the tab needs to get refreshed to show something has happened and that the list of files in the database has changed.
Everything I've read shows you need a button to refresh. But that is not user-friendly. What people want to do is upload a file and then see that file in the list. Is this possible with Tkinter and if so how?
class AppWindow():
my_list = [4]
def __init__(self, parent):
global my_list
# Create the window
self.window = parent
self.window.geometry("700x600")
#self.center_window()
self.window.title("Test app")
# Create a text label and place it in the window
self.hello_label = tk.Label(self.window, text="Hello world!")
self.hello_label.place(x=20, y=20)
# Create 3 tabs
self.tab_container = tk.Frame(self.window)
self.tab_container.place(x=0,y=0,width=700,height=400)
self.tabs = ttk.Notebook(self.tab_container)
self.tab_2 = tk.Frame(self.tabs)
self.tabs.add(self.tab_2, text="Parameters")
self.tabs.place(x=0,y=0,height=400,width=700)
# Content for tab 2
self.label2 = tk.Label(self.tab_2, text="")
self.label201=tk.Label(self.tab_2, text="Put in your target GPA")
self.label201.place(x=50, y=50)
btn1 = tk.Button(self.tab_2,text="Target GPA", command=self.getGPA)
btn1.place(x=50, y=80)
for lst in self.my_list:
btn99=tk.Button(self.tab_2,text=lst)
btn99.grid()
def getGPA(self):
userInput = sd.askstring('User Input','Enter target GPA')
self.my_list.append(userInput)
if __name__ == "__main__":
root = tk.Tk()
app = AppWindow(root)
root.mainloop()
This is merely a guess because of the all the reasons I've already mentioned in comments under your question. Generally speaking, to update or what you call "refresh" a tab requires adding, changing, or deleting the widgets you put on it.
The code below is based what's currently in your code. Every time the Target GP button is clicked another Button is added to the self.tab_2 frame as well as appended to my_list. Changing the widgets doesn't have to be triggered by clicking on a button, it could be the result of some other event (such as the completion of a file upload in the background for example).
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.simpledialog as sd
class AppWindow():
def __init__(self, parent):
# Create the window
self.window = parent
self.window.geometry("700x600")
#self.center_window()
self.window.title("Test app")
# Create and initialize data list.
self.my_list = [4]
# Create a text label and place it in the window
self.hello_label = tk.Label(self.window, text="Hello world!")
self.hello_label.place(x=20, y=20)
# Create 3 tabs
self.tab_container = tk.Frame(self.window)
self.tab_container.place(x=0, y=0, width=700, height=400)
self.tabs = ttk.Notebook(self.tab_container)
self.tab_2 = tk.Frame(self.tabs)
self.tabs.add(self.tab_2, text="Parameters")
self.tabs.place(x=0, y=0, height=400, width=700)
# Content for tab 2
self.label201 = tk.Label(self.tab_2, text="Put in your target GPA")
self.label201.place(x=50, y=50)
btn1 = tk.Button(self.tab_2, text="Target GPA", command=self.getGPA)
btn1.place(x=50, y=80)
# Create a Button for each item currently in list.
for item in self.my_list:
btn = tk.Button(self.tab_2, text=item)
btn.grid()
def getGPA(self):
userInput = sd.askstring('User Input', 'Enter target GPA')
if userInput is None: # User closed the dialog or clicked Cancel?
return
self.my_list.append(userInput)
btn = tk.Button(self.tab_2, text=userInput) # Create another Button.
btn.grid()
if __name__ == "__main__":
root = tk.Tk()
app = AppWindow(root)
root.mainloop()
Related
I'm trying to make it so that new information shows in in a new window, but I want the new window to be connected to the parent window, even when the parent window is clicked the new window should still show up similar to how a dropdown menu works. I'm also planning on having some of the new windows have treeviews later on.
from tkinter import *
win = Tk()
win.geometry("500x500+0+0")
def button_function ():
win2 = Toplevel()
label = Label(win2,text='dropdown', width=7)
label.pack()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y()+30}")
button = Button(win, command=lambda: button_function (), width=12)
button.pack()
win.mainloop()
Ok so with a little bit of googling I came across this post: tkinter-detecting-a-window-drag-event
In that post they show how you can keep track of when the window has moved.
By taking that code and making some small changes we can use the dragging() and stop_drag() functions to move the top level window back to where it was set to relative to the main window.
That said this will only work in this case. You will need to write something more dynamic to track any new windows you want so they are placed properly and on top of that you will probably want to build this in a class so you do not have to manage global variables.
With a combination of this tracking function and using lift() to bring the window up we get closer to what you are asking to do.
That said you will probably want remove the tool bar at the top of the root window to be more clean. I would also focus on using a dictionary or list to keep track of open and closed windows and their locations to make the dynamic part of this easier.
import tkinter as tk
win = tk.Tk()
win.geometry("500x500+0+0")
win2 = None
drag_id = ''
def dragging(event):
global drag_id
if event.widget is win:
if drag_id == '':
print('start drag')
else:
win.after_cancel(drag_id)
print('dragging')
drag_id = win.after(100, stop_drag)
if win2 is not None:
win2.lift()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y() + 30}")
def stop_drag():
global drag_id, win2, win
print('stop drag')
drag_id = ''
if win2 is not None:
win2.lift()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y() + 30}")
win.bind('<Configure>', dragging)
def button_function():
global win2
win2 = tk.Toplevel()
label = tk.Label(win2, text='drop down', width=7)
label.pack()
win2.geometry(f"+{win.winfo_x()}+{win.winfo_y()+30}")
tk.Button(win, command=button_function, width=12).pack()
win.mainloop()
EDIT:
Ok so I took some time to write this up in a class so you could see how it could be done. I have also added some level of dynamic building of the buttons and pop up windows.
We use a combination of lists and lambdas to perform a little bit of tracking and in the end we pull off exactly what you were asking for.
let me know if you have any questions.
import tkinter as tk
class Main(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('500x500')
self.pop_up_list = []
self.drag_id = ''
self.button_notes = ['Some notes for new window', 'some other notes for new window', 'bacon that is all!']
self.bind('<Configure>', self.dragging)
for ndex, value in enumerate(self.button_notes):
print(ndex)
btn = tk.Button(self, text=f'Button {ndex+1}')
btn.config(command=lambda b=btn, i=ndex: self.toggle_button_pop_ups(i, b))
btn.grid(row=ndex, column=0, padx=5, pady=5)
self.pop_up_list.append([value, 0, None, btn])
def dragging(self, event):
if event.widget is self:
if self.drag_id == '':
pass
else:
self.after_cancel(self.drag_id)
self.drag_id = self.after(100, self.stop_drag)
for p in self.pop_up_list:
if p[1] == 1:
p[2].lift()
p[2].geometry(f"+{p[3].winfo_rootx() + 65}+{p[3].winfo_rooty()}")
def stop_drag(self):
self.drag_id = ''
for p in self.pop_up_list:
if p[1] == 1:
p[2].lift()
p[2].geometry(f"+{p[3].winfo_rootx() + 65}+{p[3].winfo_rooty()}")
def toggle_button_pop_ups(self, ndex, btn):
p = self.pop_up_list
if p[ndex][1] == 0:
p[ndex][1] = 1
p[ndex][2] = tk.Toplevel(self)
p[ndex][2].overrideredirect(1)
tk.Label(p[ndex][2], text=self.pop_up_list[ndex][0]).pack()
p[ndex][2].geometry(f"+{btn.winfo_rootx() + 65}+{btn.winfo_rooty()}")
else:
p[ndex][1] = 0
p[ndex][2].destroy()
p[ndex][2] = None
if __name__ == '__main__':
Main().mainloop()
I'm currently trying to create a desktop file converter application for myself using tkinter. It so far has a drag and drop area, and a button you can press to just select the file from the file explorer. However, I'm having trouble figuring out how to correctly position the widgets so they sit on top of each other. I want it so the drag and drop box is off the the left side of the screen, and in the middle of the box I want a text widget that says, "Drag and drop file, or select them", with a button widget below it that allows them to select from the file manager if they please.
import tkinter as tk
import tkinter.filedialog
from TkinterDnD2 import DND_FILES, TkinterDnD
from conversion import *
#global variables
path_to_file = " "
file_type = " "
compatable_converstion = []
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.master.title("File Converter")
self.master.minsize(1000,600)
self.master.maxsize(1200,800)
self.pack()
self.create_widgets()
def create_widgets(self):
#Drag and drop files area
self.drop_box = tk.Listbox(root, selectmode=tk.SINGLE, background="#99ff99")
self.drop_box.pack(ipadx=170)
self.drop_box.pack(ipady=120)
self.drop_box.pack(side="left")
self.drop_box.drop_target_register(DND_FILES)
self.drop_box.dnd_bind("<<Drop>>", open_dropped_file)
#Select file button
self.select_file = tk.Button(self)
self.select_file["text"] = "Select File"
self.select_file["command"] = self.open_selected_file
self.select_file.place(relx=1.0, rely=1.0, anchor="se")
#Instructional Text
sentence = "Drag and drop or select your file"
self.instructions = tk.Text(root)
self.instructions.insert(tk.END, sentence)
self.instructions.place(relx=1.0, rely=1.0, anchor="se")
def open_selected_file(self):
path_to_file = tk.filedialog.askopenfilename(initialdir="/", title="Select A File", filetypes = (("jpeg files","*.jpg"),("all files","*.*")))
temp_str = " "
for chars in reversed(path_to_file):
if(chars == '.'):
break
temp_str += chars
file_type = temp_str[::-1]
compatable_converstion = retrieve_compatable_conversions(file_type)
def main():
global root
root = TkinterDnD.Tk()
app = Application(master=root)
app.mainloop()
if __name__ == "__main__":
main()
Just in case my explanation of how I want it laid out sucks, here is a picture:
You are creating the widgets self.drop_box and self.instructions on the root window. They should be created on self.
The place() geometry manager does not reserve space in a widget, so you will have to make the widget(self) as big as necessary with expand=True, fill='both'. I would advise using the grid() geometry manager for any design which is not really simple.
The widgets will be stacked in the order they are placed, which means that the Button will be hidden beneath the Text widget. Just change the order in which they are placed.
As for using ipadx and ipady in the pack() function, have a look at Why does the 'ipady' option in the tkinter.grid() method only add space below a widget?
Also; you don't need to make root a global variable because the application knows it as self.master.
Here is an example, not of the whole program but the parts I have mentioned:
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.master.title("File Converter")
self.master.minsize(1000,600)
self.master.maxsize(1200,800)
self.pack(expand=True, fill='both') # Fill entire root window
self.create_widgets()
def create_widgets(self):
#Drag and drop files area
self.drop_box = tk.Listbox(self, selectmode=tk.SINGLE, background="#99ff99")
self.drop_box.pack(side="left", ipadx=170, ipady=120)
self.drop_box.drop_target_register(DND_FILES)
self.drop_box.dnd_bind("<<Drop>>", open_dropped_file)
#Instructional Text
sentence = "Drag and drop or select your file"
self.instructions = tk.Text(self)
self.instructions.insert(tk.END, sentence)
self.instructions.place(relx=1.0, rely=1.0, anchor="se")
#Select file button
self.select_file = tk.Button(self, text="Select File",
command=self.open_selected_file)
self.select_file.place(relx=1.0, rely=1.0, anchor="se")
This should let you work out most of your problems.
I have this few lines of code which print the selected content in the Listbox when double clicked but i want to display text beside the cursor like double click the selected content to print to prompt the user to take such action before it can be printed.
I search the listbox documentation and found the is an attribute cursor which you set the cursor type to display when the widget has focus but didn't find something like cursor-text to do that.Is the a way i can work around to achieve that, your suggestions are welcomed.
from tkinter import *
def test(event=None):
print("woow test works")
print(l.get(ACTIVE))
root = Tk()
l = Listbox(root, cursor="tcross")
l.pack()
l.insert(END, ("today"),("tomorrow"))
l.bind("<Double-Button-1>", test)
root.mainloop()
I don't quite understand your English, but you can see this page for an example of using tooltips, which make text appear on mouse hover.
Yes, it's possible to add specific info or comments to listbox lines using Hovertip from idlelib.tooltip. In the example below, Mark Lutz's customizable scrolled listbox works normally, except that right-clicking on any line opens a tip with info/comments for that line.
Ref. : Programming Python 4th ed. Example 9-9
from tkinter import *
from idlelib.tooltip import Hovertip
class ScrolledList(Frame):
def __init__(self, options, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
botfrm = Frame(parent)
botfrm.pack(side=BOTTOM)
Label(botfrm,
text="Select a line and right-click for info").pack()
self.makeWidgets(options)
self.myTip = None
def handleList(self, event):
if self.myTip:
self.myTip.__del__()
label = self.lstbx.get(ACTIVE)
self.runCommand(label)
def overflyLine(self, event):
if self.myTip:
self.myTip.__del__()
self.myTip = Hovertip(self.lstbx,f"Comments for {self.lstbx.get(ACTIVE)}")
self.myTip.showtip()
def makeWidgets(self, options):
sbar = Scrollbar(self)
list = Listbox(self, relief=SUNKEN, bg='misty rose')
sbar.config(command=list.yview)
list.config(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT, fill=Y)
list.pack(side=LEFT, expand=YES, fill=BOTH)
for label in options:
list.insert(END, label)
self.lstbx = list
list.bind('<Button-3>', self.overflyLine)
list.bind('<Double-1>', self.handleList)
list.bind('<Return>', self.handleList)
def runCommand(self, selection):
print('You selected:', selection)
if __name__ == '__main__':
options = (('Lumberjack-%s' % x) for x in range(20))
scr = ScrolledList(options).mainloop()
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.
This is my code :
import sys
from tkinter import *
#first new screen
def next_screen(names):
for widget in names:
widget.place_forget()
buttonhyp = Button (text = "button1",fg = "blue",command = hypoténusegetdef())
buttonhyp.grid (row = 1,column = 2)
def forget_page1():
widgets = [mLabel1, button]
next_screen(widgets)
################################################################################
def hypténusegetdef ():
widgets1 = [buttonhyp]
nextscreen1(widgets1)
def next_screen(names):
for widget in names:
widget.place_forget()
hyplabel1 = Label (text = "This is my text")
#first page things
mGui = Tk ()
mGui.geometry("600x600+545+170")
mGui.title("MyMathDictionary")
mLabel1 = Label (text = "Welcome to MyMathDictionary. Press Next to continue.",
fg = "blue",bg = "white")
mLabel1.place (x= 150,y = 200)
button = Button (text = "Next", command = forget_page1 )
button.place(x = 275,y = 230)
mGui.mainloop()
What i'm trying to do is to open the program and get the user to click on "Next" and then to show another button which is called "button1" and when the user clicks on "button1" it shows up a text called which says "This is my text" in my code.But when i run it i click on "Next" and nothing shows up i checked and re- checked but nothing seems to work.Any help would be appreciated.
#
I am not an expert, but i will give it a try.
Firstly, import sys is not necessary. And importing all the objects from tkinter module using from tkinter import* is not recommended. You should use import tkinter or import tkinter as tk to avoid unexepcted consequences.
You have 2 functions with the same name. next_screen(names) which should not happen.
Instead of using widgets = [mLabel1, button] to hide the widgets, you should put them in a frame so that you can use winfo_children() to find all the children widgets.
You should put the parent widget name when you create buttons and labels. for example,
import tkinter as tk
root = tk.Tk()
mylabel = tk.Label(root,text='this is a label')
mylabel.pack()
root.mainloop()
In your first next_screen(names) function , you used grid method to display the button. You should not mix the grid method and place method.
This is something i came up with
import tkinter as tk
def Screen_1():
Clear(myframe)
mybutton2= tk.Button(myframe,text = "Button1", command = Screen_2)
mybutton2.pack()
def Screen_2():
Clear(myframe)
mylabel2= tk.Label(myframe,text = "This is my text",fg = "blue",bg = "white")
mylabel2.pack(side='top')
def Clear(parent):
for widget in parent.winfo_children():
widget.pack_forget()
root =tk.Tk()
myframe=tk.Frame(root)
myframe.pack()
mylabel1= tk.Label(myframe,text = "Welcome to MyMathDictionary. Press Next to continue.",fg = "blue",bg = "white")
mylabel1.pack(side='top')
mybutton1= tk.Button(myframe,text = "Next", command = Screen_1)
mybutton1.pack(side='bottom')
root.mainloop()
hope it helps!