Python Tkinter Frame flashing with Scrollbar - python

I'm attempting to create a level selection menu of sorts for a terminal-based game I'm making. I had it sorting levels into columns of ten, but it occured to me that the menu would get very wide as more levels are added. Instead, I'm trying to make an alphabetical list that can be scrolled through.
I've followed several posts' advice about making a scrollable frame, and I've run into a problem I haven't seen before: The window keeps flashing and spazzing out. It only occurs one I add the line indicated below, but that line also is the one that makes the level buttons appear.
def show_levels(self, frames):
self.menu_title = tk.Label(self,
text=" Nonstop Robot ",
fg="blue"
)
if frames:
self.frame_levels = tk.Frame(self,
relief="sunken",
width=50,
height=100
)
self.level_canvas = tk.Canvas(self.frame_levels)
self.level_list = tk.Frame(self.level_canvas)
self.level_scroll = tk.Scrollbar(self.frame_levels,
orient="vertical",
command=self.level_canvas.yview
)
self.level_canvas.configure(yscrollcommand=self.level_scroll.set)
self.frame_menu = tk.Frame(self)
def level_bind(event):
self.level_canvas.configure(
scrollregion=self.level_canvas.bbox("all"),
width=70,
height=200
)
self.level_buttons = []
"""
Code that adds the buttons to all the frames.
"""
for b in self.level_buttons:
b.pack()
if frames:
self.level_scroll.pack(side="right", fill="y")
self.level_canvas.pack(side="left")
self.level_canvas.create_window((0, 0),
window=self.frame_levels,
anchor="nw"
)
self.frame_levels.bind("<Configure>", level_bind)
self.frame_levels.pack(side="bottom")
self.level_list.pack() # This is the line in question
self.frame_menu.pack(side="top")
self.menu_title.pack()
I removed the code that actually creates all the buttons, since it's very long. If needed, I can add it.

You can't use both create_window and pack on self.frame_levels, and if you use self.frame_levels.pack, it will not scroll along with the rest of the canvas.
You also seem to have the problem that you create a canvas that is a child of self.frame_levels, and then later you try to add self.frame_levels to the canvas.

Related

python Tkinter grid method not working as should be for some reason

i am trying to get my listbox to move to the bottom of the gui but no matter how high of a row value i give it it wont budge. you can see my listbox in the Creat_Gui method in my code. im not sure why this is happpening it cant be the button because the button is in row 1 so im not sure whats causing this.
i tried using sticky='s' that didnt work i tried changing the rows multiple times didnt work. i tried using the root.rowconfigure(100,weight=1) this worked kind of but messes with thte grid which is annoying
import tkinter as tk
class Manager:
def __init__(self):
self.root=tk.Tk()
self.root.title('password_manager')
self.root.geometry('500x600')
self.create_GUI()
self.storage = {}
self.root.mainloop()
def create(self):
pass
def open_page(self):
print('openpage')
def add_new_button(self):
pass
def add_new(self):
self.app_title=tk.Label(text='test')
self.app_title.grid(row=2,column=50)
self.application_name=tk.Entry(self.root,width=20,font=('arial',18))
self.username=tk.Entry(self.root,width=20,font=('arial',18))
self.password=tk.Entry(self.root,width=20,font=('arial',18))
self.application_name.grid(row=2, column=1)
self.username.grid(row=3, column=2)
self.password.grid(row=4, column=3)
self.app_name_label = tk.Label(self.root, text='Application Name:')
self.username_label = tk.Label(self.root, text='Username:')
self.password_label = tk.Label(self.root, text='Password:')
self.app_name_label.grid(row=2, column=0)
self.username_label.grid(row=3, column=1)
self.password_label.grid(row=4, column=2)
self.password.bind('<Return>',self.hide)
def hide(self,thing):
#store user info to pass onto dictionary and hide textboxes
username=self.username.get()
password=self.password.get()
app_name=self.application_name.get()
self.application_name.grid_forget()
self.username.grid_forget()
self.password.grid_forget()
self.add_to_memory(username,password,app_name)
def add_to_memory(self,username,password,app_name):
#store username password and application name in dictionary
if app_name in self.storage.keys():
return
else:
self.storage[app_name]=(username,password)
print(self.storage)
def create_GUI(self):
#create initial interface
#self.root.columnconfigure(100, weight=1)
#self.root.rowconfigure(100, weight=1)
self.listbox=tk.Listbox(self.root,width=100)
self.listbox.grid(row=200,column=0)
self.button=tk.Button(self.root,text='add new',font=('arial',18),command=self.add_new)
self.button.grid(row=1,column=0)
Manager()
If you want to use grid, and you want a widget to be at the bottom of it's parent, you need to designate some row above that widget to take all of the extra space, forcing the widget to the bottom. Here's one example:
def create_GUI(self):
self.listbox=tk.Listbox(self.root,width=100)
self.expando_frame = tk.Frame(self.root)
self.button=tk.Button(self.root,text='add new',font=('arial',18),command=self.add_new)
self.root.grid_rowconfigure(2, weight=1)
self.button.grid(row=1,column=0)
self.expando_frame.grid(row=2, sticky="nsew")
self.listbox.grid(row=3,column=0)
With that, the listbox will be at the bottom, with extra space above it. If you want to add more widgets, you can add them to self.expando_frame rather than self.root and they will appear above the listbox.
Using frames in this way is a valuable technique. You could, for example, use three frames in root: one for the top row of buttons, one for the listbox on the bottom, and one for everything in the middle. You could then use pack on these frames and save a line of code (by virtue of not having to configure the rows). You can then use grid for widgets inside the middle frame.

