Textbox not expanding with containing frame - TKinter - python

I'm building an application that provides viewports for an internal data file. The files are somewhat complex, so I've created three sub-windows that handle different aspects of the file. The upper left provides an outline of the different sections, the upper right provides a series of text widgets containing errors found in the data file, and the lower provides a view of the datafile itself. To facilitate all of this, I wrote a small class that serves as a frame for each of these sections and can be populated with labels, textboxes, etc. (Code below.)
The problem I'm running into is that the text widgets in the upper right and lower sections do not expand with their containing frame. Based on various searches of effbot.org, Stackoverflow, and others, I think I have the settings correct, but obviously something is wrong. If I enlarge the main window, each section adapts as it should, but the text widgets don't expand left to right to fill the new subwindow dimensions.
Any tips are greatly appreciated.
Here's the class that provides functionality for the subwindows:
import Tkinter as tk
class ScrollingChildFrame(tk.Frame):
'''
A Tkinter class creating a scrollable window that can be used
in a docked multiple document interface form. The window created here
allows addition of widgets to create scrolling tables (spreadsheet like),
groups of text fields, etc.
'''
def __init__(self, root):
self.count = 0
tk.Frame.__init__(self)
self.root = root
self.canvas = tk.Canvas(self, height=self.winfo_height(), width=self.winfo_width() )
self.canvas.grid(row=0, column=0, sticky='nsew')
self.vsb = tk.Scrollbar(self, orient='vertical', command=self.canvas.yview)
self.vsb.grid(row=0,column=1,sticky='ns')
self.hsb = tk.Scrollbar(self, orient='horizontal', command=self.canvas.xview)
self.hsb.grid(row=1,column=0,sticky='ew')
self.intframe = tk.Frame(self.canvas)
self.intframe.config(height=self.winfo_height(), width=self.winfo_width())
self.canvas.configure(yscrollcommand=self.vsb.set, xscrollcommand=self.hsb.set)
self.canvas.create_window(0, 0, window=self.intframe, anchor='nw')
#set event bindings
self.bind('<Configure>', self.OnFrameConfigure)
self.intframe.bind('<Configure>', self.OnIntFrameConfigure)
def OnFrameConfigure(self, event=None):
'''
adjust canvas when main frame adjusts
'''
self.canvas.configure(width=event.width - self.vsb.winfo_width()-2,
height=event.height - self.hsb.winfo_height()-2)
def OnIntFrameConfigure(self, event=None):
'''
adjust the scrolling window when the internal frame is adjusted
'''
self.canvas.configure(scrollregion=self.canvas.bbox(tk.ALL))
Here's an example of how I'm using it with textboxes that don't expand:
import Tkinter as tk
from scrollingchildframe import *
class Vis_GUI:
'''
The main GUI class
'''
def __init__(self):
#tkinter stuff
self.root = tk.Tk()
self.root.geometry('500x500')
self.create_frames()
self.root.mainloop()
def create_frames(self):
'''
Build the GUI frames
'''
self.root.columnconfigure(0,weight=1)
self.root.columnconfigure(1,weight=3)
self.root.rowconfigure(0,weight=1)
self.root.rowconfigure(1,weight=3)
#data blocks
self.block_frame = ScrollingChildFrame(self.root)
self.block_frame.config(height=200, width=200)
##error list
self.error_frame = ScrollingChildFrame(self.root)
self.error_frame.config(height=200, width=300)
##data
self.data_frame = ScrollingChildFrame(self.root)
self.data_frame.config(height=300, width=500)
##populate with empty cells
self.PopulateEmpty()
##place them on the grid
self.block_frame.grid(row=0, column=0, padx=2, pady=2, sticky='nsew')
self.error_frame.grid(row=0,column=1, padx=2, pady=2, sticky='nsew')
self.data_frame.grid(row=1, column=0, columnspan=2, padx=2,pady=2, sticky='nsew')
def PopulateEmpty(self):
'''
Populate the frames with empty contents so it doesn't look quite so empty.
'''
z = tk.Text(self.data_frame.intframe)
z.insert(tk.INSERT, 'blah\nblah\nblah')
height = float(z.index(tk.END))
z.config( height=height, state=tk.DISABLED, wrap=tk.NONE)
z.pack(anchor='nw', expand=1, fill=tk.X)
z = tk.Text(self.error_frame.intframe, height=1)
z.pack(anchor='w', expand = 1, fill=tk.X)
z = tk.Label(self.block_frame.intframe, text = 'No file open')
z.pack(anchor='w')
if (__name__=="__main__"):
wv = Vis_GUI()

