How do you keep a widget in view while scrolling? - python

I am using Tix to automatically create a scroll bar as the content changes. I want to keep a button or two in the user's view while they scroll through the contents of the application.
I haven't seen this question for Tkinter/Tix yet so I thought I'd ask.
The following code will create a sample of the problem where the button is at a fixed point in the window, and is subject to being scrolled.
from Tkinter import *
import Tix
class some_GUI:
def __init__(self, root):
sw= Tix.ScrolledWindow(root, scrollbar=Tix.AUTO)
sw.pack(fill=Tix.BOTH, expand=1)
frame1 = Frame(sw.window)
frame1.grid(row = 0, column = 1)
frame2 = Frame(sw.window)
frame2.grid(row = 0, column = 2)
def quit():
root.quit()
for i in range(0,300):
label1 = Label(frame1, text = "foo")
label1.grid(row = i, column = 0)
button = Button(frame2, text = "Quit", command = quit)
button.pack()
root = Tix.Tk()
display = some_GUI(root)
root.mainloop()
I want the button(s) to be in "frame2" and centered vertically relative to the application's window. I tried using winfo_height/winfo_width to find the frame's height/ width to work with update, but these values didn't change with the addition of the labels and button.
Attempted/possible solutions:
I put frame2 in sw.subwidgets_all[1] by doing the following:
frame1.pack(side = LEFT)
frame2 = Frame(sw.subwidgets_all()[1])
frame2.pack(side = RIGHT)
button = Button(frame2, text = "Quit", command = quit)
button.pack(side = RIGHT)
This allows the fixed position relative to the application, but the window resizes relative to the button's parent instead of frame1. Another drawback is that the horizontal scrollbar is only relative to frame1.
Find the midpoint of the scrollbar and update the position of the buttons relative to those coordinates using place(maybe?) not sure how to accomplish this, and seeing SO solutions in general I think this might be an inefficient way of doing this.
EDIT: Although this isn't exactly what I had in mind, the following code works as per falsetru's suggestion in the comments:
from Tkinter import *
import Tix
class some_GUI:
def __init__(self, root):
def quit():
root.quit()
frame2 = Frame(root)
frame2.pack(side = RIGHT)
button = Button(frame2, text = "Quit", command = quit)
button.pack()
frame1 = Frame(root)
frame1.pack(side = LEFT)
sw= Tix.ScrolledWindow(frame1, scrollbar=Tix.AUTO)
sw.pack(fill=Tix.BOTH, expand=1)
for widget in sw.subwidgets_all():
print widget
for i in range(0,300):
label1 = Label(sw.window, text = "foo")
label1.grid(row = i, column = i)
print root.winfo_toplevel()
for widget in sw.subwidgets_all():
print widget
root = Tix.Tk()
display = some_GUI(root)
root.mainloop()

You can put the button out of ScrollWindows:
import Tix
from Tkinter import *
def build_ui(root):
sw = Tix.ScrolledWindow(root, scrollbar=Tix.AUTO)
sw.pack(side=LEFT, fill=Tix.BOTH, expand=1)
for i in range(300):
label1 = Label(sw.window, text="foo")
label1.grid(row=i, column=0)
button = Button(root, text="Quit", command=root.quit)
button.pack(side=RIGHT)
root = Tix.Tk()
build_ui(root)
root.mainloop()

The second option you mentioned could be the one that satisfies your situation, however that is computationally expensive as you will need to delete the button(s) and redraw them over and over relatively to the scrollbar up/down motion. Not only this is ugly by design but it can be an obstacle for any further scalability of your application or even lead to unexpected bugs if your application runs some serious operations.
The only realistic solution I see for your problem is to fix the button(s) on (for example the bottom of) the upper canvas (or whatever region you want to set) and outside the scrollable region as #falsetru commented you.

Related

How to shrink a frame in tkinter after removing contents?