Storing and using GUI objects with Tkinter

So I have a class that just creates a gui with a text box to type into. I would like to store multiple of these objects and their content that is entered by the user. I have a button (on a seperate gui) that creates the new object and spawns the gui and then stores that into a simple list listOobjects = []. My thought process was that I could simply just call listOobjects[i] when a button was pressed to bring that gui back with its contents still in place. That didn't work so then I thought maybe I could use the listOobjects[i].withdraw() and listOobjects[i].deiconify() to hide and recall what gui I want. Not only did that not work but I also feel that isn't the best course of action with possibly 12 gui's practically 'minimized'. I also looked into pickle to save and recall objects. I haven't tried it yet but was curious of my best course of action here?
Although I don't know what is considered best practice in this case, here is one possible way that avoids keeping track of 'minimized' widgets that aren't being used.
This solution deletes widgets which are not currently being displayed by calling the destroy method, so the program isn't doing extra work maintaining objects that aren't being used.
In order to be able to recall the deleted widgets when the button is pressed, all relevant information from a widget is recorded and stored in a tuple before the widget is deleted. This way, no information is lost, but only simple values are being stored instead of tkinter widgets.
These tuples of values are then used as parameters to instantiate new widgets which exactly replicate the old, deleted ones.
Here is a simple demo that toggles between three different Entry widgets:
import tkinter as tk
window = tk.Tk()
window['width'] = 500
window['height'] = 300
textBox1 = tk.Entry(master=window, bg='darkblue', fg='yellow', text="First",
relief=tk.RAISED, bd=3)
textBox2 = tk.Entry(master=window, bg='purple', fg='white', text="Second",
relief=tk.RAISED, bd=3)
textBox3 = tk.Entry(master=window, bg='darkgreen', fg='yellow', text="Third",
relief=tk.RAISED, bd=3)
textBox1.place(x=50, y=100, width=100, height=30)
textBox2.place(x=200, y=100, width=100, height=30)
textBox3.place(x=350, y=100, width=100, height=30)
# Will store all information necessary to reconstruct and place previously displayed
# text boxes which have been removed.
storage = [None, None, None]
# Store a reference to the text box which is currently displayed
activeTextBox = {'textBox': textBox1, 'index': 0}
# After recording all information to be used in 'storage', call 'destroy()' to delete
# the widget instead of hiding it (for more efficiency)
def sendToStorage(textBox, index):
parameters = (textBox['bg'], textBox['fg'], textBox['relief'], textBox['bd'], textBox.get())
storage[index] = parameters
textBox.destroy()
# Using the stored information, construct a new text box (tk.Entry widget) which is
# identical to the old one that was deleted.
def retrieveFromStorage(index):
txtB = storage[index]
storage[index] = None
activeTextBox['textBox'] = tk.Entry(window, bg=txtB[0], fg=txtB[1], relief=txtB[2], bd=txtB[3])
activeTextBox['textBox'].insert(0, txtB[4])
activeTextBox['textBox'].place(x=50+150*index, y=100, width=100, height=30)
# Put the old text box in storage and retrieve the one after it. Increment the index
# of the text box that is currently active (loop around once you get to the end of 'storage').
def toggleTextBox():
sendToStorage(activeTextBox['textBox'], activeTextBox['index'])
activeTextBox['index'] += 1
if activeTextBox['index'] == len(storage):
activeTextBox['index'] = 0
retrieveFromStorage(activeTextBox['index'])
window.update()
# DEMO: CALL FUNCTION TO STORE AND DELETE ALL BUT 1 TEXT BOX
sendToStorage(textBox2, 1)
sendToStorage(textBox3, 2)
# THIS IS THE BUTTON THAT WILL CYCLE BETWEEN THE TEXT BOXES
toggleButton = tk.Button(master=window, text='TOGGLE ACTIVE TEXT BOX',
command=toggleTextBox)
toggleButton.place(x=100, y=200, width=300, height=50)
window.mainloop()
It will keep track of the text boxes' current text (that the user has entered) as well as their formatting options. Try it out and see if it does what you're looking for!