The Frame also has to have expand and fill options set (and you will have to check on what ScrollingChildFrame does-and this is not a complaint about incomplete code, just pointing out the next step). Using just pack() for the Frame in the following code will not allow it to expand. You can uncomment it and comment the other pack if you want to see the difference.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
top=tk.Tk()
## use separate frame instead of top
fr=tk.Frame(top)
##fr.pack() ## does not expand
fr.pack(anchor='nw', expand=1, fill=tk.X)
z = tk.Text(fr)
insert_text="%s" % ("blah"*25) + 'blah\nblah\nblah'
z.insert(tk.INSERT, insert_text)
height = float(z.index(tk.END))
z.config( height=height, state=tk.DISABLED, wrap=tk.NONE)
z.pack(anchor='nw', expand=1, fill=tk.X)
top.mainloop()

Related

How do I get my frame sizes to match up to my root window size?

I'm making a text game. I used an entry widget for player input, a text widget for the game's output, and put them into frames. I set the root window's geometry and the frame sizes to fit into that geometry. However, the frame sizes are smaller than expected. Specifically, my story_text_frame is shorter than expected. I have done a tutorial, and am not sure what I am missing now.
import tkinter as tk
class Game(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs)
self.geometry('1280x720')
self.player_input_frame = tk.Frame(self, height=20, width=625)
self.player_input_field = tk.Entry(self.player_input_frame, background='black', foreground='white', relief='flat')
self.player_input_field.grid(row=0, column=0)
self.player_input_frame.grid(row=2, column=1)
self.story_text_frame = tk.Frame(self, height=670, width=625)
self.story_text_field = tk.Text(self.story_text_frame, background='grey', foreground='white')
self.story_text_field.grid(row=0, column=0)
self.story_text_frame.grid(row=1, column=1)
To have a widget size follow the size of a master widget or a window you must specify how this is to be done with columnconfigure() and rowconfigure(). Then you must expand the widget to fill the available cell space by instructing grid() to stick to the edges; sticky='nsew'. See my example:
import tkinter as tk
class Game(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs)
self.geometry('800x600')
self.columnconfigure(0, weight=1) # Specify how columns and rows will
self.rowconfigure(0, weight=1) # change when window size changes.
# The game's output Text() widget
self.story_text_field = tk.Text(self)
self.story_text_field.grid(row=0, column=0, sticky='nsew',
pady=10, padx=10) # Sticky expands the widget to fill the cell
# The player input Entry() widget
self.player_input_field = tk.Entry(self)
self.player_input_field.grid(row=1, column=0, pady=(0,10))
Game().mainloop()
I have removed the Frames to make the construction clearer. Also I removed the coloring of the widgets. I introduced some padding to make the result more pleasing to the eye and changed the window size to be easier to handle on my tiny screen.
For further information on grid() I can recommend effbot's The Tkinter Grid Geometry Manager. You can also read Mike - SMT's answer to Find position of another anchor than the anchor already used which elaborates on both grid() and pack().

Specific issue using pack while creating a layout

