Tkinter add widget on scroolable frame last item does not shown - python

I want to create a window that allows entering one-to-many fields for the file transfer.
I created a Scrollable Frame and I am adding Entry-Text pairs in runtime. If I click the button for the first time, everything goes well. After the second time, nothing happens on the UI side. It works perfectly after the second click. But I saw that all pairs added successfully, just the UI did not display it. Does anybody know how to fix it?
import tkinter as tk
class VerticalScroolFrame(tk.Frame):
"""A frame with a vertical scroolbar"""
def __init__(self, master, *args, **kwargs):
main_frame = tk.Frame(master)
main_frame.grid()
main_frame.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
self._canvas = tk.Canvas(main_frame)
self._canvas.grid(row=0, column=0, sticky=tk.NSEW)
scrollbar = tk.Scrollbar(main_frame, command=self._canvas.yview)
scrollbar.grid(row=0, column=1, sticky=tk.N+tk.S+tk.W)
self._canvas.configure(yscrollcommand=scrollbar.set)
self._canvas.bind('<Configure>', lambda *_: self.on_configure())
super().__init__(self._canvas, *args, **kwargs)
self._canvas.create_window((0, 0), window=self, anchor=tk.NW)
def on_configure(self):
"""update scrollregion after starting 'mainloop'
when all widgets are in self._canvas. And also need to be triggered
whenever a widget added as a child.
"""
self._canvas.configure(scrollregion=self._canvas.bbox('all'))
class AdvancedTransfer:
"""Opens a window that allows you to enter source file list
and targets for them. One-to-many relation.
"""
def __init__(self, root):
self._scroolable_frame = VerticalScroolFrame(root)
self._entry_text_dict = {}
self._button = tk.Button(root, text="Add", command=self.add_item)
self._button.grid()
def add_item(self):
"""Add entry-text widget group"""
row = len(self._entry_text_dict)
entry = tk.Entry(self._scroolable_frame)
entry.insert(0, "row number: {0}".format(row))
entry.grid(row=row, column=0)
text = tk.Text(self._scroolable_frame)
text.grid(row=row, column=1)
self._entry_text_dict[entry] = text
self._scroolable_frame.on_configure()
def main():
root = tk.Tk()
main_frame = tk.Frame(root)
main_frame.grid()
AdvancedTransfer(main_frame)
root.mainloop()
if __name__ == "__main__":
main()
Repro steps:
Run the code below.
Click the button two times.
You should see 2 pairs but only 1 pair shown instead.

It is because you bind <Configure> event on wrong widget. You should bind on the internal frame (i.e. instance of VerticalScroolFrame) instead of canvas (self._canvas):
class VerticalScroolFrame(tk.Frame):
"""A frame with a vertical scroolbar"""
def __init__(self, master, *args, **kwargs):
main_frame = tk.Frame(master)
main_frame.grid()
main_frame.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
self._canvas = tk.Canvas(main_frame)
self._canvas.grid(row=0, column=0, sticky=tk.NSEW)
scrollbar = tk.Scrollbar(main_frame, command=self._canvas.yview)
scrollbar.grid(row=0, column=1, sticky=tk.N+tk.S+tk.W)
self._canvas.configure(yscrollcommand=scrollbar.set)
#self._canvas.bind('<Configure>', lambda *_: self.on_configure())
super().__init__(self._canvas, *args, **kwargs)
self._canvas.create_window((0, 0), window=self, anchor=tk.NW)
# bind <Configure> event on itself
self.bind('<Configure>', lambda _: self.on_configure())

Related

How to switch between different tkinter canvases from a start-up page and return back to start-up page in sub-canvas

