Positioning different sets of entries next to each other in python tkinter - python-3.x

I use a Tkinter frame in python, and on one of the pages I need to display 2 sets of entries, one next to the other, where the number of entries is equal to the number in range (in the full program is a changing variable, so each time the number of entries changes). I use the for loop to do it.
However, when I try to pack the entries into the frame, the 2 sets of 3 entries are shown in one single column of 6 entries, instead of showing 2 columns with 3 rows of entries each.
If I adjust the packs to the left and right sides of the frame, each set of entries then is shown in 1 row, and has 3 columns, which is not needed.
When I use .place or .grid instead of .pack, then for each set only one single entry is shown (I guess all 3 entries are just placed in a single defined location ex. (x = 550, y = 80), so that 3 entries "overlap" into one)
I guess I need to write a more sophisticated "for loop" function and use .grid or .place positioning, so that all 3 entries will be displayed in a column one after the other.
Or I'm also thinking that using .pack and inserting the entries into a new frame inside the first frame, and then position these 2 frames one next to another might work. But again, I tried to create an extra frame inside the first page, and it didn't work.
Any observations and tips would be highly appreciated!
Here is the full code, so you might try playing around with it and see the whole picture. (sorry for a mess in imports, I also have to sort it out)
P.S. It is a part of a bigger code, where I need to use more then 1 page, so this code is the smallest that works - if I there would be only a single frame in the program, I would have no problem arranging the entries as I need. The problem is that I don't know how to arrange the entries is this particular structure.
import tkinter as tk
from tkinter import font as tkfont
import traceback
from tkinter import messagebox
from pandastable.core import Table
from pandastable.data import TableModel
from tkinter import *
from tkinter import ttk
import tkinter as Tkinter
import tkinter.ttk as ttk
LARGE_FONT= ("Verdana", 12)
class MyTable(Table):
"""
Custom table class inherits from Table.
You can then override required methods
"""
def __init__(self, parent=None, **kwargs):
Table.__init__(self, parent, **kwargs)
return
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
self.geometry('800x600+200+100')
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button2.place(x = 20, y = 50)
entries = [Entry(self, font =('Calibri', 7 )) for _ in range(3)]
for entry in entries:
#entry.place(x = 400, y = 80)
entry.pack()
entries_date = [Entry(self, font =('Calibri', 7 )) for _ in range(3)]
for entry in entries_date:
#entry.place(x = 550, y = 80)
entry.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()

If you're trying to create rows and columns, there are two generally accepted ways to do it:
make each row a frame, and pack the entries along a side
use grid to create rows and columns.
The first is best if your columns don't need to line up, the second is best if your columns do need to line up. Though, you also have to take into consideration what else you're putting in the frame -- you can't use both grid and pack on widgets that have the same master.
Note: If you're creating identical widgets in each row, then as a side effect the columns will line up even when you use pack.
Example using pack:
entries = []
entry_frame = Frame(self)
entry_frame.pack(side="top", fill="x")
for column in range(3):
entry = Entry(entry_frame, font=('Calibri', 7))
entry.pack(side="left", fill="both", expand=True)
entries.append(entry)
entries_date = []
entry_date_frame = Frame(self)
entry_date_frame.pack(side="top", fill="x")
for column in range(3):
entry = Entry(entry_date_frame, font=('Calibri', 7))
entry.pack(side="left", fill="both", expand=True)
entries_date.append(entry)
Example using grid:
entries = []
for column in range(3):
entry = Entry(self, font=('Calibri', 7))
entry.grid(row=0, column=column, sticky="ew")
entries.append(entry)
entries_date = []
for column in range(3):
entry = Entry(self, font=('Calibri', 7))
entry.grid(row=1, column=column, sticky="ew")
entries_date.append(entry)

Related

TKInter - Confused about frames and scrolling