I must use tkinter library to create GUI.
I have this code:
# -*- coding: UTF-8 -*-
import tkinter as tk
class Application(tk.Frame):
resx=1600
resy=900
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.pack(fill="both", expand=1)
self.createWidgets()
master.minsize(self.resx, self.resy)
master.maxsize(self.resx, self.resy)
def createWidgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Create new window"
self.hi_there["command"] = self.PlayMode
self.hi_there.pack(side="top")
def ShowMenu(self, master):
print("Here I need DELETE master, in my case PlayM")
def PlayMode(self):
PlayM = tk.Tk()
PlayM.minsize(self.resx, self.resy)
PlayM.maxsize(self.resx, self.resy)
PlayM.title("Game")
bf= tk.Frame(PlayM, bg="blue")
bf.pack(side="bottom", fill=tk.X, expand = 1)
lbTEST=tk.Label(bf)
lbTEST["text"] = "TESTING"
lbTEST.pack()
mf = tk.Frame(PlayM,bg="red")
mf.pack(side="right", fill=tk.Y, expand=1)
self.LogOut = tk.Button(mf)
self.LogOut["text"] = "LOGOUT"
self.LogOut.pack()
self.LogOut["command"] = self.ShowMenu(PlayM)
root = tk.Tk()
app = Application(master=root)
app.master.title("Useless think")
app.mainloop()
I need something like this picture:
I don't know why my code is not working. When I pack my bf (bottom frame) and set side = "bottom", but it appears in the middle of the screen. Why?
Same with side = "right" when I pack mf (menu frame)
And I have one more question. About logout button. I set command's method "ShowMenu".
When I run my code, this method is started automatically only once, but when I click to button nothing happens. Why?
First, you have a critical flaw in your code. You should not be creating more than one instance of Tk. If you need to create additional windows, create instances of Toplevel.
When I pack my bf (bottom frame) and set side = "bottom", but it appears in the middle of the screen. Why?
You set expand to 1 for both mf and mf so each will end up taking half of the available space in the window. If you simply set expand to 0 (zero) or False for bf, it will only take up as much space as necessary.
bf.pack(side="bottom", fill=tk.X, expand = 0)
As a general rule, you should only set a true value for expand on a single widget (the "hero" widget), unless you want to distribute extra space equally among widgets.
When I run my code, this method is started automatically only once, but when I click to button nothing happens. Why?
Because you're telling it to run. Take a look at this code:
self.LogOut["command"] = self.ShowMenu(PlayM)
It is exactly the same as this code:
result = self.ShowMenu(PlayM)
self.logOut["command"] = result
See the problem?
The command attribute requires a reference to a function. Roughly translated, that means you can't use (). If you need to pass in an argument to the command, the typical solution is to use functools.partial or lambda to create a reference to an anonymous function that will call the real function with the argument:
self.logOut["command"] = lambda arg=PlayM: self.ShowMenu(arg)
Is there any specific reason why you use pack method instead of grid?
You could configure your Application frame like:
self.grid_rowconfigure(0, weight=1) #make the first "upper" row expand
self.grid_rowconfigure(1, weight=0) #leave the second "lower" row and do not expand it
self.grid_columnconfigure(0, weight=0) # first column, do not expand
self.grid_columnconfigure(1, weight=1) # second columnd, DO expand
self.grid_columnconfigure(0, weight=0) # third column, do not expand
on your mainframe/application class and then call:
bf.grid(row=1, column=0, columnspan=3, sticky=tk.NW+tk.SE) # span over three columns
lf.grid(row=0, column=0, sticky=tk.NW+tk.SE) # default is to span over one column
rf.grid(row=0, column=2, sticky=tk.NW+tk.SE)
Edit
I am very sorry, i forgot to mention about the logout button.
You call the event handler at binding time, therefore it will be executed there.
If you want to have it pass values either use:
command=lambda parent=PlayM: self.ShowMenu(parent)
use a class object to store youre parent reference to at creation time self._Parent = PlayM and use this inside ShowMenu
I personally prefer storing objects for single values. if you have many windows to destroy, I would use the lambda.
Apart from the problems already mentioned by Bryan Oakley and the solution given by R4PH43L using grid, here's a possible solution for your layout only using pack.
The idea is that by using side="left" (or side="right") and then side="top" (or side="bottom") does not work the way you may be expecting.
Within a frame you should just be using values for side (when packing the widgets in that same frame) which are either vertical ("top" or "bottom") or horizontal ("right" or "left"), but not both. Thus to accomplish layouts like yours using only pack you need additional frames!
# -*- coding: UTF-8 -*-
import tkinter as tk
class Application(tk.Frame):
resx=400
resy=200
def __init__(self, master=None):
tk.Frame.__init__(self, master, bg="red")
self.pack(fill="both", expand=1)
self.create_widgets()
master.geometry("400x200+100+500")
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Create new window"
self.hi_there["command"] = self.play_mode
self.hi_there.pack(side="top")
def show_menu(self, master):
print("Here I need DELETE master, in my case play_m")
def play_mode(self):
# Using a Toplevel instead of a Tk
# You should have just one Tk in your app
play_m = tk.Toplevel()
play_m.geometry("400x200+100+500")
play_m.title("Game")
top_frame = tk.Frame(play_m, bg="blue")
top_frame.pack(side="top", fill="both", expand=True)
left_top_frame = tk.Frame(top_frame, bg="white")
left_top_frame_label = tk.Label(left_top_frame, text="left top frame")
left_top_frame_label.pack()
left_top_frame.pack(side="left", fill="y")
middle_top_frame = tk.Frame(top_frame, bg="black")
middle_top_frame_button = tk.Button(middle_top_frame, text="Logout", command=play_m.destroy)
middle_top_frame_button.pack()
middle_top_frame.pack(side="left", fill="both", expand=True)
right_top_frame = tk.Frame(top_frame, bg="white")
right_top_frame_label = tk.Label(right_top_frame, text="right top frame")
right_top_frame_label.pack()
right_top_frame.pack(side="right", fill="y")
bottom_frame = tk.Frame(play_m, bg="yellow")
bottom_frame_label = tk.Label(bottom_frame, text="bottom frame")
bottom_frame_label.pack()
bottom_frame.pack(side="top", fill="both")
root = tk.Tk()
app = Application(master=root)
app.master.title("Useless think")
app.mainloop()
Here's the result of the main Tk window on a OS X (Sierra):
Second toplevel
I changed a little bit the sizes for the sake of exposition. I also renamed the methods to use _ and lower case letters.

