How to add Autoscroll on insert in Tkinter Listbox? - python

I'm using a listbox (with scrollbar) for logging:
self.listbox_log = Tkinter.Listbox(root, height = 5, width = 0,)
self.scrollbar_log = Tkinter.Scrollbar(root,)
self.listbox_log.configure(yscrollcommand = self.scrollbar_log.set)
self.scrollbar_log.configure(command = self.listbox_log.yview)
Now, when I do:
self.listbox_log.insert(END,str)
I want the inserted element to be selected. I've tried:
self.listbox_log.selection_anchor(END)
but that doesn't work... Please suggest a solution...

AFAIK the ScrollBar widget doesn't have an auto-scroll feature, but it can be easily implemented by calling the listBox's yview() method after you insert a new item. If you need the new item to be selected then you can do that manually too using the listbox's select_set method.
from Tkinter import *
class AutoScrollListBox_demo:
def __init__(self, master):
frame = Frame(master, width=500, height=400, bd=1)
frame.pack()
self.listbox_log = Listbox(frame, height=4)
self.scrollbar_log = Scrollbar(frame)
self.scrollbar_log.pack(side=RIGHT, fill=Y)
self.listbox_log.pack(side=LEFT,fill=Y)
self.listbox_log.configure(yscrollcommand = self.scrollbar_log.set)
self.scrollbar_log.configure(command = self.listbox_log.yview)
b = Button(text="Add", command=self.onAdd)
b.pack()
#Just to show unique items in the list
self.item_num = 0
def onAdd(self):
self.listbox_log.insert(END, "test %s" %(str(self.item_num))) #Insert a new item at the end of the list
self.listbox_log.select_clear(self.listbox_log.size() - 2) #Clear the current selected item
self.listbox_log.select_set(END) #Select the new item
self.listbox_log.yview(END) #Set the scrollbar to the end of the listbox
self.item_num += 1
root = Tk()
all = AutoScrollListBox_demo(root)
root.title('AutoScroll ListBox Demo')
root.mainloop()

try to do it in this way. (I have copied from another question: How to auto-scroll a gtk.scrolledwindow?) It works fine for me.
def on_TextOfLog_size_allocate(self, widget, event, data=None):
adj = self.scrolled_window.get_vadjustment()
adj.set_value( adj.upper - adj.page_size )

Related

Pass selection Listbox topwindow to main window