I have created a start-up canvas, which contains buttoms to shfit to two other sub-canvases. In addition, in those two sub-canvases, buttom to return to start-up canvas is created. However, after I enter the sub-canvas, I fail to return to start-up canvas. That is to say, when I click the buttom to return to the start-up canvas, it will create a start-up canvas beside the sub-canvas instead of closing the subcanvas and shifting to the start-up canvas. Is there any way to switch between canvases and return to the main canvas? Thank you!
import tkinter as tk
from tkinter import ttk
import re
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._Canvas= None
self.switch_Canvas(StartUpPage)
def switch_Canvas(self, Canvas_class):
new_Canvas = Canvas_class(self)
if self._Canvas is not None:
self._Canvas.destroy()
self._Canvas = new_Canvas
self._Canvas.pack()
class StartUpPage(tk.Canvas):
def __init__(self, master, *args, **kwargs):
tk.Canvas.__init__(self, master, *args, **kwargs)
tk.Frame(self)
tk.Label(self, text="Example").grid(column = 0, row = 0)
tk.Button(self, text="Canvas1",
command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
tk.Button(self, text="Canvas2",
command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)
class PageOne(tk.Canvas, tk.Tk):
def __init__(self, master, *args, **kwargs):
root = tk.Canvas.__init__(self, *args, **kwargs)
self.frame1 = tk.Frame(root, width=430)
self.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
tk.Label(self.frame1, text="First Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self.frame1, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
class PageTwo(tk.Canvas, tk.Tk):
def __init__(self, master, *args, **kwargs):
root = tk.Canvas.__init__(self, *args, **kwargs)
self.frame2 = tk.Frame(root, width=430)
self.frame2.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
tk.Label(self.frame2, text="Second Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self.frame2, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
So, few things:
First: There's the wrong parent/master relationship in PageOne and PageTwo in
def __init__(self, master, *args, **kwargs):
Since you're inheriting from tk.Canvas, the new instance of the class has parent master which in itself get's passed from
SampleApp's switchCanvas as Canvas_class(self) meaning master is . or the (main/root) window.
But then (we're still inside PageOne) you're making root = tk.Canvas.__init__(self, *args, **kwargs) which becomes None since initializers return None.
Then you're creating a new Frame whose parent is root which is None.
In short
PageOne and PageTwo are (also) new instances of Canvas whose parent is the main window, or the instance made by SampleApp.
root aka the Frame's parent is None (Which I even don't know how that affects it when you position it byself.frame1.pack(fill=tk.BOTH, side=tk.LEFT, expand=True))
Hence I assume you're trying to making PageOne and PageTwo instances of Frame and put a Canvas inside them.
These are the few changes I made to the two classes: Their instances are now both of (type) tk.Frame that contains the other widgets.
class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
def __init__(self, master, *args, **kwargs):
# self is now an istance of tk.Frame
tk.Frame.__init__(self,master, *args, **kwargs)
# make a new Canvas whose parent is self.
self.canvas = tk.Canvas(self,bg='yellow', width=430)
self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5)
self.button = tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage))
self.button.pack()
# pack the canvas inside the self (frame).
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
#print('is instance',isinstance(self,tk.Frame))
Second: Keeping track of which instances/objects are created, instead of continuously spawning new ones. I decided to keep it simple and create an internal dictionary whose keys are the classes of StratUpPage,PageOne or PageTwo meaning each class gets to have one instance that can be switched to.
def switch_Canvas(self, Canvas_class):
# Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
if self._mainCanvas:
self._mainCanvas.pack_forget()
# Modification 2: is the Class type passed is a one we have seen before?
canvas = self._allCanvases.get(Canvas_class, False)
# if Canvas_class is a new class type, canvas is False
if not canvas:
# Instantiate the new class
canvas = Canvas_class(self)
# Store it's type in the dictionary
self._allCanvases[Canvas_class] = canvas
# Pack the canvas or self._mainCanvas (these are all frames)
canvas.pack(pady = 60)
# and make it the 'default' or current one.
self._mainCanvas = canvas
Entire Code
import tkinter as tk
from tkinter import ttk
import re
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._mainCanvas= None
# The dictionary to hold the class type to switch to
# Each new class passed here, will only have instance or object associated with it (i.e the result of the Key)
self._allCanvases = dict()
# Switch (and create) the single instance of StartUpPage
self.switch_Canvas(StartUpPage)
def switch_Canvas(self, Canvas_class):
# Unless the dictionary is empty, hide the current Frame (_mainCanvas is a frame)
if self._mainCanvas:
self._mainCanvas.pack_forget()
# is the Class type passed one we have seen before?
canvas = self._allCanvases.get(Canvas_class, False)
# if Canvas_class is a new class type, canvas is False
if not canvas:
# Instantiate the new class
canvas = Canvas_class(self)
# Store it's type in the dictionary
self._allCanvases[Canvas_class] = canvas
# Pack the canvas or self._mainCanvas (these are all frames)
canvas.pack(pady = 60)
# and make it the 'default' or current one.
self._mainCanvas = canvas
class StartUpPage(tk.Canvas):
def __init__(self, master, *args, **kwargs):
tk.Canvas.__init__(self, master, *args, **kwargs)
tk.Frame(self) # Here the parent of the frame is the self instance of type tk.Canvas
tk.Label(self, text="Example").grid(column = 0, row = 0)
tk.Button(self, text="Canvas1",
command=lambda: master.switch_Canvas(PageOne)).grid(column = 0, row = 1)
tk.Button(self, text="Canvas2",
command=lambda: master.switch_Canvas(PageTwo)).grid(column = 0, row = 2)
class PageOne(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self,master, *args, **kwargs)
self.canvas = tk.Canvas(self,bg='blue', width=430)
print('got',self,master,args,kwargs)
tk.Label(self, text="First Canvas").pack(side="top", fill="x", pady=5)
tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage)).pack()
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
class PageTwo(tk.Frame): # Sub-lcassing tk.Frame
def __init__(self, master, *args, **kwargs):
# self is now an istance of tk.Frame
tk.Frame.__init__(self,master, *args, **kwargs)
# make a new Canvas whose parent is self.
self.canvas = tk.Canvas(self,bg='yellow', width=430)
self.label = tk.Label(self, text="Second Canvas").pack(side="top", fill="x", pady=5)
self.button = tk.Button(self, text="Back to start-up page",
command=lambda: master.switch_Canvas(StartUpPage))
self.button.pack()
# pack the canvas inside the self (frame).
self.canvas.pack(fill=tk.BOTH, side=tk.LEFT, expand=True)
#print('is instance',isinstance(self,tk.Frame))
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
EDIT: Visual Demo