Most of the topics I came across deals with how to not shrink the Frame with contents, but I'm interested in shrinking it back after the destruction of said contents. Here's an example:
import tkinter as tk
root = tk.Tk()
lbl1 = tk.Label(root, text='Hello!')
lbl1.pack()
frm = tk.Frame(root, bg='black')
frm.pack()
lbl3 = tk.Label(root, text='Bye!')
lbl3.pack()
lbl2 = tk.Label(frm, text='My name is Foo')
lbl2.pack()
So far I should see this in my window:
Hello!
My name is Foo
Bye!
That's great, but I want to keep the middle layer interchangeable and hidden based on needs. So if I destroy the lbl2 inside:
lbl2.destroy()
I want to see:
Hello!
Bye!
But what I see instead:
Hello!
███████
Bye!
I want to shrink frm back to basically non-existence because I want to keep the order of my main widgets intact. Ideally, I want to run frm.pack(fill=tk.BOTH, expand=True) so that my widgets inside can scale accordingly. However if this interferes with the shrinking, I can live without fill/expand.
I've tried the following:
pack_propagate(0): This actually doesn't expand the frame at all past pack().
Re-run frm.pack(): but this ruins the order of my main widgets.
.geometry(''): This only works on the root window - doesn't exist for Frames.
frm.config(height=0): Oddly, this doesn't seem to change anything at all.
frm.pack_forget(): From this answer, however it doesn't bring it back.
The only option it leaves me is using a grid manager, which works I suppose, but not exactly what I'm looking for... so I'm interested to know if there's another way to achieve this.
When you destroy the last widget within a frame, the frame size is no longer managed by pack or grid. Therefore, neither pack nor grid knows it is supposed to shrink the frame.
A simple workaround is to add a small 1 pixel by 1 pixel window in the frame so that pack still thinks it is responsible for the size of the frame.
Here's an example based off of the code in the question:
import tkinter as tk
root = tk.Tk()
lbl1 = tk.Label(root, text='Hello!')
lbl1.pack()
frm = tk.Frame(root, bg='black')
frm.pack()
lbl3 = tk.Label(root, text='Bye!')
lbl3.pack()
lbl2 = tk.Label(frm, text='My name is Foo')
lbl2.pack()
def delete_the_label():
lbl2.destroy()
if len(frm.winfo_children()) == 0:
tmp = tk.Frame(frm, width=1, height=1, borderwidth=0, highlightthickness=0)
tmp.pack()
root.update_idletasks()
tmp.destroy()
button = tk.Button(root, text="Delete the label", command=delete_the_label)
button.pack()
root.mainloop()
Question: Shrink a Frame after removing the last widget?
Bind to the <'Expose'> event and .configure(height=1) if no children.
Reference:
Expose
An Expose event is generated whenever all or part of a widget should be redrawn
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
tk.Label(self, text='Hello!').pack()
self.frm = frm = tk.Frame(self, bg='black')
frm.pack()
tk.Label(self, text='Bye!').pack()
tk.Label(frm, text='My name is Foo').pack()
self.menubar = tk.Menu()
self.config(menu=self.menubar)
self.menubar.add_command(label='delete', command=self.do_destroy)
self.menubar.add_command(label='add', command=self.do_add)
frm.bind('<Expose>', self.on_expose)
def do_add(self):
tk.Label(self.frm, text='My name is Foo').pack()
def do_destroy(self):
w = self.frm
if w.children:
child = list(w.children).pop(0)
w.children[child].destroy()
def on_expose(self, event):
w = event.widget
if not w.children:
w.configure(height=1)
if __name__ == "__main__":
App().mainloop()
Question: Re-run frm.pack(): but this ruins the order of my main widgets.
frm.pack_forget(), however it doesn't bring it back.
Pack has the options before= and after. This allows to pack a widget relative to other widgets.
Reference:
-before
Use its master as the master for the slaves, and insert the slaves just before other in the packing order.
Example using before= and self.lbl3 as anchor. The Frame are removed using .pack_forget() if no children and get repacked at the same place in the packing order.
Note: I show only the relevant parts!
class App(tk.Tk):
def __init__(self):
...
self.frm = frm = tk.Frame(self, bg='black')
frm.pack()
self.lbl3 = tk.Label(self, text='Bye!')
self.lbl3.pack()
...
def on_add(self):
try:
self.frm.pack_info()
except:
self.frm.pack(before=self.lbl3, fill=tk.BOTH, expand=True)
tk.Label(self.frm, text='My name is Foo').pack()
def on_expose(self, event):
w = event.widget
if not w.children:
w.pack_forget()
Tested with Python: 3.5 - 'TclVersion': 8.6 'TkVersion': 8.6