This is my first real Python project. I am currently developing a GUI in Tkinter that allows the user to select Tasks and CVs to automatically compile documents using standard predefined task and CV texts from a database.
I have created two "Add" buttons in the main window to add Tasks and CVs that show a popup Listbox that allow the user to select the Tasks and CVs they want to have included in the commercial proposal. I have managed to create the popup window as a separate Class and it stores the selected Tasks in a list, but now I need to pass the list with the selected items to the Listbox in the main window when the user clicks the Select button in the popup window, but I cannot get my head around on how to do that.
I have researched on different fora and watched a variety of Youtube videos, but all focus on entry popups or some sort.
This is the code for the main window:
from tkinter import *
from Add import *
# make main window
root = Tk()
theLabel = Label(root, text="ProposalBuilder")
theLabel.grid(row=0)
# make frames
taskFrame = Frame(root)
taskFrame.grid(row=1, column=0)
CVFrame = Frame(root)
CVFrame.grid(row=1, column=1)
buildFrame = Frame(root)
buildFrame.grid(row=2, columnspan=2)
# add labels to frames
taskLabel = Label(taskFrame, text="Tasks")
taskLabel.pack()
CVLabel = Label(CVFrame, text="CVs")
CVLabel.pack()
# add listboxes to frames
scrollTask = Scrollbar(taskFrame, orient=VERTICAL)
listTask = Listbox(taskFrame, selectmode=MULTIPLE, yscrollcommand=scrollTask.set)
scrollTask.config(command=listTask.yview)
scrollTask.pack(side=RIGHT, fill=Y)
listTask.pack()
scrollCV = Scrollbar(CVFrame, orient=VERTICAL)
listCV = Listbox(CVFrame, selectmode=MULTIPLE, yscrollcommand=scrollCV.set)
scrollCV.config(command=listCV.yview)
scrollCV.pack(side=RIGHT, fill=Y)
listCV.pack()
# add commands to buttons
def addTask():
taskBox = Add('C:\\Users\\204703\\ProposalBuilder\\Database')
sel_test = taskBox.selection
def addCV():
CVBox = Add('C:\\Users\\204703\\ProposalBuilder\\Database')
# add buttons to frames
buttonAddTask = Button(taskFrame, text="Add", command=addTask)
buttonAddTask.pack(fill=X)
buttonDelTask = Button(taskFrame, text="Delete")
buttonDelTask.pack(fill=X)
buttonUpTask = Button(taskFrame, text="Up")
buttonUpTask.pack(fill=X)
buttonDownTask = Button(taskFrame, text="Down")
buttonDownTask.pack(fill=X)
buttonAddCV = Button(CVFrame, text="Add", command=addCV)
buttonAddCV.pack(fill=X)
buttonDelCV = Button(CVFrame, text="Delete")
buttonDelCV.pack(fill=X)
buttonUpCV = Button(CVFrame, text="Up")
buttonUpCV.pack(fill=X)
buttonDownCV = Button(CVFrame, text="Down")
buttonDownCV.pack(fill=X)
buttonBuild = Button(buildFrame, text="Build Proposal")
buttonBuild.pack(side=RIGHT)
root.mainloop()
This is the code for the separate class I created for the popup window:
from tkinter import*
from os import *
class Add:
def __init__(self, path):
# the slected tasks
self.selection = []
# make a frame
top = Toplevel()
# get file names from the directory (path) and save in list
self.path = path
self.dirList = listdir(self.path)
# add listbox to frames and populate with file names
self.scrollList = Scrollbar(top, orient=VERTICAL)
self.listbox = Listbox(top, selectmode=MULTIPLE, yscrollcommand=self.scrollList.set)
self.scrollList.config(command=self.listbox.yview)
self.scrollList.pack(side=RIGHT, fill=Y)
for item in self.dirList:
self.listbox.insert(END,item)
self.listbox.pack()
# add buttons to frame
self.selectButton = Button(top, text="Select", command=self.select)
self.selectButton.pack()
self.quitButton = Button(top, text="Quit", command=top.destroy)
self.quitButton.pack()
# identify selected rows and return a list with the selection
def select(self):
selectedRows = self.listbox.curselection()
for item in selectedRows:
self.selection.append(self.dirList[item])
print(self.selection)
return self.selection
Question: pass the list with the selected items to the Listbox in the main window
You need a reference of the main window Listbox.
I show, how to using listTask
Extend your __init__ to accept the reference target_listbox
class Add:
def __init__(self, target_listbox, path):
self.target_listbox = target_listbox
Insert the selected items into .target_listbox
Note: Your, return self.selection is useless, a Button.command can't process returns.
def select(self):
selectedRows = self.listbox.curselection()
for item in selectedRows:
self.target_listbox.insert(tk.END, self.dirList[item])
Pass the reference listTask to Add(...
taskBox = Add(listTask, ...)

How to add scrollbar to listbox in python tkinter?

# create the listbox widgets
def onselect(evt):
# Note here that Tkinter passes an event object to onselect()
w = evt.widget
index = int(w.curselection()[0])
value = w.get(index)
print(value)
listbox = Listbox(main_frame, selectmode=tk.SINGLE)
listbox.config(width=int(screen_width/50), height=int(screen_width/50))
for index in range(0, 5):
listbox.insert(tk.END, "video"+str(index))
listbox.yscrollcommand = True
# listbox.grid(row=1, column=2)
listbox.place(x=screen_width-int(screen_width/3), y=0)
# <Double-1> for double click
listbox.bind('<Double-1>', onselect)
selection = listbox.curselection()
I am having trouble putting a scrollbar for the listbox and an error keeping pop out saying that "Scrollbar" is not defined. Can anybody help me?

tkinter programmaticall creation of buttons

Hi I am trying to create a GUI with tkinter but I am unable to programmatically make the buttons show and/or behave properly.
This is the code I am using to create the buttons based on a a dict:
from tkinter import *
import sqlite3
from functools import partial
def query(x):
conn = sqlite3.connect("_db")
cur = conn.cursor()
q = cur.execute("SELECT text, option1, option2, option3 FROM tbl_1 WHERE id=?", [x, ])
actions = q.fetchone()
return actions
def pkey(identifier, label):
q = query(identifier)
buttons = {}
text = q[0]
label.config(text=text)
for entry in q:
if entry != text and entry is not None:
buttons[int(entry[0])] = entry[3:]
new_button = Button(root)
for k, v in buttons.items():
new_button.config(text=v, command=partial(pkey, k, label))
new_button.pack(fill=X)
print(buttons)
lbl.pack()
root = Tk()
root.geometry("300x200")
text_frame = LabelFrame(root, bg="#A66D4F")
text_frame.pack(fill=BOTH, expand=Y)
options_frame = LabelFrame(root, bg="cyan").pack(fill=X)
lbl = Label(text_frame)
pkey(1, lbl)
root.mainloop()
This creates the buttons within a frame, but when I click one of the buttons and they should replace the existing buttons all I get is a instance of the buttons on top of the existing one. Going from 3 buttons to 6 buttons.
Is there a way to replace the existing instance of the buttons with a new one after clicking?
Thanks
#BryanOakley Thank you for the reply. You were correct I was missing a destroy.
i have updated the code with the below and it is working properly now.
def destroy(frame, key, label):
frame.destroy()
frame = LabelFrame(self.parent, bg="cyan")
frame.pack(fill=X)
option_buttons(frame, key, label)
Thanks again

python - binding to a tkinter widget is called twice if set() is called on a StringVar()

I have an entry, a listbox(dropdown) and another listbox. Whenever more than 3 characters are typed inside the entry. A completion list is looked up and inserted to the dropdown and the dropdown is shown. If an item is selected from the dropdown. It's value should fill the entry and the entry should get the focus again and the cursor should go to the end of the entry. And then, when Enter key is pressed the value of the entry should be inserted to the other listbox.
I've developed a code for that with much help from this utility and the code works perfectly fine. Except, I realized that whenever I select an option from the dropdown the corresponding method is called twice(I get two prints in the console from the same thing). But if I select the first option of the dropdown, it's called once(which is what should have actually happened in the other case) but the focus does not go to the entry (which is a problem).
Here is my code:
from tkinter import *
class Autocomplete(Frame, object):
def __init__(self, width, height, entries, *args, **kwargs):
super(Autocomplete, self).__init__(*args, **kwargs)
self._entries = entries
self.listbox_height = height
self.entry_width = width
self.text = StringVar()
self.entry = Entry(
self,
textvariable=self.text,
width=self.entry_width
)
self.frame = Frame(self)
self.listbox = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width
)
self.dropdown = Listbox(
self.frame,
height=self.listbox_height,
width=self.entry_width,
background="#cfeff9"
)
def build(self):
self.text.trace("w", lambda name, index, mode, text=self.text: self._update_autocomplete())
self.entry.bind("<Return>", lambda event,: self._add_course())
self.entry.focus_set()
self.entry.pack()
self.frame.pack()
self.listbox.grid(column=0, row=0, sticky=N)
self.dropdown.bind("<<ListboxSelect>>", self._select_entry)
self.dropdown.grid(column=0, row=0, sticky=N)
self.dropdown.grid_forget()
return self
def _update_autocomplete(self):
self.dropdown["height"] = self.listbox_height
self.dropdown.delete(0, END)
text = self.text.get()
if len(text) < 3:
self.dropdown.grid_forget()
return
else:
for entry in self._entries:
if text.lower() in entry.strip().lower():
self.dropdown.insert(END, entry)
listbox_size = self.dropdown.size()
if not listbox_size:
self.dropdown.insert(END, "No results found for '{}'")
self.dropdown["height"] = 1
else:
if listbox_size <= self.dropdown["height"]:
self.dropdown["height"] = listbox_size
self.dropdown.grid(column=0, row=0, sticky=N)
def _select_entry(self, event):
widget = event.widget
value = widget.get(int(widget.curselection()[0]))
print(value)
self.text.set(value)
self.entry.focus_set()
self.entry.icursor(END)
def _add_course(self):
self.listbox.insert(END, self.text.get())
So what am I missing here?
By the way any general improvement to the code will also be much appreciated.
And here is how I call it:
from tkinter import *
from autocomplete import Autocomplete
from main import *
courses = load_courses_from_file("courses.txt")
root = Tk()
autocomplete_frame = Autocomplete(
60,
10,
list(set(course.name + ", " + course.instructor for course in courses))
).build().pack()
mainloop()
The selection of the listbox changes when you click on the item -- this is the default behavior of the listbox. This causes the entry widget value to change, which triggers a call to _update_autocomplete. That function deletes everything in the listbox, causing the selection to change again.