I am trying to code a tkinter application that has three frames - a top frame, where the user inputs some text, a dynamically constructed middle section where some pre-analysis is conducted on the text, and a bottom frame where, once the user has selected which option they want in the middle section, the output will be produced.
The problem is that, depending upon the input, there could be around 10-20 (and in the worst case 30) lines displayed and on a small monitor the output will disappear off the screen.
What I would like is for the top (input) and bottom (output) frames to be visible no matter how the screen is re-sized, and for the middle section to scroll (if required) and still allow the user to select their choice.
I am confused as to how to get the middle section to resize when the screen is resized, show a scrollbar if required, and still allow all of the content to be accessed.
I have created a cut-down version here (for simplicity, I have removed the processing methods and have instead created some fake output in a loop that resembles what the actual middle section would look like).
Please ignore the hideous colour-scheme - I was just trying to understand which frame went where (I will remove the colours as soon as I can!)
Thank you for any suggestions...
import tkinter as tk
from tkinter import scrolledtext
class MyApp(tk.Tk):
def __init__(self, title="Sample App", *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title(title)
self.configure(background="Gray")
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
# Create the overall frame:
master_frame = tk.Frame(self, bg="Light Blue", bd=3, relief=tk.RIDGE)
master_frame.grid(sticky=tk.NSEW)
master_frame.rowconfigure([0, 2], minsize=90) # Set min size for top and bottom
master_frame.rowconfigure(1, weight=1) # Row 1 should adjust to window size
master_frame.columnconfigure(0, weight=1) # Column 0 should adjust to window size
# Create the frame to hold the input field and action button:
input_frame = tk.LabelFrame(master_frame, text="Input Section", bg="Green", bd=2, relief=tk.GROOVE)
input_frame.grid(row=0, column=0, padx = 5, pady = 5, sticky=tk.NSEW)
input_frame.columnconfigure(0, weight=1)
input_frame.rowconfigure(0, weight=1)
# Create a frame for the middle (processing) section.
middle_frame = tk.LabelFrame(master_frame, text = "Processing Section")
middle_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)
# Create the frame to hold the output:
output_frame = tk.LabelFrame(master_frame, text="Output Section", bg="Blue", bd=2, relief=tk.GROOVE)
output_frame.grid(row=2, column=0, columnspan=3, padx=5, pady=5, sticky=tk.NSEW)
output_frame.columnconfigure(0, weight=1)
output_frame.rowconfigure(0, weight=1)
# Add a canvas in the middle frame.
self.canvas = tk.Canvas(middle_frame, bg="Yellow")
self.canvas.grid(row=0, column=0)
# Create a vertical scrollbar linked to the canvas.
vsbar = tk.Scrollbar(middle_frame, orient=tk.VERTICAL, command=self.canvas.yview)
vsbar.grid(row=0, column=1, sticky=tk.NS)
self.canvas.configure(yscrollcommand=vsbar.set)
# Content for the input frame, (one label, one input box and one button).
tk.Label(input_frame,
text="Please type, or paste, the text to be analysed into this box:").grid(row=0, columnspan = 3, sticky=tk.NSEW)
self.input_box = scrolledtext.ScrolledText(input_frame, height=5, wrap=tk.WORD)
self.input_box.columnconfigure(0, weight=1)
self.input_box.grid(row=1, column=0, columnspan = 3, sticky=tk.NSEW)
tk.Button(input_frame,
text="Do it!",
command=self.draw_choices).grid(row=2, column=2, sticky=tk.E)
# Content for the output frame, (one text box only).
self.output_box = scrolledtext.ScrolledText(output_frame, width=40, height=5, wrap=tk.WORD)
self.output_box.grid(row=0, column=0, columnspan=3, sticky=tk.NSEW)
def draw_choices(self):
""" This method will dynamically create the content for the middle frame"""
self.option = tk.IntVar() # Variable used to hold user's choice
self.get_input_text()
for i in range(30):
tk.Radiobutton(self.canvas,
text=f"Option {i + 1}: ", variable=self.option,
value=i,
command=self.do_analysis
).grid(row=i, column=0, sticky=tk.W)
tk.Label(self.canvas,
text=f"If you pick Option {i + 1}, the output will look like this: {self.shortText}.",
anchor=tk.W
).grid(row=i, column=1, sticky=tk.W)
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def get_input_text(self):
""" Will get the text from the input box and also create a shortened version to display on one line"""
screenWidth = 78
self.input_text = self.input_box.get(0.0, tk.END)
if len(self.input_text) > screenWidth:
self.shortText = self.input_text[:screenWidth]
else:
self.shortText = self.input_text[:]
self.shortText = self.shortText.replace('\n', ' ') # strip out carriage returns just in case
def do_analysis(self):
"""This will ultimately process and display the results"""
option = self.option.get() # Get option from radio button press
output_txt = f"You picked option {option + 1} and here is the output: \n{self.input_text}"
self.output_box.delete(0.0, tk.END)
self.output_box.insert(0.0, output_txt)
if __name__ == "__main__":
app = MyApp("My Simple Text Analysis Program")
app.mainloop()
I understand that you can't mix grid and pack geometries in the same container, and that a scrollbar must be attached to a canvas, and objects to be placed on that canvas must therefore be in yet another container so, attempting to follow Bryan's example, I created a minimal version of what I want - window with three sections - top, middle and bottom. The Top and bottom sections will contain a simple text field, the middle section will contain dynamic content and must be able to scroll as required.
Imports:
ScrollbarFrame
Extends class tk.Frame to support a scrollable Frame]
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("A simple GUI")
# Top frame
self.top_frame = tk.Frame(self, bg="LIGHT GREEN")
self.top_frame.pack(fill=tk.X)
tk.Label(self.top_frame, bg=self.top_frame.cget('bg'),
text="This is a label on the top frame")\
.grid(row=0, columnspan=3, sticky=tk.NSEW)
# Middle Frame
# Import from https://stackoverflow.com/a/62446457/7414759
# and don't change anything
sbf = ScrollbarFrame(self, bg="LIGHT BLUE")
sbf.pack(fill=tk.X, expand=True)
# self.middle_frame = tk.Frame(self, bg="LIGHT BLUE")
self.middle_frame = sbf.scrolled_frame
# Force scrolling by adding multiple Label
for _ in range(25):
tk.Label(self.middle_frame, bg=self.middle_frame.cget('bg'),
text="This is a label on the dynamic (middle) section")\
.grid()
# Bottom Frame
self.bottom_frame = tk.Frame(self, bg="WHITE")
self.bottom_frame.pack(fill=tk.X)
tk.Label(self.bottom_frame, bg=self.bottom_frame.cget('bg'),
text="This is a label on the bottom section")\
.grid(row=0, columnspan=3, sticky=tk.NSEW)
if __name__ == '__main__':
App().mainloop()