Tkinter Text Widget Horizontal Fill

I have a small python program to build a GUI. I'm trying to use a text widget to create an easily scrollable window that contains vertically stacked frames. A frame is created on button press and added to the bottom of the text widget. This works fine; however, I'm struggling to get these frames to stretch to fill the text box horizontally.
import Tkinter as tk
class NewEntry(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
#self.pack(fill="x", expand=True) #possible error source
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0,weight=1)
textField = tk.Entry(self)
textField.grid(row=1, column=0, padx=2, pady=1, sticky="ew")
addButton = tk.Button(self, text="Add", cursor="arrow")
addButton.grid(row=1, column=1, padx=10, pady=2, sticky="ew")
newLabel = tk.Label(self, text="Test", bg="#5522FF")
newLabel.grid(row=0, padx=2, pady=2, sticky="ew", columnspan=2)
newLabel.columnconfigure(0, weight=1)
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent)
self.parent=parent
self.grid()
self.columnconfigure(0, weight=1)
self.grid_rowconfigure(0,weight=1)
self.text = tk.Text(self, wrap="none", bg="#AA3333")
vsb = tk.Scrollbar(orient="vertical", command=self.text.yview)
self.text.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
self.text.pack(fill="both", expand=True)
b = tk.Button(self, text="Button #%s" % 1, command=self.OnButtonClick)
self.text.window_create("end", window=b)
self.text.insert("end", "\n")
def OnButtonClick(self):
self.text.configure(state="normal")
panel = NewEntry(self.text, bg="#FF1111")
self.text.window_create("end", window=panel)
self.text.insert("end", "\n")
self.text.configure(state="disabled")
if __name__=="__main__":
root = tk.Tk()
root.resizable(True, True)
appinstance=MainApplication(root)
appinstance.pack(fill="both", expand=True)
root.mainloop()
I've read many different posts talking about grid_columnconfigure, fill options, sticky options, etc, but I haven't been able to get it filling properly. I am wondering if the window_create() method of the Text widget creates some sort of size limitation? It seems as though my code in NewEntry class is properly filling the space allowed by the window_create method, but I don't know how to create a "panel" to fill the width of the text box.
I am also aware of the possibility of using a canvas instead of text box (I'm wanting to maintain dynamic size and scrollability, though). I read from several different posts that a text widget is easiest if you have a simple stack of widgets, though. I will accept any recommendation, though.
The root of the problem is that a frame typically wants to fit its contents. So, when you add the label, entry widget, and button to the frame, it will shrink to fit. Any size you give to the frame will be ignored.
There are several solutions to this problem. What many people do is turn geometry propagation off (eg: self.grid_propagate(False)) but that means you have to manage both the width and height when all you really want is to control the width.
Another solution is to put something inside the panel that can be configured with an explicit width, and which will then cause the containing frame to grow to fit. For example, you can add an invisible frame in row 0 that sits behind the other widgets in row 0. When you change the width of this frame it will cause the containing frame to grow:
class NewEntry(tk.Frame):
def __init__(...):
...
# create this _before_ creating the other widgets
# so it is lowest in the stacking order
self.sizer = tk.Frame(self, height=1)
self.sizer.grid(row=0, columnspan=2)
...
With that, you can force the panel to be any width you want by setting the width of the sizer, but tkinter will continue to compute the optimum height for the widget:
self.sizer.configure(width=200)
Now you just need to set up bindings so that whenever the text widget changes size, you call this function to resize each entry.
For example, you might want to save all of the panels in a list so that you can iterate over them later.
class MainApplication(...):
def __init__(...):
...
self.panels = []
...
def OnButtonClick(...):
...
panel = NewEntry(...)
self.panels.append(panel)
...
With that, you can set up a binding that triggers whenever the window resizes:
class MainApplication(...):
def __init__(...):
...
self.text.bind("<Configure>", self.OnConfigure)
...
def OnConfigure(self, event):
for panel in self.panels:
panel.sizer.configure(width=event.width)
I wouldn't do it precisely like that since it tightly couples the implementation of the panel to the main window, but it illustrates the general technique of explicitly controlling the width of an embedded window.
Other solutions involve putting the panels inside a containing frame, and make that frame be the only widget added to the text widget. You could also use a canvas instead of a text widget since it allows you to explicitly set the width and height of embedded windows.

Enabling the "setgrid" option for a Tkinter widget

I am using the grid geometry manager of Tkinter to generate a table with horizontally scrollable text in column 2. I tried creating a Listbox widget that lives in column 2 (as a child of the overall frame) and spans all the rows. This seemed promising, until it became apparent that the lines of text in the Listbox are not aligned with the rows of the parent grid. I've been searching in vain for a way to pad each row of text in the Listbox so that the rows match up; but even if that were possible, I would prefer a more general, less kludgy solution.
I recently stumbled across a description of Gridded Geometry Management that alludes to a setgrid option for a widget. It purports to do exactly what I want: that is, "[determine] whether this widget controls the resizing grid for its top-level window." I tried enabling this option in my Listbox widget, but to no avail. Am I somehow misunderstanding the purpose/usage of setgrid?
(In order to see the problem with the code below, use the Select File or Select Folder buttons to load multiple audio files into the file list.)
#! /usr/bin/env python
#################################################
# This tool allows the user to select audio files
# (or folders containing audio files) and subject
# them to loudness analysis.
#################################################
import sys
import os
import codecs
import re
import Tkinter
from Tkinter import *
import tkFileDialog
from os import walk
from os import path
from Tkinter import Tk, Text, BOTH, W, N, E, S
from ttk import Frame, Button, Label, Style, Progressbar
from ScrolledText import *
from progressbar import ProgressBar
class Leveler_tk(Frame):
fileList = []
allowedExtensions = ['mp3','mp2','m4a','aiff','wav']
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=1)
self.columnconfigure(2, pad=250)
self.columnconfigure(3, weight=1)
self.columnconfigure(4, weight=1)
self.columnconfigure(5, weight=1)
lbl1 = Label(self, text="Analyze")
lbl1.grid(pady=4, padx=5,row=0,column=0)
lbl2 = Label(self, text="Adjust")
lbl2.grid(pady=4, padx=5,row=0,column=1)
lbl3 = Label(self, text="File")
lbl3.grid(pady=4, padx=5,row=0,column=2)
lbl4 = Label(self, text="Integrated\nLoudness")
lbl4.grid(pady=4, padx=5,row=0,column=3)
lbl5 = Label(self, text="LRA")
lbl5.grid(pady=4, padx=5,row=0,column=4)
lbl6 = Label(self, text="Peak")
lbl6.grid(pady=4, padx=5,row=0,column=5)
lbl7 = Label(self, text="Progress")
lbl7.grid(pady=4, padx=5,row=0,column=6)
lbl8 = Label(self, text="Meets\nSpecs?")
lbl8.grid(sticky=W, pady=4, padx=5,row=0,column=7)
file_btn = Button(self, text="Select File",command=self.selectFile)
file_btn.grid(row=1,rowspan=2, column=8,padx=5,pady=4)
folder_btn = Button(self, text="Select Folder", command=self.selectFolder)
folder_btn.grid(row=3, rowspan=2, column=8,padx=5,pady=4)
def render(self):
count = 0
filebox = Listbox(self,selectmode=EXTENDED,setgrid=1)
scrollbar = Scrollbar(filebox, orient=HORIZONTAL)
scrollbar.config(command=filebox.xview)
filebox.grid(row=1, column=2, rowspan=len(self.fileList), columnspan=1, sticky=N+S+E+W)
filebox.config(xscrollcommand=scrollbar.set)
scrollbar.pack(side=BOTTOM, fill=X)
for file in self.fileList:
analyze = IntVar()
adjust = IntVar()
Radiobutton(self, text="", variable=analyze, value=count, borderwidth=0).grid(row=count+1, column=0)
Radiobutton(self, text="", variable=adjust, value=count, borderwidth=0).grid(row=count+1, column=1)
filebox.insert(END, file + "\n")
Progressbar(self, orient=HORIZONTAL,length=100, mode='determinate').grid(row=count+1, column=6)
count += 1
def addToList(self, name):
dot = re.search("\.(?=[^.]*$)",name)
extension = name[dot.end():]
if extension in self.allowedExtensions and not name in self.fileList:
self.fileList.append(name)
def selectFile(self):
input = tkFileDialog.askopenfilename(filetypes = [('MP3', '*.mp3'), ('MP2', '*.mp2'), ('M4A', '*.m4a'), ('AIFF', '*.aiff'), ('WAV', '*.wav')], multiple = 1)
for el in input:
if os.path.isfile(el) and ".DS_Store" not in el:
try:
self.addToList(el)
except:
tkMessageBox.showerror("Some error")
self.render()
def selectFolder(self):
input = tkFileDialog.askdirectory()
for (dirpath, dirnames, filenames) in walk(input):
for name in filenames:
if name != ".DS_Store":
self.addToList(dirpath + "/" + name)
self.render()
def main():
root = Tk()
app = Leveler_tk(root)
root.mainloop()
if __name__ == "__main__":
main()
I think you are misunderstanding setgrid.
It is so that a widget with a natural size that is based on something other than a pixel (such as a text widget, whose size is based on characters) can prevent the parent from setting it to an unnatural size (eg: 20.5 characters). With setgrid, when you interactively resize the window it will resize in grid units (eg: character height or width) rather than pixels.
setgrid doesn't prevent resizing so much as that it makes sure resizing happens at multiples of some other unit.
Here is the complete, definitive reference for the setgrid option, from the tcl/tk manual:
Specifies a boolean value that determines whether this widget controls
the resizing grid for its top-level window. This option is typically
used in text widgets, where the information in the widget has a
natural size (the size of a character) and it makes sense for the
window's dimensions to be integral numbers of these units. These
natural window sizes form a grid. If the setGrid option is set to true
then the widget will communicate with the window manager so that when
the user interactively resizes the top-level window that contains the
widget, the dimensions of the window will be displayed to the user in
grid units and the window size will be
constrained to integral numbers of grid units. See the section GRIDDED
GEOMETRY MANAGEMENT in the wm manual entry for more details.