Why tkinter grid, columnconfigure and rowconfigure value not changing dynamically?

Please help anyone
You can read all details as an inline comment.
I have created three classes LeftFrame, RightFrame, DynamicWindow
In DynamicWindow I am inheriting RightFrame
Step 1
LeftFrame, in column 0 with minsize 350
Step 2
RightFrame, in column 1 with weight 1, capturing all available space
Step 3
DynamicWindow, ingeriting RightFrame , Here is main problem, Please read the code.
import tkinter as tk
from win32api import GetMonitorInfo, MonitorFromPoint
root = tk.Tk()
monitor_info = GetMonitorInfo(MonitorFromPoint((0, 0)))['Work']
root.geometry(f'{monitor_info[2]}x{monitor_info[3]}')
root.state('zoomed')
root.columnconfigure(0, minsize=350) # Width of left frame
root.columnconfigure(1, weight=1) # All available space for right frame
root.rowconfigure(0, weight=1) # Full screen height for both frame
class LeftFrame(tk.Frame):
"""
Left Frame
"""
def __init__(self, container):
super().__init__(container)
self.config(bg='red')
self.grid(row=0, column=0, sticky='nsew')
class RightFrame(tk.Frame):
"""
Right Frame:
Divided into three section head frame, middle frame and bottom frame
head frame contains button
middle frame contains dynamically changeable frame. Here I am facing problem,
frame not able to take actual size according to weight and minsize that I given.
"""
def __init__(self, container):
super().__init__(container)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, minsize=30)
self.rowconfigure(1, weight=1)
self.rowconfigure(2, minsize=30)
self.config(bg='green')
self.grid(row=0, column=1, sticky='nsew')
self.head_frame = tk.Frame(self, bg='orange')
self.head_frame.grid(row=0, column=0, sticky='nsew')
self.head_frame.rowconfigure(0, weight=1)
self.first_screen_button = tk.Button(self.head_frame, text='First Screen')
self.first_screen_button.grid(row=0, column=0, sticky='nsew', ipadx=20)
self.second_screen_button = tk.Button(self.head_frame, text='Second Screen')
self.second_screen_button.grid(row=0, column=1, sticky='nsew', ipadx=20)
self.middle_frame = tk.Frame(self, bg='green')
self.middle_frame.grid(row=1, column=0, sticky='nsew')
self.bottom_frame = tk.Frame(self, bg='orange')
self.bottom_frame.grid(row=2, column=0, sticky='nsew')
class DynamicWindow(RightFrame):
def __init__(self, container):
super().__init__(container)
self.first_screen_button.config(command=self.first_screen)
self.second_screen_button.config(command=self.second_screen)
self.first_screen() # I am calling this here becuase on first click on first screen button
# window don't appear. You can check by comment this code.
# Can anyone please tell me why first screen not appear on first click.
def first_screen(self):
"""
First screen that I want to appear when I click on button
It has two frame
"""
for widget in self.middle_frame.winfo_children():
# Want to destroy all available widget in middle frame
widget.destroy()
self.middle_frame.columnconfigure(0, weight=1)
# Configuring size and weight but this is not working properly
self.middle_frame.columnconfigure(1, minsize=30)
self.middle_frame.rowconfigure(0, weight=1)
self.middle_frame.rowconfigure(1, weight=0)
main_chart_window = tk.Frame(self.middle_frame, bg='#4d4d4d')
main_chart_window.grid(row=0, column=0, sticky='nsew')
toolbar = tk.Frame(self.middle_frame, bg='red')
toolbar.grid(row=0, column=1, sticky='nsew')
def second_screen(self):
"""
This is not working in proper way
I am not able to reconfigure weight of middle frame
I want this window in full screen in middle frame
Here you will notice column 1 taking minsize 30, can anyone solve this
"""
for widget in self.middle_frame.winfo_children():
widget.destroy()
self.middle_frame.columnconfigure(0, weight=1)
self.middle_frame.rowconfigure(0, weight=1)
second_screen_window = tk.Frame(self.middle_frame, bg='purple')
second_screen_window.grid(row=0, column=0, sticky='nsew')
left_frame = LeftFrame(root)
dynamic_window = DynamicWindow(root)
root.mainloop()
I made it all work for you. I lost interest in fighting with your code/method so, I completely rewrote the code from scratch and devised a different method. All of the issues that you highlighted have been resolved. The structure of my code should be much easier to work with. The main issue is that you were destroying children, but you weren't destroying the column and/or row that the children were in. You basically can't. Using grid_forget() even in conjunction with destroy() or grid_remove() doesn't seem to remove the grid cell.
changes:
Every major widget has been separated into it's own class
Names have been changed to reflect the actual purpose of each widget (to the best of my ability based on your example)
We swap 'main display' widgets by removing/re-instating the entire widget ~ instead of destroying/recreating all of it's children
a lambda is used in the button command to pass the desired 'main display' to the method that does the swapping
we never use super() to instantiate a class. We specifically refer to the super by classname
all args and kwargs are maintained, so we can treat our custom widgets like their super
we only import exactly what we need (my preference)
The comments should tell you the rest, but if there is confusion, point it out to me in the comment section and I will respond with a more detailed explanation.
widgets.py
from tkinter import Frame, Button
from typing import List, Dict, Callable
from dataclasses import dataclass
#a simple "typedef" for storing menu button data
#dataclass
class MenuData_t:
func:Callable #method the command lambda will call
buttons:List[Dict] #Button(**kwargs)
targets:List[Frame] #'main display' to switch to
griddata:List[Dict] #.grid(**kwargs)
'''
this replaces your 'head_frame'
it also provides an interface to concoct all of the buttons that will swap 'main displays'
if you need other types of buttons you will need to manually create them in __init__
considerations have been made in init_displayswap_menu for existing buttons
'''
class MenuFrame(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
def init_displayswap_menu(self, md:MenuData_t):
c = len(self.winfo_children())
for i, (b, t, g) in enumerate(zip(md.buttons, md.targets, md.griddata)):
self.__dict__[f'swap_btn{i+1}'] = Button(self, command=lambda m=t: md.func(m), **b)
self.__dict__[f'swap_btn{i+1}'].grid(row=0, column=i+c, **g)
#this replaces your "bottom_frame"
class Footer(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
#this replaces your "first_screen"
class PrimaryFrame(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.chart = Frame(self, bg='#4d4d4d')
self.chart.grid(row=0, column=0, sticky='nswe')
self.toolbar = Frame(self, bg='red')
self.toolbar.grid(row=0, column=1, sticky='nswe')
self.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(1, minsize=30)
self.grid_rowconfigure(0, weight=1)
#this replaces your "second_screen"
class SecondaryFrame(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
#this replaces your "LeftFrame"
class Sidebar(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
#this replaces your "RightFrame" AND "DynamicWindow"
class MainFrame(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
##INSTANTIATE
#menu
self.menu = MenuFrame(self, bg='orange')
self.menu.grid(row=0, column=0, sticky='nsew')
#main display
self.current = None #for storing currently used 'main display'
self.primary = PrimaryFrame(self, bg='green')
self.secondary = SecondaryFrame(self, bg='purple')
#footer
self.footer = Footer(self, bg='orange')
self.footer.grid(row=2, column=0, sticky='nsew')
##UTILIZE
#concoct main display swap menu
'''
append accordingly to the 3 lists to create more buttons that will switch frames
done this way so button creation can remain in MenuFrame but use remote data
row, column and command are managed in MenuFrame
'''
self.menu.init_displayswap_menu(MenuData_t(
self.main_display, #method the command lambda will call
[{'text':'Primary'}, #Button(**kwargs)
{'text':'Secondary'},
],
[self.primary, #'main display' to switch to
self.secondary,
],
[{'sticky':'nswe','ipadx':20}, #.grid(**kwargs)
{'sticky':'nswe','ipadx':20},
]
))
#init main display
'''
I could have called main_display directly but this illustrates 2 things
1: how to virtually click a button
2: how to access the buttons that MenuFrame created in it's __dict__
'''
self.menu.swap_btn1.invoke()
#configure grid
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, minsize=30)
self.grid_rowconfigure(1, weight=1)
self.grid_rowconfigure(2, minsize=30)
#replaces your 'first_screen' AND 'second_screen' methods
def main_display(self, frame):
if self.current is not frame: #only swap if we aren't requesting the current 'main display'
if self.current:
self.current.grid_remove() #remove current from the grid, instead of destroy
self.current = frame #set new current and add it to the grid
self.current.grid(row=1, column=0, sticky='nsew')
main.py
from win32api import GetMonitorInfo, MonitorFromPoint
from widgets import Sidebar, MainFrame
from tkinter import Tk
#This is your "root"
class Application(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
Sidebar(self, bg='red').grid(row=0, column=0, sticky='nswe')
MainFrame(self, bg='black').grid(row=0, column=1, sticky='nswe')
#configure grid
self.grid_columnconfigure(0, minsize=350)
self.grid_columnconfigure(1, weight=1)
self.grid_rowconfigure(0, weight=1)
#kick off the entire app with proper PEP8
if __name__ == '__main__':
monitor_info = GetMonitorInfo(MonitorFromPoint((0, 0)))['Work']
app = Application()
app.title("Manish Pushpam's Bad-Ass Application")
app.geometry(f'{monitor_info[2]}x{monitor_info[3]}')
app.minsize(800, 600)
app.mainloop()

Tkinter Treeview not showing newly inserted items, no errors given

I'm building a desktop application that lets you insert some data into a form and then the data is displayed in a series (3) of Treeview widgets.
This is the form that I'm using to enter new data:
It's in a Toplevel widget. When the Add button is pressed the new data is stored in a file and it also should insert the new data in the corresponding Treeview Widget.
This is the root window:
It's comprised of 3 Treeview widgets. The purpose of the application is to give the user the opportunity to sort candidates into the right Treeview widget.
The issue that I'm facing is that when the Add button is pressed the new data is not shown in the Treeview widget and no errors are given. I think it may be an issue of class instantiation. This is an excerpt from my app, please see below a Minimal, Complete and Verifiable example
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
...
# frame and menu classes are instantiated here
self.FrameList = {ViableCandidates: ViableCandidates(self),
NotViableCandidates: NotViableCandidates(self),
InProgressCandidates: InProgressCandidates(self)}
...
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
...
# menu code is here
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
...
# code for the small Toplevel window
...
# this is the code that I use to add the new item to Treeview when the Add button is pressed
if CandidateInfo['status'] == 'Viable':
app.InstanceLinker(ViableCandidates).AddtoList()
elif CandidateInfo['status'] == 'Not Viable':
app.InstanceLinker(NotViableCandidates).AddtoList()
else:
app.InstanceLinker(InProgressCandidates).AddtoList()
# ViableCandidates, NotViableCandidates, InProgressCandidates are created with the same pattern
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
global Counter
tk.Frame.__init__(self, parent)
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
title = tk.Label(self, text="Candidates In Progress", font="Verdana 10 bold")
title.grid(row=0, column=0, sticky='nesw')
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns=('Name', 'Date'), selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('Name', width=150, minwidth=10, stretch=tk.YES)
self.tree.column('Date', width=80, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('Name', text='Name', anchor=tk.W)
self.tree.heading('Date', text='Date', anchor=tk.W)
if Counter < 4:
Counter += 1
self.PopulateList()
def PopulateList(self):
selection = Database().SelectFromDB('name, date', "status = 'In progress'")
for i in range(len(selection)):
name = list(selection[i])[0]
date = adjusttotimezone(list(selection[i])[1])
self.tree.insert("", i, name, text=i + 1)
self.tree.set(name, 'Name', name)
self.tree.set(name, 'Date', date)
CandidateCounter['InProgressCandidates'] = i
def AddtoList(self):
CandidateCounter['InProgressCandidates'] += 1
print('I was here')
self.tree.insert("", CandidateCounter['InProgressCandidates'], CandidateInfo['name'],
text=CandidateCounter['InProgressCandidates'])
self.tree.set(CandidateInfo['name'], 'Name', CandidateInfo['name'])
selection = Database().SelectFromDB('date', "name = '" + CandidateInfo['name'] + "'")
date = adjusttotimezone(list(selection[0])[0])
self.tree.set(CandidateInfo['name'], 'Date', date)
app = MainApp()
app.mainloop()
When the "Add" button is pressed there are no errors and "I was here" is printed so the AddtoList method is instantiated, but there are no new items added to Treeview. I did check if the variables that I'm using to create the new Treeview item hold the correct data and they do.
EDIT: This is a Minimal, Complete and Verifiable example:
import tkinter as tk
from tkinter import ttk
Bigbadtext = ''
Counter = 0
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
self.MainWindow = tk.Tk.__init__(self, *args, **kwargs)
menu = GUIMenu(self)
self.config(menu=menu)
frame = InProgressCandidates(self)
frame.grid(row=0, column=1, sticky='nesw')
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
def InstanceLinker(self, frame):
link = self.FrameList[frame]
return link
class GUIMenu(tk.Menu):
def __init__(self, parent):
tk.Menu.__init__(self, parent)
addcandidates = tk.Menu(self, tearoff=0)
self.add_cascade(label='Add Candidates', menu=addcandidates)
addcandidates.add_command(label='Quick Add', command=lambda: QuickAdd(parent))
class QuickAdd(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
saysomething = tk.Entry(self)
saysomething.grid(row=1, column=0)
def addbutton():
global Bigbadtext
Bigbadtext = saysomething.get()
app.InstanceLinker(InProgressCandidates).AddtoList()
okbutton = ttk.Button(self, text='Add', command=addbutton)
okbutton.grid(row=2, column=0)
class InProgressCandidates(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.tree = ttk.Treeview(self)
self.tree.grid(row=1, column=0, sticky='nesw')
scrollbar = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
scrollbar.grid(row=1, column=1, sticky='nws')
self.tree.config(columns='something', selectmode='browse', height=20, yscrollcommand=scrollbar.set)
self.tree.column('#0', width=20, minwidth=10, stretch=tk.YES)
self.tree.column('something', width=150, minwidth=10, stretch=tk.YES)
self.tree.heading('#0', text='#', anchor=tk.W)
self.tree.heading('something', text='Say something', anchor=tk.W)
def AddtoList(self):
global Counter
Counter += 1
print('I was here')
self.tree.insert("", Counter, Bigbadtext, text=Counter)
self.tree.set(Bigbadtext, 'something', Bigbadtext)
app = MainApp()
app.mainloop()
The problem is that you are creating two treeview widgets, and then adding items to the one that is invisible.
You create one here:
frame = InProgressCandidates(self)
Then you create another one here:
self.FrameList = {InProgressCandidates:InProgressCandidates(self)}
Since you've already created one, the one you created should be what goes in self.FrameList:
self.FrameList = {InProgressCandidates:frame}
It is not really an answer but I up voted the question because it solved me a problem. I wanted to add items to the widget but did not want to show it to the user until I finished to populate the tree. But each insert showed right away. Now I create 2 identical widgets, one visible and the other is not, and once it is populated I change between them. Thus even a mistake can have a benefit.

Python-Tkinter Place button on left of frame

How do I place the QUIT button in below code to the extreme right of the Frame?
I tried several things like:
padx
and
self.pack(side="top", anchor="e")
but after trying some 15 times both buttons are coming close to each other. Maybe Some help from anyone would be really appreciated. I need one button on extreme right and other on extreme left
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Run_Main.pack(side='left')
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.QUIT.pack(anchor='e')
self.pack(side="top", anchor="w")
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
You have several problems here.
First, you're using the wrong geometry manager. The pack geometry manager, as the name implies, packs the widgets as close together as possible. That's not what you want. The grid geometry manager lets you put the widgets into a table-like layout with rows and columns. If you put the Browse button into the first column and the Quit button into the last column, you'll be a step closer.
Second, your Application window contains three child widgets and you're only putting two of them into a geometry manager. How that is going to mess you up I don't even want to think about. So I put the label into column 1, the Quit button into column 2, and the Browse button into column 0. The Quit button I gave a "sticky" value of "e" so it will be attached to the east (right) side of its allocated space.
Third, all the geometry managers try to compact the widgets as much as possible unless you specifically tell it to do otherwise. I told the grid manager to expand column 2 so that the extra space gets assigned to the cell that holds the Quit button.
Fourth, you need to tell the pack manager to expand the top widget so that it spans the entire window. The directive for that is fill="x".
Fifth, you have a redundant call to the pack manager at the end of your createWidgets function.
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack(fill="x")
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.Label.grid(row=0, column=1)
self.Run_Main.grid(row=0, column=0, sticky="w")
self.QUIT.grid(row=0, column=2, sticky="e")
self.columnconfigure(2, weight=1)
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
These link, link helped. The other option would be to use tkinter's grid manager, it will be more intuitive and keep you more organized in the future.
import tkinter as tk
from tkinter.ttk import *
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack()
self.createWidgets()
self.master.title("Log Parser")
def createWidgets(self):
self.Run_Main = tk.Button(self)
self.Run_Main["text"] = "Browse.."
# self.Run_Main["fg"] = "blue"
self.Run_Main["command"] = self.Sayhello
self.Run_Main.pack(side='left')
self.Label = tk.Label(self)
self.Label["text"] = 'Processing...'
self.Label.pack(side='left')
self.progressbar = Progressbar(mode="indeterminate", maximum=20)
self.QUIT = tk.Button(self)
self.QUIT["text"] = "Quit!"
self.QUIT["command"] = self.quit
self.QUIT.pack(side='right')
self.pack(side="top", fill=tk.BOTH) # changes here
def Sayhello(self):
print("Hello")
# scroll text inside application frame
class scrollTxtArea:
def __init__(self, root):
frame = tk.Frame(root)
frame.pack()
self.textPad(frame)
return
def textPad(self, frame):
# add a frame and put a text area into it
textPad = tk.Frame(frame)
self.text = tk.Text(textPad, height=18, width=60)
self.text.config()
# add a vertical scroll bar to the text area
scroll = tk.Scrollbar(textPad)
self.text.configure(yscrollcommand=scroll.set,background="black", foreground="green")
# pack everything
self.text.pack(side=tk.LEFT, pady=2)
scroll.pack(side=tk.RIGHT, fill=tk.Y)
textPad.pack(side=tk.TOP)
return
root = tk.Tk()
root.resizable(width=False, height=False)
root.option_add('*font', ('verdana', 9, 'bold'))
app = Application(master=root)
scrollFrame = scrollTxtArea(root)
app.mainloop()
There are two simple fixes you can make in order to get the behavior you want.
First, you need to pack Application so that it fills the window:
class Application(...):
def __init__(...):
...
self.pack(fill="x")
Next, simply pack the quick button on the right side of the window:
self.QUIT.pack(side="right", anchor='e')
Even though the above is all you need to do in this specific example, there are additional things you can do to make your job much easier.
I would recommend creating a frame specifically for the buttons. You can pack it at the top. Then, put the buttons inside this frame, and pack them either on the left or right. You'll get the same results, but you'll find it easier to add additional buttons later.
I also find that it makes the code much easier to read, write, maintain, and visualize when you separate widget creation from widget layout.
class Application(...):
...
def createWidgets(self):
toolbar = tk.Frame(self)
toolbar.pack(side="top", fill="x")
self.Run_Main = tk.Button(toolbar)
self.Label = tk.Label(toolbar)
self.QUIT = tk.Button(toolbar)
...
self.Run_Main.pack(side="left")
self.Label.pack(side="left", fill="x")
self.QUIT.pack(side="right")
...

Python, tkinter, complex dialogs and code structure

When implementing complex dialogs (i.e. dialogs with some 10 or more widgets, especially when arranged within multiple frames or the like), the creation requires many tkinter calls and the code can become increasingly complex (difficult to read and maintain) when it is kept within a single method. Also in general, short functions/methods are usually preferred over longer ones.
My current approach to limit method length is to encapsulate creation of all widgets that belong to a group within the dialog into one method(parent_frame, other_options) that returns the top-level widget like this:
import tkinter as tk
class Dialog:
def __init__(self, master):
self.__master = master
self.create_gui(master)
def create_gui(self, frame, title = None):
if title: frame.title(title)
group_a = self.create_group_a(frame)
group_a.grid(row=0, column=0, sticky="nsew")
group_b = self.create_group_b(frame)
group_b.grid(row=1, column=0, sticky="nsew")
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
frame.rowconfigure(1, weight=1)
def create_group_a(self, frame):
inner_frame = tk.LabelFrame(frame, text="Label")
text = self.create_text_with_scrollbar(inner_frame)
text.pack(fill="both")
return inner_frame
def create_group_b(self, frame):
button = tk.Button(frame, text="Button")
return button
def create_text_with_scrollbar(self, frame):
text_frame = tk.Frame(frame)
text_frame.grid_rowconfigure(0, weight=1)
text_frame.grid_columnconfigure(0, weight=1)
text = tk.Text(text_frame)
text.grid(row=0, column=0, sticky="nsew")
scrollbar = tk.Scrollbar(text_frame, command=text.yview)
scrollbar.grid(row=0, column=1, sticky="nsew")
text['yscrollcommand'] = scrollbar.set
return text_frame
if __name__ == "__main__":
master = tk.Tk()
Dialog(master)
tk.mainloop()
Are there any specific guidelines around on code structuring in such cases? Does anybody have any advice on how to better structure such code?
What I usually do is to write a new class for every group.
Those classes inherit from Frame.
The end result will look something like this:
class MainFrame(Frame):
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self.first_sub = FirstSubFrame(self)
self.second_sub = SecondSubFrame(self)
self.first_sub.grid()
self.second_sub.grid()
class FirstSubFrame(Frame):
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self.possibly_another_subframe = PossibleOtherFrame(self)
self.awesome_button = tkinter.Button()
self.possibly_another_subframe.grid()
self.awesome_button.grid()
...
I hope this helps.

Categories