A Single Tkinter Window Which Moves Through Frames, Bringing Each Frame to the Foreground. One Frame needs to be split into two Sub-frames

The need for two different geometric plotters arises because i want to display a sophisticated GUI with text, entry fields, pictures, buttons, alongside an animated matplotlib graph on a FigureCanvasTkAgg with a NavigationToolbar2Tk. The NavigationToolbar2Tk fails to work when any geometric plotter other than pack() is used. I have tried over the course of the week several different methods of putting the NavigationToolbar2Tk into its own frame, but in the examples, only a single frame exists. I came to the conclusion that ideally, splitting my frame ~ PageOne ~ into two subframes or instantiating two frames which make up page one.
I have several frames as shown in my code below and my knowledge of python is rudimentary so i don't have the imagination or know how to circumvent this problem. I have gutted my code to show the problem concisely and Included my Imports.
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
from matplotlib.pylab import *
from matplotlib import style
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import matplotlib.ticker as mticker
import tkinter as tk
from tkinter import ttk
from tkinter import *
from PIL import Image, ImageTk
from mpl_toolkits.axes_grid1 import host_subplot
from gpiozero import CPUTemperature
import time
import datetime as dt
import pandas as pd
import numpy as np
#************************************************************************#
# Format Graph_1 [ax1] onto fig1 at subplot [ row = 1, col = 1, index = 1 ]
# Set Figure Text
font = {'size' : 9}
matplotlib.rc('font', **font)
# Setup Figure
fig1 = Figure()
# Define Axis 1 for Vin and Vout
ax1 = fig1.add_subplot(1, 1, 1)
#subplots_adjust(left=0.05, bottom=0.10, right=0.55, top=0.8, wspace=0.2)
ax1.minorticks_on()
ax1.grid(b=True, which='major', color='k', linestyle='-')
ax1.grid(b=True, which='minor', color='k', linestyle=':')
ax1.set_title("PI3740 Paramaters", fontsize = 12)
ax1.set_xlabel("Relative Time (s)", fontsize = 10)
ax1.set_ylabel("Voltage (V)", fontsize =10)
# Define Axis 2 for Iout Which is tied to Axis 1's X-Axis
ax2 = ax1.twinx()
ax2.set_ylabel("Output Current (A)")
# Parameters
x_len = 500 # Resolution [Number of Points in Window]
x_max = 2 # X-Axis Range [ (Step)ms Samp^-1 * (x_len)Samp = X_Range]
y_range = [0, 50] # Range of possible Y values to display
# Create figure for plotting
steps = (x_max/x_len)
stepms = steps * 1000
xs = np.arange(0, x_max, steps) # xs is a list from 0 to 10 in steps of 0.01 [A list refers to a 1D Array]
ys1 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Vin
ys2 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Vout
ys3 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Iout
ax1.set_ylim(y_range) # Y-Axis Voltage Range Set
ax2.set_ylim(0, 10) # Y-Axis Current Range Set
ax1.set_xlim(0, x_max) # X-Axis Shared Relative Time Range Set
# Create a blank line. We will update the line in animate
line1, = ax1.plot(xs, ys1, 'b-', label = "Vin")
line2, = ax1.plot(xs, ys2, 'g-', label = "Vout")
line3, = ax2.plot(xs, ys3, 'r-', label = "Iout")
# Create a Legend
ax1.legend([line1, line2],[line1.get_label(), line2.get_label()])
ax1.legend(bbox_to_anchor = (0.,0.99,1.,.102), loc = 3, ncol = 2, borderaxespad = 0., frameon = False)
ax2.legend([line3],[line3.get_label()])
ax2.legend(bbox_to_anchor = (1.00,0.99), loc = 'lower right', borderaxespad = 0., frameon = False)
#************************************************************************#
#**********************Animation Function********************************#
# This function is called periodically from FuncAnimation
def updateData(self):
# Drop down menu event flags
global ChartLoad
# Graph variables
global xs
global ys1
global ys2
global ys3
if ChartLoad == True:
# Read temperature (Celsius) from TMP102
temp_c = cpu.temperature
temp_c1 = temp_c + 5.0
temp_c2 = temp_c - 35.0
# Add y to list
ys1.append(temp_c)
ys2.append(temp_c1)
ys3.append(temp_c2)
# Limit y list to set number of items
ys1 = ys1[-x_len:]
ys2 = ys2[-x_len:]
ys3 = ys3[-x_len:]
# Update line with new Y values
line1.set_ydata(ys1)
line2.set_ydata(ys2)
line3.set_ydata(ys3)
return line1, line2, line3,
#************************************************************************#
#*******************Tkinter Window Initalization*************************#
class MyApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self,"EMEA Vicor Charging Application ")
img=tk.PhotoImage(file='/home/pi/Pictures/Vicor_Icon1.png')
self.tk.call('wm','iconphoto',self._w,img)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
#*********************************************************
#******Function Required to Display Seperate Pages********
self.frames = {}
for F in (StartPage, HomePage, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
#*********************************************************
#*********************************************************
#Start Page - Agreement
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#Page One - Primary Terminal
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Frame.__init__(self, parent, bg = "white")
# Two Frames Need to be Established on Page One (Which is a “Higher Order Frame”)
my_window = Tk()
frame_name = Frame(my_window)
frame_addr = Frame(my_window)
label_f = Label(frame_name, text = "Check")
label_f.grid(row = 0, column = 0)
label_g = Label(frame_addr, text = "Correct")
label_g.grid(row = 0, column = 0)
frame_name.grid(row = 0, column = 0)
frame_addr.grid(row = 0, column = 1)
#my_window.mainloop()
app = MyApp()
app.geometry("1010x700")
ani = animation.FuncAnimation(fig1, updateData, blit = True, interval = stepms)
app.mainloop()
Page One will contain my Canvas for the Graph and Tool Bar. however to simplify the problem into its fundamental, getting two frames to make up "Higher Order" frame PageOne, with each subframe containing a label. When I run the code, another window opens with the two labels displayed. This is more progress than from other solutions that i can't implement/don't understand, producing error messages i can't respond to. I am self taught at python, and have been following tutorials from the community. I just need some help implementing the solution. If I've gutted my code to much i can provide a more comprehensive code snippet. But the essence of the problem is, while displaying one of several frames on a Tkinter Window which move to the foreground when requested, how do you split one of these "High Order" frames into two frames as to allow two different geometric plotters to be used to structure each one.
Resources Used So Far:
https://www.youtube.com/watch?v=Mxk4cMBaH3g&list=PL6lxxT7IdTxGoHfouzEK-dFcwr_QClME_&index=37&t=0s
[Most Recent Attempt - Trying to Simplify the problem]
how to make two split up screen ( canvas) inside the python tkinter window
http://www.openbookproject.net/courses/python4fun/tkphone1.html
matplotlib Navigation Bar error 'FigureCanvasTkAgg' object has no attribute 'manager' in tkinter
Displaying Matplotlib Navigation Toolbar in Tkinter via grid
[Hit the Same Problem - One Master Frame Split Into Two Sub Frames]
[This Link is probably has the answer in it and if that is the case, apologies for repeating a question already asked on stack overflow)
EDIT. To Expand Further for Clarity take the code below:
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
#import Tkinter as tk # python 2
#import tkFont as tkfont # python 2
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
how would i get PageTwo to compose of two different frames so i can use two different geometric plotters (pack() and grid()).

Is there a way to grid a tkinter element that is in a different class from the frame that i want to grid it on to

I am writing a D&D simulator using tkinter as a gui, however because I want to make an array where each index corresponds to the row that the element is on, for the option Menu's I have created my own class that includes the information that they need such as the list of options and the stringVar. However when I run the program it does not grid the option menu onto the frame even though the object has been created.
Full code if you need it: https://pastebin.com/2FxBcUb2
main class that is run:
class dndSimulator(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default="Ampersand_on_Black (1).ICO")
tk.Tk.wm_title(self, "D&D simulator")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartGame,MainMenu):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartGame)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
My frame (the part where it calls the dropdown box class):
def addCharactersection(self, firstColumn):
teamid = (firstColumn//4)
self.removeDelete(firstColumn)
self.placeholders = self.moveelements(firstColumn, 1, len(self.teamUiItems[teamid])-1)
self.placeholders = self.placeholders[0]
self.charactersection = []
self.charactersection.append(dropdownMenu("",lstoffiles))
self.charactersection.append(tk.Entry(self))
self.charactersection.append(ttk.Button(self, text = "Commit", command = lambda: self.commitValues(firstColumn)))
self.teamUiItems[teamid].insert(self.placeholders, self.charactersection)
self.teamUiItems[teamid][self.placeholders][0].dropdowngrid(self.placeholders+2, 0,1)
self.teamUiItems[teamid][self.placeholders][1].grid(row = self.placeholders+2,column = 1, columnspan = 1)
self.teamUiItems[teamid][self.placeholders][2].grid(row = self.placeholders+2, column = 2, columnspan = 1)
#grid these
self.addDelete(firstColumn)
print(self.teamUiItems[teamid][self.placeholders])
and the dropdown menu class:
class dropdownMenu(tk.Frame):
'''
create a dropdown menu where this class includes all the vars and stuff
'''
def __init__(self,default,list):
super(dropdownMenu, self).__init__()
self.default = default
self.list = list
self.menuvar = tk.StringVar()
self.menuvar.set(default)
self.menu = tk.OptionMenu(self,self.menuvar, *self.list)
def dropdowngrid(self,prow,pcolumn,pcolumnspan):
self.menu.grid(row = prow, column = pcolumn, columnspan = pcolumnspan)
def dropdownforget(self):
self.menu.grid_forget()
def dropdowninfo(self):
return {'row':self.menu.grid_info()['row'], 'column':self.menu.grid_info()['column'], 'columnspan':self.menu.grid_info()['columnspan']}
I expect it to grid onto the frame that is shown in the correct position however It does not appear even though the rest of the elements that stay within the frames class do appear.
Thank you if you can help because I have been trying to fix this for the past hour or so!
EDIT: after testing code I can confirm you forget to use .pack() to show frame. (First I tried with .grid() but your frame is in window which uses .pack())
You create your frame dropdownMenu
self.charactersection.append(dropdownMenu("",lstoffiles))
and you use
self.teamUiItems[teamid][self.placeholders][0].dropdowngrid(self.placeholders+2, 0,1)
which runs .grid() for OptionMenu
def dropdowngrid(self, ...):
self.menu.grid(...)
but optionmenu self.menu is inside frame self and you forgot self.pack()
After I add this I can see it in window.
def dropdowngrid(self, ...):
self.menu.grid(...)
self.pack()

Python frames tkinter

Im having troubles with tkinter frames
The folowing code must display labels at left side and there should be more space the button and the label , there is something wrong with my column/row setup. What am i doing wrong?
What is the correct way for a program to display information? 1 global frame with several smaller frames in it? With tkinter when using a menu with page 1 page 2 and page 3 ,
page 1 has 3 input fields , child of FramePage1 , page 2 has 2 buttons child of FramePage2, page 3 has one big text field child of FramePage3. Is it the correct way to use for changing the pages
#menu tab1 -> command #calls function page1
def page1():
self.Framepage2.grid_forget()
self.Framepage1.grid()
#content of the page
or are there other ways to use different layout style pages?
import tkinter
import tkinter as tk
class sjabloon():
def __init__(self):
#make window
self.win = tk.Tk()
self.win.geometry("600x600+10+10")
#make top frame
self.frame_header = tk.Frame(self.win, background='black', width=600, height=50)
self.frame_header.grid(column=0, row=0 , columnspan= 10)
#make body frame
self.frame_body = tk.Frame(self.win, width=600, height=400)
self.frame_body.grid(column=0, row=1 , columnspan= 10)
#button1 select
tk.Label(self.frame_body, text="Select:").grid(column=0, row=2, stick='W')
self.button1 = tk.Button(self.frame_body, text="Select")
self.button1.grid(row=2, column=5, stick='W')
#button1 select
tk.Label(self.frame_body, text="Select:").grid(column=0, row=3, stick='W')
self.button2 = tk.Button(self.frame_body, text="Select")
self.button2.grid(row=4, column=5, stick='W')
#button submit
self.submit = tk.Button(self.frame_body, text="Start")
self.submit.grid(row=10, column=9, stick='W')
#make body footer
self.frame_footer = tk.Frame(self.win, background='yellow', width=600, height=50)
self.frame_footer.grid(column=0, row=3 , columnspan= 10)
if __name__ == "__main__":
sjabloon = sjabloon()
I suggest you to follow this tkinter GUI tutorial, he makes a pretty big app and even if it's not what you exactly looking for, it will help you.
In the part 4, he make a multiple frame architecture in the tkinter GUI.
For switching "pages", i know 2 choices (there's more i think but i don't know them, i'm still a beginner). You can create all the frames inside a window/Frame and raise to the front the one you want (that's in the tutorial part 4) or you can destroy the widgets "Page 1" inside the body frame and create the widgets "Page 2" inside it (obviously in methods/functions to let you switch between the pages).
For your first problem, i'm not sure if i understand your problem, you want more space around your button widget ? if that's what you want, you can use the option padx=(leftPadx,RightPadx) like that :
self.button1.grid(row=2, column=5, stick='W', padx=(50,0))
EDIT : i made a little architecture for you (from what i learn in that tutorial)
Basically, you create all the "Page", you add them in the bodyFrame and you raise to the front the one you want. To achieve that, for each "Page", you create a class that inherits tk.Frame and you add an instance of that class in the mainWindow
import tkinter as tk
from tkinter import ttk
LARGE_FONT = ("Verdana 12")
NORM_FONT = "Verdana 10"
SMALL_FONT = ("Verdana 8")
ERROR_404 = "Error 404 : Page not found !"
class sjabloon(tk.Tk):
def __init__(self, *args, **kwargs):
#make window
tk.Tk.__init__(self, *args, **kwargs)
self.geometry("600x600+10+10")
#make top frame
self.frame_header = tk.Frame(self, background='black', width=600, height=50)
self.frame_header.grid(column=0, row=0 , columnspan= 10)
#make body frame
container = tk.Frame(self, width=600, height=400)
container.grid(column=0, row=1 , columnspan= 10)
#list of Pages
self.frames = {}
#everytime you create a "Page", you add it there
for F in (StartPage, HomePage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=1, column = 0, sticky="nsew", columnspan= 10)
self.show_page("StartPage")
#make body footer
self.frame_footer = tk.Frame(self, background='yellow', width=600, height=50)
self.frame_footer.grid(column=0, row=3 , columnspan= 10)
def show_page(self, page_name):
"""
let us use the NAME of the class to display(the function show_frame
use directly the class).
when we use the classe name, we can put our classes in defferent
files
"""
for F in self.frames:
if F.__name__ == page_name:
self.show_frame(F)
return
print(ERROR_404)
def show_frame(self, cont):
"""raise to the front the frame we want
:param cont: the frame
"""
frame = self.frames[cont]
frame.tkraise()
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#button1 select
tk.Label(self, text="Select:").grid(column=0, row=2, stick='W')
self.button1 = tk.Button(self, text="Select")
self.button1.grid(row=2, column=5, stick='W', padx=(50,0))
#button1 select
tk.Label(self, text="Select:").grid(column=0, row=3, stick='W')
self.button2 = tk.Button(self, text="Select")
self.button2.grid(row=4, column=5, stick='W', padx=(50,0))
#button submit
self.submit = tk.Button(self, text="Start")
self.submit.grid(row=10, column=9, stick='W')
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="""ALPHA application.
use at your own risk. There is no promise
of warranty""", font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = ttk.Button(self, text="Agree",
command=lambda: controller.show_page("HomePage"))
button1.pack()
button2 = ttk.Button(self, text="Disagree",
command=controller.destroy)
button2.pack()
if __name__ == "__main__":
sjabloon = sjabloon()
sjabloon.mainloop()

How to display a dataframe in tkinter

I am new to Python and even newer to tkinter.
I've utilised code from stackoverflow (Switch between two frames in tkinter) to produce a program where new frames are called and placed on top of each other depending on what options the user selects. A stripped down version of my code is below. There are a lot more frames.
import tkinter as tk
from tkinter import font as tkfont
import pandas as pd
class My_GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, Page_2):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="Welcome to....", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Option selected",
command=lambda: controller.show_frame("Page_2"))
button1.pack()
class Page_2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="The payment options are displayed below", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
#I want the able to be display the dataframe here
button = tk.Button(self, text="Restart",
command=lambda: controller.show_frame("StartPage"))
button.pack()
a = {'Option_1':[150,82.50,150,157.50,78.75],
'Option2':[245,134.75,245,257.25,128.63]}
df = pd.DataFrame(a,index=['a',
'b',
'c',
'd',
'e'])
print(df.iloc[:6,1:2])
if __name__ == "__main__":
app = My_GUI()
app.mainloop()
When Page_2 appears I want it to display a dataframe with the code below.
a = {'Option_1':[150,82.50,150,157.50,78.75],
'Option2':[245,134.75,245,257.25,128.63]}
df = pd.DataFrame(a,index=['a',
'b',
'c',
'd',
'e'])
print(df.iloc[:6,1:2])
I've searched SO e.g. How to display a pandas dataframe in a tkinter window (tk frame to be precise) (no answer provided) and other websites for an answer to similar question but without success.
How and where would I place my dataframe code selection to appear in the area I want when I select Page_2?
Check out pandastable.
It is quite a fancy library for displaying and working with pandas tables.
Here is a code example from their documentation:
from tkinter import *
from pandastable import Table, TableModel
class TestApp(Frame):
"""Basic test frame for the table"""
def __init__(self, parent=None):
self.parent = parent
Frame.__init__(self)
self.main = self.master
self.main.geometry('600x400+200+100')
self.main.title('Table app')
f = Frame(self.main)
f.pack(fill=BOTH,expand=1)
df = TableModel.getSampleData()
self.table = pt = Table(f, dataframe=df,
showtoolbar=True, showstatusbar=True)
pt.show()
return
app = TestApp()
#launch the app
app.mainloop()
and here a screenshot (also from their docs):
As a start, you could have a look at Label and Text widgets, that usually are used to display text in your GUI.
You could probably try something like:
class Page_2(tk.Frame):
def __init__(self, parent, controller):
# ... your code ...
global df # quick and dirty way to access `df`, think about making it an attribute or creating a function that returns it
text = tk.Text(self)
text.insert(tk.END, str(df.iloc[:6,1:2]))
text.pack()
# lbl = tk.Label(self, text=str(df.iloc[:6,1:2])) # other option
# lbl.pack() #
In the end, it really boils down to how fancy you want to be: the widgets are highly customizable, so you could achieve something very pleasing to the eye instead of the basic look of this example.
Edit:
I added a Combobox widget to select the option to display and a Button that prints it to the "display" widget of your choice.
from tkinter import ttk # necessary for the Combobox widget
# ... your code ...
class Page_2(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="The payment options are displayed below", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
global df
tk.Label(self, text='Select option:').pack()
self.options = ttk.Combobox(self, values=list(df.columns))
self.options.pack()
tk.Button(self, text='Show option', command=self.show_option).pack()
self.text = tk.Text(self)
self.text.pack()
tk.Button(self, text="Restart",
command=lambda: controller.show_frame("StartPage")).pack()
def show_option(self):
identifier = self.options.get() # get option
self.text.delete(1.0, tk.END) # empty widget to print new text
self.text.insert(tk.END, str(df[identifier]))
The text that is displayed is the default string representation of a data-frame's column; a custom text is left as an exercise.

Resources