TKinter Geometry Manager displaying multiple rows of widgets

I wish to display a list of sentences with missing words. The basic, one-line, idea is the following:
The construction of the above is a "label + entry + label + spacing + label". In order to make sure that the widgets were aligned left, I used the following code:
phraseLabel1 = tk.Label(questionFrame)
phraseLabel1.pack(side=tk.LEFT)
keyWordEntry = tk.Entry(questionFrame)
keyWordEntry.pack(side=tk.LEFT)
phraseLabel2 = tk.Label(questionFrame)
phraseLabel2.pack(side=tk.LEFT)
keyWordLabel = tk.Label(questionFrame)
keyWordLabel.pack(side=tk.LEFT,padx=30)
My objective is to present the users with an input screen for multiple sentence. As for example given in the following drawing:
Whilst I manage to create the labels via the underneath (experimental) code, I lack understanding to manage the geometry.
root = tk.Tk()
root.title("myTest")
root.geometry("700x700")
questionFrame = tk.Frame(root)
resultFrame = tk.Frame(root)
for frame in (questionFrame, resultFrame):
frame.grid(row=0, column=0, sticky='news')
#DB Query returning a set of phrases and Keywords
(zinPhrase1, zinPhrase2, keyWordFR, keyWordNL)=getPhrase()
#Init
lab1 = []
keyWordEntry = []
lab2 = []
keyWord = []
for i in range(4): #4 is entered as a dummy value
lab1.append(tk.Label(questionFrame))
lab1[i].pack()
keyWordEntry.append(tk.Entry(questionFrame))
keyWordEntry[i].pack()
lab2.append(tk.Label(questionFrame))
lab2[i].pack()
keyWord.append(tk.Label(questionFrame))
keyWord[i].pack()
lab1[i].config(text=zinPhrase1[i])
keyWordEntry[i].config(width=8)
lab2[i].config(text=zinPhrase2[i])
keyWord[i].config(text=keyWordNL[i],fg="red")
questionFrame.tkraise()
root.mainloop()
How can I manage the placement of the widgets line by line, as shown in the drawing above? Any help would gratefully appreciated.
Since you don't seem to want to organize your widgets in a grid, the most common solution to this problem is to create a frame for each row. The frames stack top-to-bottom, and the widgets inside the frame stack left-to-right.
In my experience, GUI code is much easier to visualize when you separate widget creation from widget layout, so I've done that in the following example to hopefully make it easier to comprehend.
for i in range(4): #4 is entered as a dummy value
rowFrame = tk.Frame(questionFrame)
rowFrame.pack(side="top", fill="x")
lab1.append(tk.Label(rowFrame))
keyWordEntry.append(tk.Entry(rowFrame))
lab2.append(tk.Label(rowFrame))
keyWord.append(tk.Label(rowFrame))
lab1[i].pack(side="left")
keyWordEntry[i].pack(side="left")
lab2[i].pack(side="left", padx=(0, 40))
keyWord[i].pack(side="left")
lab1[i].config(text=zinPhrase1[i])
keyWordEntry[i].config(width=8)
lab2[i].config(text=zinPhrase2[i])
keyWord[i].config(text=keyWordNL[i],fg="red")
The above code results in something like this:
Would something like the following suffice?
for i in range(4): #4 is entered as a dummy value
frames.append(tk.Frame(questionFrame))
frames[i].grid(row=i, column=0, stick='W')
for i in range(4):
lab1.append(tk.Label(frames[i]))
lab1[i].grid(row=0, column=0)
keyWordEntry.append(tk.Entry(frames[i]))
keyWordEntry[i].grid(row=0, column=1)
lab2.append(tk.Label(frames[i]))
lab2[i].grid(row=0, column=2)
keyWord.append(tk.Label(frames[i]))
keyWord[i].grid(row=0, column=3)
lab1[i].config(text=zinPhrase1[i])
keyWordEntry[i].config(width=8)
lab2[i].config(text=zinPhrase2[i])
keyWord[i].config(text=keyWordNL[i],fg="red")

