Tkinter Widget Placement - python

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.

Related

How to Refresh a Tab in Tkinter after Interacting with a Database?

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()

tkinter: Why am I getting a small window plus my main window and gridding is off? __init__ problem?

Creates two windows and gridding is not correct. Some additional comments in the code initiation.
I have used this approach, without the super init with no problem, many times.
Advice appreciated.
Thanks
# timhockswender#gmail.com
import tkinter as tk
from tkinter import ttk
class constants_page(tk.Frame):
def __init__(self):
super(constants_page, self).__init__() # from stackoverflow
# if not used error = 'constants_page' object has no attribute 'tk'
# if used, another tiny window is opened
# in addtion to the constants_page
self.constants_page = tk.Tk()
self.constants_page.geometry("1000x500") #width*Length
self.constants_page.title("Owen's Unit Conversion App")
self.constants_page.configure(background='light blue')
self.CreateWidgets()
def CreateWidgets(self):
self.value_label = ttk.Label(self.constants_page,text="Value----->" , width =10 )
self.value_label.grid(row=0, column=1, columnspan=1, sticky='nse')
# Problem: not gridding properly
self.title_label = ttk.Label(self.constants_page, text="Important Physical Constants",
anchor=tk.CENTER, font=("Arial",20)).grid(row=2, columnspan=2)
for r in range(2):
self.constants_page.rowconfigure(r, weight=1, uniform='row')
for c in range(2):
self.constants_page.columnconfigure(c, weight=1 )
def Show_Page():
# Create the entire GUI program
program = constants_page()
program.mainloop()
if __name__ == "__main__":
Show_Page()
The super call expects you to provide a root window (an instance of tk.Tk()). If you don't provide one it defaults to the first root window opened, and if none has been opened yet then it helpfully opens one for you. A few lines later you open a second one yourself.
The easy fix is to remove the self.constants_page = tk.Tk() line. The proper fix is to make the Tk() instance outside of the class and pass it in. This allows you to use the Frame class itself to lay out widgets (use self instead of self.constants_page). Try this:
import tkinter as tk
from tkinter import ttk
class constants_page(tk.Frame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
master.geometry("1000x500") #width*Length
master.title("Owen's Unit Conversion App")
self.configure(background='light blue')
self.CreateWidgets()
def CreateWidgets(self):
self.value_label = ttk.Label(self,text="Value----->" , width =10 )
self.value_label.grid(row=0, column=1, columnspan=1, sticky='nse')
self.title_label = ttk.Label(self, text="Important Physical Constants",
anchor=tk.CENTER, font=("Arial",20)).grid(row=2, columnspan=2)
for r in range(2):
self.rowconfigure(r, weight=1, uniform='row')
for c in range(2):
self.columnconfigure(c, weight=1 )
def Show_Page():
# Create the entire GUI program
program = tk.Tk()
win = constants_page(program)
win.pack()
program.mainloop()
if __name__ == "__main__":
Show_Page()

Way to avoid having multiple windows when using Messagebox (or alternative to it)?

I'm trying to write my first GUI based python program using Tkinter. I have created a base window which holds the menu bar with a couple of options.
One of the options is for a standard 'About' box. When I call the about section with
helpMainMenu.add_command(label="About", command=self.aboutProgram)
It opens the message box, but in a fresh window so there are now two windows showing on the taskbar.
Is there any way to stop it opening a new window and use the main one instead, or is there a better way to do it?
The full code is below
#! /usr/bin/python3
from tkinter import *
from tkinter import messagebox
import datetime
timeNow = datetime.datetime.now()
writeYear = 2020 # Enter the year you started writing the program
lineFeed = "\n"
programTitle = "Basic Menu"
programVersion = "Version 1.0.0"
programmerName = " Name (email#gmail.com)"
if timeNow.year > writeYear:
programAuthor = "©" + str(writeYear) + "-" + str(timeNow.year) + programmerName
else:
programAuthor = "©" + str(writeYear) + programmerName
aboutMessage = programTitle + lineFeed + programVersion + lineFeed + programAuthor
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("{} ({})".format(programTitle, programVersion))
self.pack(fill=BOTH, expand=1)
menu = Menu(self.master)
self.master.config(menu=menu)
fileMainMenu = Menu(menu, tearoff=0) #Create the File menu container
fileMainMenu.add_command(label="Exit", command=self.programExit) # File menu option
menu.add_cascade(label="File", menu=fileMainMenu)
helpMainMenu = Menu(menu, tearoff=0) #Create the Help menu container
helpMainMenu.add_command(label="About", command=self.aboutProgram)
menu.add_cascade(label="Help", menu=helpMainMenu)
def programExit(self):
exitMsgBox = messagebox.askquestion ("Exit Application","Are you sure you want to exit the application",icon = "warning")
if exitMsgBox == "yes":
root.destroy()
exit()
def aboutProgram(self):
messagebox.showinfo("About","About the application", icon = "info")
root = Tk() # root window created. Here, that would be the only window, but
windowHeight = int(root.winfo_screenheight()/100*75) # Set the main window height to 75% of the screen height
windowWidth = int(root.winfo_screenwidth()/100*75) # Set the main window width to 75% of the screen width
screenWidth = int(root.winfo_screenwidth())
screenHeight = int(root.winfo_screenheight())
positionRight = int(root.winfo_screenwidth()/2 - windowWidth/2) # Get the screen width and divide by 2, then minus the result of 'windowWidth' divided by 2
positionDown = int(root.winfo_screenheight()/2 - windowHeight/2) # Get the screen height and divide by 2, then minus the result of 'windowHeight' divided by 2
root.geometry("{}x{}+{}+{}".format(windowWidth, windowHeight, positionRight, positionDown)) # Positions the window in the center of the page.
app = Window(root)
root.mainloop()
Python Version 3.7.3
tkinter.TkVersion 8.6
The simplest way would be to create a new frame for the "about" page, and then overlay it on top of the main window with place -- one of the few times when place is superior to grid and pack.
You should also do a "grab" on the frame so that all events are funneled to the frame and its children. With a grab, while the popup is visible you can't interact with the widgets on the main window.
Here's a quick example:
def aboutProgram(self):
# create the frame with a message and a button
# which destroys the window
aboutFrame = Frame(self.master, bd=2, relief="groove")
label = Label(aboutFrame, text="About the application...")
button = Button(aboutFrame, text="Ok", command=aboutFrame.destroy)
label.pack(side="top", padx=20, pady=20)
button.pack(side="bottom", pady=20)
# overlay the "about" page on top of the root window
aboutFrame.place(relx=.5, rely=.5, anchor="c")
# force all events to go to the popup
aboutFrame.grab_set()
If you want to completely hide the contents of the main window, you can change the place arguments to fill the window:
aboutFrame.place(x=0, y=0, anchor="nw", relwidth=1.0, relheight=1.0)

How to display text beside cursor in tkinter Listbox

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()

Simple python program to read . gcode file

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.

Categories

Resources