User interface optimization when using pack

I have a code, which creates an UI like this.
I want those page up/down buttons to be next to Page 1 label but I couldn't managed to do that. Only way I know with pack is the side option and it is not working well.
Second thing is, that scrollbar should be in that listbox. I know, I need to create a canvas then a frame in it. Then embed both listbox and scrollbar to them but I couldn't do that either.
This is my code.
class interface(tk.Frame):
def __init__(self,den):
self.pa_nu = 0 ##page number. Both used in labeling and result slicing
self.lbl1 = tk.Label(den, text="keyword")
self.lbl2 = tk.Label(den, text="Page %d" %(self.pa_nu+1))
self.ent1 = tk.Entry(den, takefocus=True)
self.btn1 = tk.Button(den, text="Search", command=self.button1)
self.btn2 = tk.Button(den, text="Page Up", command=self.page_up)
self.btn3 = tk.Button(den, text="Page Down", command=self.page_down)
scrollbar = tk.Scrollbar(den)
scrollbar.pack(side=RIGHT, fill=Y)
self.lst1 = tk.Listbox(den, selectmode="SINGLE", width="40", yscrollcommand=scrollbar.set)
self.lst1.bind("<Double-Button-1>", self.open_folder)
scrollbar.config(command=self.lst1.yview)
self.lbl1.pack(side="top")
self.ent1.pack()
self.btn1.pack(side="top")
self.btn2.pack(side="right")
self.btn3.pack(side="left")
self.lbl2.pack(side="bottom",padx=65)
self.lst1.pack(fill=BOTH)
def button1(self):
pass #some stuff here
def page_up(self):
pass #some stuff here
def page_down(self):
pass #some stuff here
def list_fill(self,i):
pass #some stuff here
def open_folder(self,event):
pass #some stuff here
There are three geometry managers in tkinter: place (absolute position), pack (good for line of widgets, or simple layout) and grid (complex layout).
Grid is worth looking for the layout you are working on. If you keep going with pack, the usual way to achieve complex layout is to use intermediate frames. For instance, in the following picture, all widgets in frame1 are packed vertically, and horizontally in frame2.
diagram with draw.io
Regarding the scrollbar, the usual way is again to use an intermediate frame (no need for a canvas). Here a snippet (copied from http://effbot.org/zone/tkinter-scrollbar-patterns.htm#listbox)
frame = Frame(root, bd=2, relief=SUNKEN)
scrollbar = Scrollbar(frame)
scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(frame, bd=0, yscrollcommand=scrollbar.set)
listbox.pack()
scrollbar.config(command=listbox.yview)
frame.pack() #or others...

Categories