Tkinter grid of frames gets stacked vertically instead of hotizontal

I'm surely misunderstanding something with pack and grid...
I'm making the GUI for a python application that get debug messages in multiple terminal-like consoles. Making a Tkinter GUI is new to
First of all why do I need to pack buttonframe to have it shown? Shouldn't grid be equivalent to pack?
The GUI is made of a series of buttons on top in the buttonframe and a series of vertical consoles below showing up in columns. Instead what I get is all the consoles stacked vertically.
The consoles are frames with text inside which is written by other threads in the application.
If I don't grid the consoles[idx] it doesn't change (so I assume it doesn't work). If I don't pack the consoles[idx] the GUI starts flickering.
Please note that if I instead call create_Hterms, which instead stacks vertically a bunch of horizontal consoles.
How do I correctly put my consoles as vertical columns?
import Tkinter as tikei
NCONSOLES = 4
HORZ_CONSOLE_W = 10
HORZ_CONSOLE_H = 12
# FakeConsole: one of the consoles that are spawned by MainWindow.
class FakeConsole(tikei.Frame): # from Kevin in http://stackoverflow.com/questions/19479504/how-can-i-open-two-consoles-from-a-single-script
def __init__(self, root, cust_width, cust_height, *args, **kargs):
tikei.Frame.__init__(self, root, *args, **kargs)
self.grid(row=0)
#white text on black background
self.text = tikei.Text(root, bg="black", fg="white", width=cust_width, height=cust_height)
self.text.grid(row=0)
self.text.insert(tikei.END, 'ASD')
self.text.see(tikei.END)
def clearConsole(self):
self.text.delete(1.0,tikei.END)
class MainWindow(tikei.Frame):
counter = 0
consoles = []
consoleFormat = ''
e_Vvar = ''
e_Hvar = ''
var1 = ''
ctrlCmdRecipient = ''
def __init__(self, *args, **kwargs):
tikei.Frame.__init__(self, *args, **kwargs)
self.consoleFormat = 'H'
self.cF = tikei.StringVar()
self.grid(row=0)
self.buttonframe = tikei.Frame(self)
self.buttonframe.grid(row=0)
tikei.Button(self.buttonframe, text = "Clear Consoles", command=self.clearConsoles).grid(row=0, column=0)
tikei.Button(self.buttonframe, text = "start").grid(row=0, column=1)
tikei.Button(self.buttonframe, text = "stop").grid(row=0, column=2)
self.consoleFrame = tikei.Frame(self)
self.create_Hterms()
self.consoleFrame.grid(row=1)
# create a status bar
self.statusTxt = tikei.StringVar()
self.statusTxt.set('-')
self.statusLab = tikei.Label(self.buttonframe, text="Status: ", anchor=tikei.W, height=1).grid(row=1, column=0, sticky=tikei.W)
self.statusBar = tikei.Label(self.buttonframe, textvariable=self.statusTxt, anchor=tikei.W, height=1).grid(row=1, column=1, columnspan=4, sticky=tikei.W)
def clearConsoles(self):
for i in range(NCONSOLES):
self.consoles[i].clearConsole()
def create_Hterms(self):
ctr = NCONSOLES
idx = 0
cons_w = HORZ_CONSOLE_W
cons_h = HORZ_CONSOLE_H
while ctr > 0:
self.consoles.append(FakeConsole(self.consoleFrame, cons_w, cons_h)) #
self.consoles[idx].grid(row=idx+1, column=0)
ctr -= 1
idx += 1
return idx+1
root = tikei.Tk()
mainTk = MainWindow(root)
root.wm_title("TOOLS")
mainTk.grid(row=0)
root.mainloop()
First of all why do I need to pack buttonframe to have it shown?
Shouldn't grid be equivalent to pack?
No, generally speaking you do not need to pack buttonframe to have it shown. Use pack or grid, but not both on the same widget. In this specific case, however, the use of pack masks bugs deeper in your code, and makes it seem like calling pack is required.
The problem is that you use pack for self.consoleFrame, and both self.consoleFrame and self.buttonFrame have the same parent. Because they share the same parent you must use either pack or grid for both.
The GUI is made of a series of buttons on top in the buttonframe and a
series of vertical consoles below showing up in columns. Instead what
I get is all the consoles stacked vertically.
This is because you use grid, and then you use pack. You can only use one or the other, and the last one you use "wins". Thus, you're using pack, and you're not giving it any options, so it's defaulting to packing things top to bottom.
While it's perfectly safe to use both grid and pack in the same application, it's pointless to use both on the same widget, and it's an error to use different ones for widgets that share the same parent.
The solution to the problem is to be consistent with your use of grid and pack. If you intend to use grid, make sure you use it consistently for all widgets that share the same parent.
The same holds true for pack -- use it consistently. Also, I recommend to always explicitly add the side, fill and expand arguments to pack so that it is clear what your intention is.

Python - TKinter - Destroying widgets not lowering frame height

I am having and issue where I have a frame in a game that displays the current progress of the game (let's call this frame; "results").
If the player chooses to start a new game all the widgets inside results get destroyed and the frame is forgotten to hide it until it is used again.
Now the issue I am having is; When results gets called back it is in-between two other frames. However, it has remained the size it was in the previous game when it has contained all the widgets, before the widgets were destroyed. The widgets are not shown in the frame but it's still the size it was when the widgets were there.
As soon as a new widget is placed in results the size is corrected but I can't figure out how to make the height = 0. I have tried results.config(height=0) but that hasn't worked.
Does anyone know how to "reset" the size of the frame to 0?
Sorry for the proverbial "wall-of-text" but I couldn't find a way to provide the code in a compact way.
Thanks
If I completely understand what you want, then this illustration is correct:
The blue is the results frame
The results removed, everything else resized:
And the corresponding code for this is something like:
import tkinter
RESULTS_WIDTH = 128
root = tkinter.Tk()
left_frame = tkinter.Frame(root, height=64, bg='#cc3399')
right_frame = tkinter.Frame(root, height=64, bg='#99cc33')
def rem_results(event):
# Remove widget
results.destroy()
# Resize other widthets
left_frame.config(width=128 + RESULTS_WIDTH/2)
right_frame.config(width=128 + RESULTS_WIDTH/2)
# Reposition other widgets
left_frame.grid(row=0, column=0)
right_frame.grid(row=0, column=1)
def add_results(event):
# Create results widget
global results
results = tkinter.Frame(root, width=RESULTS_WIDTH, height=64, bg='#3399cc')
results.grid(row=0, column=1)
# Resize other widgets
left_frame.config(width=128)
right_frame.config(width=128)
# Reposition other widgets
left_frame.grid(row=0, column=0)
right_frame.grid(row=0, column=2)
# Initialize results
add_results(None)
# Bind actions to <- and -> buttons
root.bind( '<Left>', rem_results )
root.bind( '<Right>', add_results )
#$ Enter eventloop
root.mainloop()

Categories

Resources