How to put a widget beneath widgets that are side-by-side using pack?

I try to put the widgets like this:
I don't understand why my code doesn't do that, tried to look for examples online but didn't find a solution and nothing I tried brought me closer to the requested result.
This is my code so far(if you have any comments about anything in the code feel free to tell me because it's my first try with tkinter and GUIs in general):
from Tkinter import *
class box(object):
def __init__ (self, colour,s):
self.root = root
self.listbox = Listbox(self.root, fg = colour, bg = 'black')
self.s = s
self.place_scrollbar()
self.listbox.pack(side = self.s)
def place_scrollbar(self):
scrollbar = Scrollbar(self.root)
scrollbar.pack(side = self.s, fill = Y)
self.listbox.config(yscrollcommand = scrollbar.set)
scrollbar.config(command = self.listbox.yview)
def write(self, contenet):
self.listbox.insert(END, contenet)
root = Tk()
root.resizable(False, False)
boxs = Frame(root)
boxs.pack()
box.root = boxs
server = box("red", LEFT)
client = box("green", RIGHT )
bf = Frame(root)
bf.pack(side = BOTTOM)
entry = Entry(bf,bg ='black', fg = 'white')
entry.pack()
root.mainloop()
You can't do this without using an additional frame to contain the box objects while still using pack, while still maintaining resizability.
But it is more organized in some cases to: use an additional frame to contain your box objects, by initializing it with a parent option.
Right now the widgets inside the box class are children to global root object. Which isn't really a good practice. So let's first pass and use a parent object to be used for widgets inside.
Replace:
def __init__ (self, colour,s):
self.root = root
self.listbox = Listbox(self.root, ...)
...
def place_scrollbar(self):
scrollbar = Scrollbar(self.root)
...
with:
def __init__ (self, parent, colour,s):
self.parent= parent
self.listbox = Listbox(self.parent, ...)
...
def place_scrollbar(self):
scrollbar = Scrollbar(self.parent)
...
This makes it so that you now need to initialize the object like the following:
server = box(root, "red", LEFT)
client = box(root, "green", RIGHT )
Now that we can pass a parent widget, let's create a parent frame to contain them. Actually, there's an un-used frame already, boxs let's use that by passing it as the parent as opposed to root:
server = box(boxs, "red", LEFT)
client = box(boxs, "green", RIGHT )
Now everything looks fine, optionally if you want to make it so that entry occupies as much left space as possible currently add fill='x' as an option to the pack of both the entry and the frame that contains it:
bf.pack(side = BOTTOM, fill='x')
...
entry.pack(fill='x')
Your whole code should look like:
from Tkinter import *
class box(object):
def __init__ (self, parent, colour,s):
self.parent = parent
self.listbox = Listbox(self.parent, fg = colour, bg = 'black')
self.s = s
self.place_scrollbar()
self.listbox.pack(side = self.s)
def place_scrollbar(self):
scrollbar = Scrollbar(self.parent)
scrollbar.pack(side = self.s, fill = Y)
self.listbox.config(yscrollcommand = scrollbar.set)
scrollbar.config(command = self.listbox.yview)
def write(self, contenet):
self.listbox.insert(END, contenet)
root = Tk()
root.resizable(False, False)
boxs = Frame(root)
boxs.pack()
box.root = boxs
server = box(boxs, "red", LEFT)
client = box(boxs, "green", RIGHT )
bf = Frame(root)
bf.pack(side = BOTTOM, fill='x')
entry = Entry(bf,bg ='black', fg = 'white')
entry.pack(fill='x')
root.mainloop()
Or: use grid instead of pack (with columnspan=2 option for entry).
General Answer
More generally putting a widget beneath two widgets that are side-by-side can be done by:
Encapsulating the side-by-side widgets with a frame, and then simply putting the frame above the other widget:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
def main():
root = tk.Tk()
side_by_side_widgets = dict()
the_widget_beneath = tk.Entry(root)
frame = tk.Frame(root)
for name in {"side b", "y side"}:
side_by_side_widgets[name] = tk.Label(frame, text=name)
side_by_side_widgets[name].pack(side='left', expand=True)
frame.pack(fill='x')
the_widget_beneath.pack()
root.mainloop()
if __name__ == '__main__':
main()
Using grid:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
def main():
root = tk.Tk()
side_by_side_widgets = dict()
the_widget_beneath = tk.Entry(root)
for index, value in enumerate({"side b", "y side"}):
side_by_side_widgets[value] = tk.Label(root, text=value)
side_by_side_widgets[value].grid(row=0, column=index)
the_widget_beneath.grid(row=1, column=0, columnspan=2)
root.mainloop()
if __name__ == '__main__':
main()
Without using additional frames, by calling pack for the_widget_beneath with side='bottom' as the first pack call, as in Bryan's comment:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
def main():
root = tk.Tk()
side_by_side_widgets = dict()
the_widget_beneath = tk.Entry(root)
the_widget_beneath.pack(side='bottom')
for name in {"side b", "y side"}:
side_by_side_widgets[name] = tk.Label(root, text=name)
side_by_side_widgets[name].pack(side='left', expand=True)
root.mainloop()
if __name__ == '__main__':
main()
You can more easily notice reliability to global objects by creating a global main method, and add main-body of your script there and call:
...
def main():
root = Tk()
root.resizable(False, False)
boxs = Frame(root)
boxs.pack()
box.root = boxs
server = box(boxs, "red", LEFT)
client = box(boxs, "green", RIGHT )
bf = Frame(root)
bf.pack(side = BOTTOM, fill='x')
entry = Entry(bf,bg ='black', fg = 'white')
entry.pack(fill='x')
root.mainloop()
if __name__ == '__main__':
main()
How to put a widget beneath two widgets that are side-by-side using pack?
For a very simple layout like in your diagram, you simply need to pack the thing on the bottom first. That is because pack uses a "cavity" model. Each widget is organized in an unfilled cavity. Once that widget has been placed, that portion of the cavity is filled, and is unavailable for any other widgets.
In your case, you want the bottom cavity to be filled with the entry widget, so you should pack it first. Then, in the remaining upper cavity you can place your two frames side-by side, one on the left and one on the right.
For example:
import Tkinter as tk
root = tk.Tk()
entry = tk.Entry(root)
frame1 = tk.Frame(root, width=100, height=100, background="red")
frame2 = tk.Frame(root, width=100, height=100, background="green")
entry.pack(side="bottom", fill="x")
frame1.pack(side="left", fill="both", expand=True)
frame2.pack(side="right", fill="both", expand=True)
root.mainloop()
In the body of your question things get a bit more complicated, as you don't just have three widgets like your title suggests, you have several, with some being packed in the root and some being packed elsewhere, with pack statements scattered everywhere.
When using pack, it's best to group widgets into vertical or horizontal slices, and not mix top/bottom with left/right within the same group. It almost seems like you're trying to do that with your boxes, but then you don't -- your "box" is actually two widgets.
Bottom line: be organized. It also really, really helps if all of your pack (or place or grid) statements for a given parent are all in the same block of code. When you scatter them around it makes it impossible to visualize, and impossible to fix. Also, make sure that widgets have the appropriate parents.

Force TkInter Scale slider to snap to mouse

When a GUI has a TkInter Scale and they click somewhere on the scale, the default behavior seems to be to slide the slider along the Scale in the direction towards the mouse (and then unexpectedly past their mouse).
What I'd want instead is to have the slider always jump to and stay attached to the user's mouse point while they're clicking anywhere on the slider. If they click to a particular point on the Scale, the slider should jump directly to that point.
I have some code below which attempts to do this but doesn't seem to work and I cannot find the reason for it.
import tkinter as tk
from tkinter import ttk
def show_values():
print('w1 set to',w1.get())
def snapToVal1(val):
scaleVal = float(w1.get())
if int(scaleVal) != scaleVal:
w1.set(round(float(val)))
def scaleFunc1(event):
g = w1.grid_info()
w1.set(round(8 * (event.y - g['pady'])/(w1.winfo_height() - 2*g['pady'] - 2*g['ipady']))-1)
print('w1 set to',w1.get())
#---
root = tk.Tk()
f1 = ttk.Frame(root, relief = tk.GROOVE)
ttk.Label(f1, text='Stellar\nType').grid(row=0,column=0, columnspan=2,padx=2,pady=2)
for i,text in enumerate(['O','B','A','F','G','K','M','L']):
ttk.Label(f1, text = text).grid(row=i+1,column=0,pady=5,padx=(2,0))
w1 = ttk.Scale(f1, to=7, command=snapToVal1, orient=tk.VERTICAL)
w1.grid(row = 1, column = 1, rowspan = 8, pady=5, padx=2, sticky='nsew')
w1.bind('<Button-1>',scaleFunc1)
f1.grid(row = 0, column = 0,padx=(2,1),pady=2,sticky='nsew')
ttk.Button(root, text='Show', command=show_values).grid(row=1,column=0)
root.mainloop()
The pertinent function here is scaleFunc1. The idea is to have this called whenever the user presses their mouse button on the scale. It then tries to calculate, from the event pixel location and the Scale size, the fractional position of the click on the scale, convert this to a scale value, and set it to that value where the user clicked. However, I'm finding that the slider doesn't always jump to the same place, even if it reports it was set to the value I'd expect. What's going on?
I suspect it has something to do with the slide still trying to move for the fraction of a second the user keeps the mouse button pressed down.
That's actually the default right-click behavior. If you want to make the left click do that too, then the easiest thing is to simply detect leftclick and tell tkinter it was a right click instead:
import tkinter as tk
from tkinter import ttk
class Scale(ttk.Scale):
"""a type of Scale where the left click is hijacked to work like a right click"""
def __init__(self, master=None, **kwargs):
ttk.Scale.__init__(self, master, **kwargs)
self.bind('<Button-1>', self.set_value)
def set_value(self, event):
self.event_generate('<Button-3>', x=event.x, y=event.y)
return 'break'
def show_values():
print('w1 set to',w1.get())
root = tk.Tk()
f1 = ttk.Frame(root, relief = tk.GROOVE)
ttk.Label(f1, text='Stellar\nType').grid(row=0,column=0, columnspan=2,padx=2,pady=2)
for i,text in enumerate(['O','B','A','F','G','K','M','L']):
ttk.Label(f1, text = text).grid(row=i+1,column=0,pady=5,padx=(2,0))
w1 = Scale(f1, to=7, orient=tk.VERTICAL)
w1.grid(row = 1, column = 1, rowspan = 8, pady=5, padx=2, sticky='nsew')
f1.grid(row = 0, column = 0,padx=(2,1),pady=2,sticky='nsew')
ttk.Button(root, text='Show', command=show_values).grid(row=1,column=0)
root.mainloop()
from tkinter import *
root = Tk()
root.geometry('500x200')
scale = Scale(to=100, length=300, orient=HORIZONTAL)
scale.pack()
def move_to_click(event):
new_coord = (event.x - 18 + (event.x / 10)) / (300 / 100)
scale.set(new_coord)
scale.bind("<Button-1>", move_to_click)
root.mainloop()

python pack() and grid() methods together

Im new to python so please forgive my Noob-ness. Im trying to create a status bar at the bottom of my app window, but it seems every time I use the pack() and grid() methods together in the same file, the main app window doesn't open. When I comment out the line that says statusbar.pack(side = BOTTOM, fill = X) my app window opens up fine but if I leave it in it doesn't, and also if I comment out any lines that use the grid method the window opens with the status bar. It seems like I can only use either pack() or grid() but not both. I know I should be able to use both methods. Any suggestions? Here's the code:
from Tkinter import *
import tkMessageBox
def Quit():
answer = tkMessageBox.askokcancel('Quit', 'Are you sure?')
if answer:
app.destroy()
app = Tk()
app.geometry('700x500+400+200')
app.title('Title')
label_1 = Label(text = "Enter number")
label_1.grid(row = 0, column = 0)
text_box1 = DoubleVar()
input1 = Entry(app, textvariable = text_box1)
input1.grid(row = 0, column = 2)
statusbar = Label(app, text = "", bd = 1, relief = SUNKEN, anchor = W)
statusbar.pack(side = BOTTOM, fill = X)
startButton = Button(app, text = "Start", command = StoreValues).grid(row = 9, column = 2, padx = 15, pady = 15)
app.mainloop()
Any help is appreciated! Thanks!
You cannot use both pack and grid on widgets that have the same master. The first one will adjust the size of the widget. The other will see the change, and resize everything to fit it's own constraints. The first will see these changes and resize everything again to fit its constraints. The other will see the changes, and so on ad infinitum. They will be stuck in an eternal struggle for supremacy.
While it is technically possible if you really, really know what you're doing, for all intents and purposes you can't mix them in the same container. You can mix them all you want in your app as a whole, but for a given container (typically, a frame), you can use only one to manage the direct contents of the container.
A very common technique is to divide your GUI into pieces. In your case you have a bottom statusbar, and a top "main" area. So, pack the statusbar along the bottom and create a frame that you pack above it for the main part of the GUI. Then, everything else has the main frame as its parent, and inside that frame you can use grid or pack or whatever you want.
Yeah thats right. In following example, i have divided my program into 2 frames. frame1 caters towards menu/toolbar and uses pack() methods wherein frame2 is used to make login page credentials and uses grid() methods.
from tkinter import *
def donothing():
print ('IT WORKED')
root=Tk()
root.title(string='LOGIN PAGE')
frame1=Frame(root)
frame1.pack(side=TOP,fill=X)
frame2=Frame(root)
frame2.pack(side=TOP, fill=X)
m=Menu(frame1)
root.config(menu=m)
submenu=Menu(m)
m.add_cascade(label='File',menu=submenu)
submenu.add_command(label='New File', command=donothing)
submenu.add_command(label='Open', command=donothing)
submenu.add_separator()
submenu.add_command(label='Exit', command=frame1.quit)
editmenu=Menu(m)
m.add_cascade(label='Edit', menu=editmenu)
editmenu.add_command(label='Cut',command=donothing)
editmenu.add_command(label='Copy',command=donothing)
editmenu.add_command(label='Paste',command=donothing)
editmenu.add_separator()
editmenu.add_command(label='Exit', command=frame1.quit)
# **** ToolBar *******
toolbar=Frame(frame1,bg='grey')
toolbar.pack(side=TOP,fill=X)
btn1=Button(toolbar, text='Print', command=donothing)
btn2=Button(toolbar, text='Paste', command=donothing)
btn3=Button(toolbar, text='Cut', command=donothing)
btn4=Button(toolbar, text='Copy', command=donothing)
btn1.pack(side=LEFT,padx=2)
btn2.pack(side=LEFT,padx=2)
btn3.pack(side=LEFT,padx=2)
btn4.pack(side=LEFT,padx=2)
# ***** LOGIN CREDENTIALS ******
label=Label(frame2,text='WELCOME TO MY PAGE',fg='red',bg='white')
label.grid(row=3,column=1)
label1=Label(frame2,text='Name')
label2=Label(frame2,text='Password')
label1.grid(row=4,column=0,sticky=E)
label2.grid(row=5,column=0,sticky=E)
entry1=Entry(frame2)
entry2=Entry(frame2)
entry1.grid(row=4,column=1)
entry2.grid(row=5,column=1)
chk=Checkbutton(frame2,text='KEEP ME LOGGED IN')
chk.grid(row=6,column=1)
btn=Button(frame2,text='SUBMIT')
btn.grid(row=7,column=1)
# **** StatusBar ******************
status= Label(root,text='Loading',bd=1,relief=SUNKEN,anchor=W)
status.pack(side=BOTTOM, fill=X)
For single widgets, the answer is no: you cannot use both pack() and grid() inside one widget. But you can use both for different widgets, even if they are all inside the same widget. For instance:
my_canvas=tk.Canvas(window1,width=540,height=420,bd=0)
my_canvas.create_image(270,210,image=bg,anchor="center")
my_canvas.pack(fill="both",expand=True)
originallbl = tk.Label(my_canvas, text="Original", width=15).grid(row=1,column=1)
original = tk.Entry(my_canvas, width=15).grid(row=1,column=2)
I used pack() in order to set canvas, I used grid() in order to set labels, buttons, etc. in the same canvas

Tix ScrolledListbox not scrollable

Hello and happy new year,
i'm trying to build a user interface and have a
problem with the Tix.ScrolledListbox.
(Python 2.6.5, Tix 8.4.3, Windows XP)
I wanted to use it to show items of varying number,
depending on a previous choice made by the user.
It's a GIS thing: the user picks a layer from a
ComboBox, presses a button and the Listbox
shows all fieldnames retrieved from the attribute
table. So for some layers there are 5, for others
30 fields. In principle it works.
But the scrollbar next to the listbox remains
grey with no function.
In a small test snippet, where, after pressing
a button, a random (0..100) number of items is shown
in the listbox the scrollbar works.
I have no idea.
Anybody had this before?
Edit: The following samplecode shows a not scrollable
scrolledListbox when arcpy is imported
import Tix
import random
import arcpy
class SampleApp(object):
def __init__(self):
self.window = Tix.Tk()
#listbox
self.lbx = Tix.ScrolledListBox(self.window, width = 30)
self.lbx.listbox.insert(Tix.END, " ")
self.lbx.listbox.bind("<<ListboxSelect>>", self.Choose)
#button to generate new list
self.btn = Tix.Button(self.window, text = "new list",
command = self.NewList)
#label shows chosen list item
self.lbl = Tix.Label(self.window, text = " ", bg = "white")
#pack
self.btn.pack(pady = 10)
self.lbx.pack(side="top", fill="both", expand=True, pady = 10)
self.lbl.pack(pady = 10)
self.window.mainloop()
#function to generate new listbox items on button command
def NewList(self):
self.lbx.listbox.delete(0, Tix.END)
r = random.randint(1, 30)
for i in range(r):
self.lbx.listbox.insert(Tix.END, i)
#event to show selected item in label
def Choose(self, event):
widget = event.widget
selection = widget.curselection()
value = widget.get(selection[0])
self.lbl.config(text = value)
sa = SampleApp()
Did you attach the scrollbar to the listbox?
from Tkinter import *
root = Tk()
scrollbar = Scrollbar(root)
scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(root)
listbox.pack()
for i in range(100):
listbox.insert(END, i)
# attach listbox to scrollbar
listbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=listbox.yview)
mainloop()
stolen from: http://effbot.org/zone/tkinter-scrollbar-patterns.htm
edit: Tix.ScrolledListBox works differently so don't mix it up with the above solution.

Categories

Resources