I'm using tkinter to create a GUI for a component inventory application I'm making. The GUI will print the current active list for the program. I'm using a class for the main window, and I'm having trouble displaying the current list after loading a new one.
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.active_list = []
self.dir = os.getcwd()
self.create_window()
In the code above, I initialized the active_list as an empty list. When a new list is opened, it sets the active_list to the new list from a csv file. The class' show_list() method below prints the active_list in a text box on the GUI. The main issue is that it doesn't update when a new list is loaded. It can print the empty active_list that is initialized in the __init__() func but cannot seem to be updated.
def show_list(self):
self.scrlbr1 = tk.Scrollbar(self.master)
self.disp_list = tk.Text(self.master, height=5, width=50)
if not self.active_list:
self.disp_list.insert(tk.END, 'no list loaded')
else:
self.disp_list.insert(tk.END, self.active_list)
self.scrlbr1.pack(side=tk.RIGHT, fill=tk.Y)
self.disp_list.pack(side=tk.LEFT, fill=tk.Y)
My initial thought was that the tk mainloop func was resetting the active_list to an empty list when the class was created. Could anybody suggest some fixes? Thanks
You need a mechanism to check if the list was modified, or maybe more simply, a mechanism to refresh the display at regular intervals.
Maybe like this:
def __init__(self, master=None):
super().__init__(master)
...
self.create_show_list_widgets()
def create_show_list_widgets(self):
"""creates the widgets used to display the list
"""
self.scrlbr1 = tk.Scrollbar(self.master)
self.disp_list = tk.Text(self.master, height=5, width=50)
self.scrlbr1.pack(side=tk.RIGHT, fill=tk.Y)
self.disp_list.pack(side=tk.LEFT, fill=tk.Y)
self.show_list()
def show_list(self):
"""refreshes the display of the list every second
"""
if not self.active_list:
self.disp_list.insert(tk.END, 'no list loaded')
else:
self.disp_list.insert(tk.END, self.active_list)
self.after(1000, self.show_list) # assuming your class is a subclass of `tk.Tk`, or `tk.Frame`
Related
This question already has an answer here:
How to save askdirectory result in a variable I can use using tkinter with OOP?
(1 answer)
Closed 4 years ago.
first of all im pretty new to OOP coding, so sorry for asking stupid questions.
I have a problem returning a value from Class AskDir that gets its value from Class SelectDir to Class MainWindow if that makes sense
Currently the code i have works, in every other way, except I cannot save the actual "self" (which is the path i.e. "/home/etc/") return value from Class AskDir() to another variable no matter what i do.
From what i have read i should be able to get the return value with a = AskDir(self)
print(a), but what I get instead is ".!frame.!functionchangename.!askdir"
So in short, how to save actual return path from a function inside Class AskDir, to be saved to variable a in Class MainWindow()?
To clarify, what i want is for Class MainWindow() to have a variable (a) that gets the return value from a subfunction get() inside Class SelectDir(), and that value should be the path that get() function returns
simplified code:
class MainWindow:
self.controller = controller
# prints the button to this frame!
getdir = AskDir(self)
print(getdir) # this should return actual path (/home/etc/), but it doesnt
class AskDir(tk.Frame):
def __init__(self, container):
tk.Frame.__init__(self, container)
self.selectdir = SelectDir(self, "Select directory",
"/home/user/folder/")
button = tk.Button(frame, text="Select directory",
command=self.select_dir)
self.act_dir = tk.StringVar()
self.act_dir.set("/home/")
def select_dir(self):
self.selectdir.show()
self.act_dir.set(self.selectdir.get())
class SelectDir:
def __init__(self, container, title, initial):
self.master = container
def show(self):
result = filedialog.askdirectory()
if result:
self.selected = result
# THIS RETURNS THE ACTUAL PATH!
def get(self):
return self.selected
Does this work for you?
I can see no need to have SelectDir as a class given that its purpose is just to call the askdirectory method and return the selected folder path so I've change it to a function.
With this example, pressing the "Select directory" button will open the directory dialog and then place the selected directory text in to an Entry widget.
If the MainWindow class wishes to do anything with the path once it has been set then you just need to call the get_dir method of AskDir e.g. print(askDir.get_dir()
import tkinter as tk
from tkinter import filedialog
def doStuffFunction(value):
print(value)
class MainWindow(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.askDir = AskDir(self)
self.askDir.grid()
doStuffButton = tk.Button(self, text="Do Stuff", command=self.doStuff)
doStuffButton.grid()
def doStuff(self):
doStuffFunction(self.askDir.get_dir())
class AskDir(tk.Frame):
def __init__(self, master, **kw):
tk.Frame.__init__(self, master=master,**kw)
button = tk.Button(self, text="Select directory",
command=self.select_dir)
button.grid(row=0,column=1)
self.act_dir = tk.StringVar()
self.act_dir.set("/home/")
self.pathBox = tk.Entry(self, textvariable=self.act_dir,width=50)
self.pathBox.grid(row=0,column=0)
def select_dir(self):
selectdir = SelectDir("Select directory", "/home/user/folder/")
self.act_dir.set(selectdir)
def get_dir(self):
return self.act_dir.get()
def SelectDir(title,initial):
result = filedialog.askdirectory(initialdir=initial,title=title)
return result
if __name__ == '__main__':
root = tk.Tk()
MainWindow(root).grid()
root.mainloop()
You can now use the AskDir class as many times as you want in the MainWindow (perhaps to select Source and Destination paths). This has been added to my example with the doStuff method inside MainWindow.
class window:
def __init__(self):
addMovie = Tk()
addMovie.minsize(300, 150)
addMovie.maxsize(300, 150)
self.premier = IntVar()
isPremier = Checkbutton(self.addMovie, text="Premier", variable=self.premier)
isPremier.place(relx=0, x=5, y=40)
def detect(self):
if self.premier.get() == 1:
print("Premier")
else:
print("Not premier")
So this code works perfectly if I create the instance in the same file, but when I try to make the instance from another place, for example newWindow = GUI.newMovie.window() only the checkbuttons are not working properly, other elements as Entry and ttk.ComboBox does work as intend
Im calling the detect method from a button, if that info is needed
This is the Tkinter window when calling addFilterList(list)
I called this function like so:
tkWindow = TkWindow()
tkWindow.addFilterList(['A','B','C','D','E','F','G','H','I','J','K','L'])
tkwindow.runwindow()
I have this TKinker class. I am stuck on ways to make this more dynamic. First the scroll bar, buttons, and listbox are hard coded to be in specific places in the window. Is there a way to get this format no matter where on the Tkinter window it appears. For example, If I have a bunch of buttons on top, I would like the this feature to appear in this format without having to go back to the code and change its row or column location.
Second: The way I set it up, there can only be one addFilterList per TkWindow because of the return value. Can someone point me in the right directions in how to alter the code so that I can return the values of multiple Listbox in one Tkinter window.
class TkWindow(object):
def __init__(self):
self.top = tk.Tk()
def addFilterList(self, list_box):
self.list_box = list_box
self.value = []
self.text_field = tk.StringVar()
self.entry = tk.Entry(self.top, textvariable=self.text_field, width=60)
self.listbox = tk.Listbox(self.top, width=40, selectmode=tk.MULTIPLE)
self.entry.grid()
self.listbox.grid(row=7)
self.text_field.trace("w", lambda name, index, mode: self.update_list())
self.button_show = tk.Button(self.top, text="Select",
command=self.selected_item)
self.button_clear = tk.Button(self.top, text="Clear",
command=self.clear)
self.scrollbar = tk.Scrollbar(self.top)
self.show_list = tk.Listbox(self.top, width=60, height=4)
self.scrollbar.grid(row=7, sticky=tk.N + tk.S + tk.E, padx=(10, 50))
self.button_show.grid(row=8, padx=(10, 300))
self.button_clear.grid(row=8, sticky=tk.E, padx=(10, 100))
self.show_list.grid()
# Add scrollbar
self.listbox.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.listbox.yview)
self.update_list()
def update_list(self):
# Used in addFilterList()
search_term = self.text_field.get()
self.listbox.delete(0, tk.END)
for item in self.list_box:
if search_term.lower() in item.lower():
self.listbox.insert(tk.END, item)
def selected_list(self):
# Used in addFilterList()
self.show_list.delete(0, tk.END)
for item in self.value:
self.show_list.insert(tk.END, item)
self.selected = self.listbox.selection_clear(0, tk.END)
def selected_item(self):
# Used in addFilterList()
self.selected = self.listbox.curselection()
for i in self.selected:
self.value.append(self.listbox.get(i))
self.selected_list()
def clear(self):
# Used in addFilterList()
self.value = []
self.show_list.delete(0, tk.END)
def return_value(self):
return self.value
def runWindow(self):
self.top.mainloop()
I'm not sure I understand your question but I will try to offer some advice. I think you are trying to do too many things in the function addFilterList. Your code is hard to read and modify as a result. You have three distinct things to be done:
Initializing the widgets
Laying out the widgets
Populating the widgets with values
I usually do #1 in the constructor. So your constructor would be, in outline:
def __init__(self):
self.top = tk.Tk()
self.entry = tk.Entry(...)
self.listbox = tk.ListBox(...)
Then I do the layout in a separate function, call it doLayout():
def doLayout(self):
self.entry.grid(...)
self.listbox.grid(...)
Now your function addFilterList can be concerned ONLY with adding a list of items to your listbox. You can change the layout without changing this function. You can add additional widgets to the window without changing this function.
If you want to have more than one FilterList, you might consider making a subclass of tk.Listbox. The functions here would set the list contents, clear the list contents, handle list selection events and so forth. Then if you decide you want two lists instead of just one, you can instantiate another instance of this class and add that to your window.
I have a Tkinter GUI having 2 entry fields, 2 buttons ( initialization of these not shown in code). There is one more button (initialized in code) which performs the main task of performing change detection on two images. Also there is a progress bar.
Now, when the task of change detection has been completed, I want to display the 4 images(pre, post, aligned, chng) returned by wave.changedetection() in a separate Tkinter window. I want the new window to come only after changedetection() has completed.(wave.py is my own file, not some module)
Unfortunately, if I try to add code to make new window, Tk.Toplevel() ,after the wave.changedetection() call, nothing happens and the main GUI window becomes unresponsive and has to be killed.
There is no way to know when the new created thread (start_thread)completes it's work, so that I can do Tk.Toplevel() there.
How can I do what I require?
class GUI(Tkinter.Tk):
def __init__(self, parent)
Tkinter.Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.button = Tkinter.Button(text = "Start")
self.button.bind('<Button-1>', self.OnButtonClick)
self.button.pack()
self.int = Tkinter.IntVar()
self.pgbar = Tkinter.ProgressBar(variable = self.int, mode = determinate)
def OnButtonClick(self,event):
#this func has been made since I have more buttons.It may seem redundant here
self.button['command'] = self.start_thread()
self.update_idletasks()
def start_thread(self):
self.int_var.set(1)
q = queue.Queue()
self.secondary_thread = threading.Thread(target = self.change)
self.secondary_thread.start()
self.after(50, self.check_queue, q)
def check_queue(self, q):
while True:
try:
x = wave.q.get_nowait()
except queue.Empty :
self.after(50,self.check_queue,q)
break
else:
self.int_var.set(x)
if x == 6:
self.button3['state'] = 'normal'
break
def change(self):
'''The 6 functions of wave.changedetection() change the value of self.int
due to which progress bar progresses.'''
pre, post, aligned, chng = wave.changedetection(self.entry_1.get(),
self.entry_2.get())
if __name__ == '__main__':
gui = GUI(None)
gui.mainloop()
code to update progress bar taken from here (2nd answer,Honest Abe's answer)
You have to be able to differentiate name spaces, i.e. this is in the main window and this is in the Toplevel. I would suggest that you get the Toplevels working first and then decide if you want to add threading or not. The code below is a simple example of creating Toplevels and shows how to place widgets in a specific name space (window in this case). You may or may not want a separate "create a Toplevel" class if there are functions you want to associate with each Toplevel's namespace. Also there are examples on the web on using Tkinter's "after" to update a progressbar. That is a different question so start another thread if you have questions about the progressbar.
try:
import Tkinter as tk ## Python 2.x
except ImportError:
import tkinter as tk ## Python 3.x
from functools import partial
class OpenToplevels():
""" open and close additional Toplevels with a button
"""
def __init__(self):
self.root = tk.Tk()
self.button_ctr=0
## in the "root" namespace *********************
but=tk.Button(self.root, text="Open a Toplevel",
command=self.open_another)
but.grid(row=0, column=0)
tk.Button(self.root, text="Exit Tkinter", bg="red",
command=self.root.quit).grid(row=1, column=0, sticky="we")
self.root.mainloop()
def close_it(self, id):
## destroy the window in this id's namespace ***********
id.destroy()
## id.withdraw()
## id.iconify()
def open_another(self):
self.button_ctr += 1
id = tk.Toplevel(self.root)
id.title("Toplevel #%d" % (self.button_ctr))
## in the "id for this Toplevel" namespace ***********
tk.Button(id, text="Close Toplevel #%d" % (self.button_ctr),
command=partial(self.close_it, id),
bg="orange", width=20).grid(row=1, column=0)
Ot=OpenToplevels()
I have a Tkinter GUI that is composed of two widgets, the first is a widget class with an entry field and a button and the second is a scrollable text widget class. I have combined these two widget classes to make a single GUI. Each of these widget classes works correctly as individuals.
The text field of the GUI is being used to display the contents of a specific index of a list. I want to be able to enter an index number in the entry field and upon pressing the button the text in the text field is re-configured to show the contents of the list for the specified index. However when I press the button I get the following error message:
" File "/Users/BioASys/BioasysDB/testgui.py", line 25, in
fetch_update_text
article_review_widget_assembled.update_text(article_index) TypeError:
unbound method update_text() must be called with
article_review_widget_assembled instance as first argument (got int
instance instead)"
When I initialize (mainloop) the GUI I have the text widget set to display the 0 index of the list.
I have written simplified code that exhibits my problem. My original code is too complex to sensibly post here. Because of the layout of all the individual widgets in my original code it was necessary to combine multiple widget classes to achieve my desired widget layout.
If anybody has any thoughts on how to get these two widgets to communicate I would greatly appreciate the assistance.
Here is the simplified code I have written which exhibits the issue I am having.
from Tkinter import *
# This list contains the sample text
# that is to be placed in the individual text widgets,
# referenced by index
text_list = [['text0AAAAAAA'], ['text1AAAAAAA'], ['text2AAAAAAA']]
# This class creates the widget that will be designated top_left
# in the article_review_widget_assembled widget
class article_review_entry_button_widget(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
self.index = 2
self.makeWidgets()
def handleList(self, event):
index = self.listbox.curselection()
label = self.listbox.get(index)
self.runCommand(label)
def fetch(self):
# This def gets the value placed in the entry field
print 'Input => "%s"' % self.ent.get()
article_index = self.ent.get()
def fetch_update_text(self):
# This def gets the value placed in the entry field
# and also attempts to access the update_text def
# to update the text in the top_right widget
print 'Input => "%s"' % self.ent.get()
article_index = int(self.ent.get())
# It is this line that is generating the TypeError
# It is this line that is generating the TypeError
article_review_widget_assembled.update_text(article_index)
def makeWidgets(self):
self.ent = Entry(self)
btn = Button(self, text='Next Article', command=self.fetch_update_text)
self.ent.insert(0, 0)
self.ent.pack(side=TOP, fill=BOTH)
self.ent.focus()
self.ent.bind('<Return>', (lambda event: self.fetch()))
value = self.ent.get()
btn.pack(side=TOP)
class ScrolledText(Frame):
# This class creates a text widget that can be scrolled,
# I use this as the basis for the other text widgets in this GUI
def __init__(self, parent=None, text='', file=None,
width='', height=''):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
self.width = width
self.height = height
self.makewidgets()
self.settext(text, file)
def makewidgets(self):
sbar = Scrollbar(self)
text = Text(self, relief=SUNKEN, width=self.width, height=self.height)
sbar.config(command=text.yview)
text.config(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT, fill=Y)last
text.pack(side=LEFT, expand=YES, fill=BOTH)
self.text = text
def settext(self, text='', file=None):
self.text.delete('1.0', END)
self.text.insert('1.0', text)
self.text.mark_set(INSERT, '1.0')
self.text.focus()
def gettext(self)
return self.text.get('1.0', END+'-1c')
class article_review_widget_assembled(Frame):
# This class uses the previous classes
# to create the final assemnbeld GUI widget
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
self.text = text_list[0]
self.makeWidgets()
# This is the def that is called by the fetch_update_text def
# in the article_review_entry_button_widget class
def update_text(index):
self.top_right.configure(text=text_list[index])
print "The index is:", index
def makeWidgets(self):
self.top_left = article_review_entry_button_widget(self).pack(side=LEFT)
self.top_right = ScrolledText(self, text= self.text, width=50, height=15).pack(side=LEFT)
if __name__ == '__main__':
#article_review_entry_button_widget().mainloop()
#ScrolledTextComposite().mainloop()
article_review_widget_assembled().mainloop()
Your problem is that you created a class (article_review_widget_assembled), created an instance of that class (article_review_widget_assembled()), but then tried to access a method by treating it like a static method (article_review_widget_assembled.update_text(article_index)).
The proper way to call update_text as you have defined it is via an instance of that class. That is what the message TypeError: unbound method update_text() must be called with article_review_widget_assembled instance as first argument (got int instance instead) is telling you. "unbound method" means you're calling a method designed to be "bound" to an instance, but you're not calling it via an instance.
The solution is to call it via an instance. Fortunately, in your case you're passing the reference to article_review_entry_button_widget as the parent parameter, so it looks like you can call it like self. parent.update_text() if you also set self.parent to the value of the parent parameter:
class article_review_entry_button_widget(Frame):
def __init__(self, parent=None):
self.parent = parent
Frame.__init__(self, parent)
...
def fetch_update_text(self):
...
self.parent.update_text(article_index)
Bottom line: when you get an "unbound method" error, it means you're calling a method via a class rather than via an instance of the class. These errors are a bit easier to spot if you adhere to the convention of starting class names with uppercase characters and instances with lowercase. This makes your code much easier to read and understand (eg: Article_review_entry_button, etc)