I'm trying to manipulate a listbox in Tkinter but I'm having some troubles. I used to have everything in one class, on one page, and it worked fine. I separated the methods into different classes on two different pages (one for displaying things, one for modifying them) and now I'm having some issues.
I'm getting the following error AttributeError: Actions has no attribute 'listbox'. I'm assuming it's something inheritance related because it worked fine before I split it into two files.
Here's the first file
from Tkinter import *
import Tkinter
import SortActions
class MakeList(Tkinter.Listbox):
def BuildMainWindow(self):
menubar = Frame(relief=RAISED,borderwidth=1)
menubar.pack()
mb_file = Menubutton(menubar,text='file')
mb_file.menu = Menu(mb_file)
mb_file.menu.add_command(label='open', command = self.BuildListbox)
mb_file.pack(side=LEFT)
mb_edit = Menubutton(menubar,text='edit')
mb_edit.menu = Menu(mb_edit)
mb_edit.pack(padx=25,side=RIGHT)
mb_file['menu'] = mb_file.menu
mb_edit['menu'] = mb_edit.menu
return
def BuildListbox(self):
self.listbox = Tkinter.Listbox()
index = SortActions.Actions()
self.listbox.bind('<<ListboxSelect>>', index.GetWindowIndex)
MoveItem = SortActions.Actions()
self.listbox.bind('<B1-Motion>', index.MoveWindowItem)
for item in ["one", "two", "three", "four"]:
self.listbox.insert(END, item)
self.listbox.insert(END, "a list entry")
self.listbox.pack()
#print self.listbox.get(0, END)
return
if __name__ == '__main__':
start = MakeList()
start.BuildMainWindow()
mainloop()
And the second file, the one that I'm having issues with
from FileSort import MakeList
class Actions(MakeList):
#gets the current item that was clicked in the window
def GetWindowIndex(self, event):
w = event.widget
self.curIndex = int(w.curselection()[0])
#moves the current item in the window when clicked/dragged
def MoveWindowItem(self, event):
i = self.listbox.nearest(event.y) #here is where the error is occurring
print i
I assumed since I inherit the MakeList class I should have access. I also tried changing it so I directly accessed MakeList (an object) but instead of the error saying "Actions instance has no...." it said "MakeList has no attribute..."
I posted something previously but I accidentally ran an older version of the code, so I was referencing the wrong error. Sorry if you saw that post. It's gone now
As I see it, there's no reason for the Actions to be in a class ...
#SortActions.py
#gets the current item that was clicked in the window
def GetWindowIndex(self, event):
w = event.widget
self.curIndex = int(w.curselection()[0])
#moves the current item in the window when clicked/dragged
def MoveWindowItem(self, event):
i = self.nearest(event.y) #here is where the error is occurring
print i
Now you can use the actions:
...
def BuildListbox(self):
#self.listbox = Tkinter.Listbox() #??? This has no master widget ...
#Since this is already a listbox, there's no point in building another ...
self.bind('<<ListboxSelect>>', lambda e:SortActions.GetWindowIndex(self,e))
self.bind('<B1-Motion>', lambda e:SortActions.MoveWindowItem(self,e)
for item in ("one", "two", "three", "four"):
self.insert(END, item)
self.insert(END, "a list entry")
self.pack()
Related
I'm currently building a desktop application using some checkable comboboxes.
I've used the code snippet we can find easily on the web about how customizing comboboxes to make the items checkable.
It 's been working fine but I wanted each combobox list not to collapse once any item was selected.
That's where the virtual function hidePopup(self) comes in: I only had to define it within the checkable combobox class, without any code!!, to prevent the popup list from hiding... I don't understand why since there is absolutely no code but "pass" in my functions!
You'll find below the script from the checkable combobox class.
class CheckableComboBox(QtWidgets.QComboBox):
def __init__(self):
super().__init__()
self.liste_artisans = []
self.view().pressed.connect(self.handle_item_pressed)
self.setModel(QStandardItemModel(self))
def handle_item_pressed(self, index):
# getting which item is pressed
item = self.model().itemFromIndex(index)
# make it check if unchecked and vice-versa
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
self.checked_items_list()
def checked_items_list(self):
for i in range(self.count()):
text_label = self.model().item(i, 0).text()
if self.item_check_status(i) and text_label not in self.liste_artisans:
self.liste_artisans.append(text_label)
elif not self.item_check_status(i) and text_label in self.liste_artisans:
self.liste_artisans.remove(text_label)
def hidePopup(self):
pass
# method called by checked_items_list
def item_check_status(self, index):
# getting item at index
item = self.model().item(index, 0)
# return true if checked else false
return item.checkState() == Qt.Checked
I'm new to Tkinter and I'm trying to add event handling to a GUI.
I have a list that contains sentences and words( the list contains a sublist consisting of the sent as a string as its first element and a list of its words as its second element), and I first want to display the sentences using a Label widget. What I'd like to do is switch between the sentences using the Up and Down keys.
My first problem, however is a different one. I want to store the sentence that is currently displayed in a variable called current_sent, so I try to assign 0 to self.current_sent in the constructor of the app. However, when I try to reference this variable in my code, I get an attribute error. When I initialize self.current_sent in the initialize() method of my app, I don't get the error. Can anybody tell me why this is?
Now if I set self.current_sent = 0 in the initialize method, the gui starts, but I don't get any changes when pushing the Down button.
I also tried this using only '' as an event, but that also doesn't cause the second sentence to be displayed.
If I try to call print statements from the next_sent method, nothing is displayed, so I never enter the event handling function.
Can anybody tell me, what I'm doing wrong, please?
import nltk
import Tkinter as tk
import os
class Annotator(tk.Tk):
def __init__(self, parent):
tk.Tk.__init__(self, parent)
self.sents = self.get_sents()
self.initialize()
self.current_sent = 0
self.current_word = 0
def sent_tokenize(self, textfile):
f = open(textfile)
s = f.readlines()
text = " ".join(s)
sents = nltk.sent_tokenize(text)
tags = [[x,nltk.word_tokenize(x)] for x in sents]
return tags
def get_sents(self):
article_files = self.get_articles()
list_of_sents = [self.sent_tokenize(x) for x in article_files]
sents = [sent for sublist in list_of_sents for sent in sublist]
return sents
def get_articles(self):
directory = "/Users/------------/Documents/reuters/reuters/articles"
list_of_articles = []
for f in os.listdir(directory):
if not f.startswith('.'):
filename = directory + "/" + f
list_of_articles.append(filename)
return list_of_articles
def next_sent(self,event):
if (self.current_sent < len(self.sents) - 1):
self.current_sent += 1
self.label.config(text = self.sents[self.current_sent][0])
def initialize(self):
self.label = tk.Label(text = self.sents[self.current_sent][0])
self.label.bind('<KeyPress-Down>', self.next_sent)
self.label.grid(row = 0, column = 0, columnspan = 2)
if __name__ == "__main__":
app = Annotator(None)
app.mainloop()
The AttributeError is coming up because __init__ calls initialize before defining self.current_sent so you just need to rearrange the __init__ a little bit:
def __init__(self, parent):
tk.Tk.__init__(self, parent)
self.current_sent = 0
self.current_word = 0
self.sents = self.get_sents()
self.initialize()
As for the Binding issue, only the widget with keyboard focus will respond to events, either try clicking on the label before testing the events or set it up to respond regardless of what has focus like this:
self.bind_all('<KeyPress-Down>', self.next_sent)
I have a piece of code that does some textual analysis and displays the results in a tkinter window.
Since users can choose to do the same analysis on multiple sources, and this can take quite some time, I would like to display the results as they become available.
However, the tkinter window pops up only after the last result becomes available. From other questions on Stackoverflow, I understand why this does not work (simplified version of my code):
class Output:
def __init__(self):
self.set_main_variables()
def set_main_variables(self):
self.names= []
#and other variables...
def initialize(self):
self.t = Tk()
self.make_frames()
self.populate_frames()
self.t.mainloop()
def update(self):
self.populate_frames()
def populate_frames(self):
<uses the data in self.names to display the results>
output = Output()
for i, s in enumerate(sources):
results = analyze(s)
output.names = results
if i == 0:
output.initialize()
else:
output.update()
Therefore, I tried taking the initialize() out of the loop, and creating a button in the window by which the user can update the results (if any are available):
output = Output()
first_results = analyze(s[0])
output.names = first_results
output.initialize()
for i, s in enumerate(sources):
results = analyze(s)
output.names = results
This does not solve the problem, though; the window still pops up only after the last source has been analyzed.
I have read many possible options to deal with this (using after, guiloop, etc.) but I don't see how these could help me in this situation.
Could anyone set me on the right path?
A colleague found the solution to my question.
Eric, in the comments, put me partly on the right path. He proposed using update_idletasks() in the GUI, but that did not work: it opened a blank window, which was filled in with widgets only when the "sources" loop was finished.
The solution was to put update() not inside the GUI, but inside the loop that is sending the updates to the GUI.
A simplified example with working code:
from tkinter import *
class SimpleTestClass:
def __init__(self):
self.names = []
self.top = Tk()
self.names_string = StringVar()
self.label = Label(self.top, textvar=self.names_string).pack()
#self.top.mainloop()
def updateLabel(self):
self.names_string.set("\n".join(self.names))
def test(gui):
names = ["a", "b", "c"]
sources = [11, 2, 3]
for i, s in enumerate(sources):
print(i)
gui.names.append(names[i])
print(gui.names)
gui.updateLabel()
gui.top.update()
#gui.top.update_idletasks()
time.sleep(3) # this simulates the program taking /
# some time to do its analysis of the source
s = SimpleTestClass()
test(s)
Two notes:
using update_idletasks() works as well, but displays a strange black
border at the bottom of the window until the sources loop is
finished.
calling self.top.mainloop() blocks the window update
I also tried an alternative version, where the user can push a button to manually update the list of results, but this did not function well (the button reacts only when the sources loop is finished):
class SimpleTestClass:
def __init__(self):
self.names = []
self.top = Tk()
self.names_string = StringVar()
self.label = Label(self.top, textvar=self.names_string).pack()
Button(self.top, text="update", command=self.updateLabel).pack()
def updateLabel(self):
self.names_string.set("\n".join(self.names))
def test(gui):
names = ["a", "b", "c"]
sources = [11, 2, 3]
for i, s in enumerate(sources):
gui.names.append(names[i])
print(gui.names)
gui.top.update()
time.sleep(3) # this simulates the program taking /
# some time to do its analysis of the source
s = SimpleTestClass()
test(s)
I am running a script with tkinter that captures user input and then opens a second and possibly a third window based on the input. The issue I am having is capturing user input from the third and final window. Each window is divided up into it's own python class on execution.
Here is the code that calls the third window, which executes properly:
test_assign = TestAssign(mylist)
Here is the third window code:
class TestAssign:
def __init__(self, mylist):
self.mylist = mylist
self.selected_values = []
self.master = Tk()
for i in range(len(mylist)):
setattr(self, 'item'+mylist[i], IntVar())
ch = Checkbutton(master, text='item'+mylist[i], variable=getattr(self, 'item'+mylist[i])
ch.pack()
b = Button(master, text='Next', command=self.get_selected_values)
b.pack()
mainloop()
def get_selected_values(self):
for i in range(len(self.mylist)):
if getattr(self, 'item'+self.mylist[i]) == 1:
self.selected_values.append(self.mylist[i])
self.master.destroy()
Control then returns to the call point (at least I believe it does). Where I attempt to print the selected values:
test_assign = TestAssign(mylist)
while not test_assign.selected_values:
pass
print test_assign.selected_values
Everytime execution gets to the print statement it prints an empty list whether there are boxes checked or not. If I call dir(test_assign) for testing purposes, the checkbox attrs are there. Not sure why I am not able to capture it like this.
Can anyone see the flaw in my code?
Two things:
1)
ch = Checkbutton(master, text='item'+mylist[i], variable=getattr(self, 'item'+mylist[i])
and
b = Button(master, text='Next', command=self.get_selected_values)
I think master should be self.master (but honestly, that almost certainly just a copy/pasting error.)
2) The important one:
if getattr(self, 'item'+self.mylist[i]) == 1:
should be
if getattr(self, 'item'+self.mylist[i]).get() == 1:
(you need to call get on your IntVars to read the value.)
I have a code that uses Listbox.curselection():
self.index = int(self._Listbox.curselection()[0])
I would like to raise an error window when nothing is selected in the Listbox.
Any feedback would be appreciated.
Thanks!
I'm not 100% sure what the problem is. If there are no items selected, the self._Listbox.curselection() should return an emtpy list. Since you then grab index 0, it should throw an IndexError.
Demo Code:
from Tkinter import *
master = Tk()
listbox = Listbox(master)
listbox.pack()
listbox.insert(END, "a list entry")
for item in ["one", "two", "three", "four"]:
listbox.insert(END, item)
def callback():
items = map(int, listbox.curselection())
if(len(items) == 0):
print "No items"
else:
print items
button = Button(master,text="press",command=callback)
button.pack()
mainloop()
Based on the behavior of the code above (nothing selected returns an empty list), your code should throw an IndexError when you don't have anything selected ... now you just need to handle the exception:
try:
self.index = int(self._Listbox.curselection()[0])
except IndexError:
tkMessageBox.showwarning("Oops","Need to select something")
Finally, I suppose I'll leave a link to some documentation on the standard Tkinter dialogs (tkMessageBox module)