Problems in Python getting multiple selections from Tkinter listbox

This is the same problem I posed earlier today and which a couple of you tried to help me with, but I can't get it to work. All I want to do is to populate "ichose" with the multiple selections I make when I click on the listbox.
import Tkinter as tk
from Tkinter import *
global ichose
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master=master
self.grid()
self.ichose = ()
self.l = Listbox(self, height=10, selectmode=EXTENDED)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
self.l.grid(column=0, row=0, sticky=(N,W,E,S))
self.l.bind("Double-Button-1", self.entered)
s = Scrollbar(self, orient=VERTICAL, command=self.l.yview)
s.grid(column=0, row=0, sticky=(N,S,E))
self.l['yscrollcommand'] = s.set
for i in range(1,101):
self.l.insert('end', 'Line %d of 100' % i)
def entered(self, event):
self.ichose = self.selection_get()
self.ichose = ('hello')
root=tk.Tk()
root.title('Listbox Problem')
root.geometry('200x200')
app=App(root)
root.mainloop()
print app.ichose
Whatever I do, "ichose" comes out as an empty tuple ().
It's clear that the function "entered" is never called because I never see the test string 'hello'.
I also don't know what the various options are as in "Double-Button-", "<>" etc. Where can I find a list and explanation of each one?
If somebody could please just modify my program so the "print ichose" works, I'd be really grateful. You can see from my program that I don't really know what I'm doing but am keen to learn. Thank you.
I've finally found the answer to my own question. This is REALLY useful if you want to capture multiple responses from a listbox. I've commented a lot. Hope it helps!
import Tkinter as tk
from Tkinter import *
class App(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self,master)
self.master=master
self.grid()
self.ichose = []
self.l = Listbox(self, height=10, selectmode=MULTIPLE)
# Selectmode can be SINGLE, BROWSE, MULTIPLE or EXTENDED. Default BROWSE
self.l.grid(column=0, row=0, sticky=(N,W,E,S))
s = Scrollbar(self, orient=VERTICAL, command=self.l.yview)
s.grid(column=0, row=0, sticky=(N,S,E))
self.l['yscrollcommand'] = s.set
for i in range(1,101):
self.l.insert('end', 'Line %d of 100' % i)
# Create Textbox that will display selected items from list
self.selected_list = Text(self,width=20,height=10,wrap=WORD)
self.selected_list.grid(row=12, column=0, sticky=W)
# Now execute the poll() function to capture selected list items
self.ichose = self.poll()
def poll(self):
items =[]
self.ichose = []
# Set up an automatically recurring event that repeats after 200 millisecs
self.selected_list.after(200, self.poll)
# curselection retrieves the selected items as a tuple of strings. These
# strings are the list indexes ('0' to whatever) of the items selected.
# map applies the function specified in the 1st parameter to every item
# from the 2nd parameter and returns a list of the results. So "items"
# is now a list of integers
items = map(int,self.l.curselection())
# For however many values there are in "items":
for i in range(len(items)):
# Use each number as an index and get from the listbox the actual
# text strings corresponding to each index, and append each to
# the list "ichose".
self.ichose.append(self.l.get(items[i]))
# Write ichose to the textbox to display it.
self.update_list()
return self.ichose
def update_list(self):
self.selected_list.delete(0.0, END)
self.selected_list.insert(0.0, self.ichose)
root=tk.Tk()
root.title('Listbox Multi-Capture')
root.geometry('200x340')
app=App(root)
root.mainloop()
print app.ichose
# ----------------[ Listbox EXAMPLE ]----------------
self.sarcCountries = (
"Bangladesh",
"India",
"Pakistan",
"Nepal",
"Bhutan",
"Sri Lanka",
"Afghanistan"
)
self.listData = StringVar(value = self.sarcCountries)
self.listbox = Listbox(
master = self,
height = 10,
listvariable = self.listData,
selectmode = MULTIPLE,
selectbackground = "#BC80CC"
)
self.listbox.bind("<<ListboxSelect>>", self.OnListboxSelectionChanged)
self.listbox.pack(fill = BOTH, expand = 0, padx = 10, pady = 10)
# ----------------[ Listbox EXAMPLE ]----------------
def OnListboxSelectionChanged(self, val):
# NOTE: If your listbox's select mode is MULTIPLE, then you may use this portion of code
selections = val.widget.curselection()
print("---------------------------")
if (selections != ()):
for index in selections:
print(self.sarcCountries[int(index)])
print("---------------------------")

Categories