How to loop through subfolders showing jpg in Tkinter? - python

It's a Python beginner question.
I want to loop through several subfolders of parent folder (subfolders contain jpg and txt files). I want to show images using Tkinter. There should be an image and Next button beside - when button is clicked the next image from the list should be loaded.
How to force an application to stop on each image and wait for user reaction?
In the test code below the image loaded is from the last directory in the loop (images from other directories are not displayed although I can print their names during the loop run).
import Tkinter, os, glob
from PIL import Image, ImageTk
class simpleapp_tk(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid
parentSrcFolder = r"D:\2012\RCIN\test"
srcFoldersLst = os.listdir(parentSrcFolder)
for srcFolder in srcFoldersLst:
jpgFilesPathLst = glob.glob(os.path.join(parentSrcFolder, srcFolder, "*.jpg"))
self.labelVariable = Tkinter.StringVar()
label = Tkinter.Label(self,textvariable=self.labelVariable,anchor="w")
label.grid(column=0,row=0,columnspan=2,sticky='EW')
self.labelVariable.set(jpgFilesPathLst[0])
cardImage = Image.open(jpgFilesPathLst[0])
indexCard = ImageTk.PhotoImage(cardImage)
labelImage = Tkinter.Label(self,image=indexCard)
labelImage.image = indexCard
labelImage.grid(column=0,row=3)
def main():
app = simpleapp_tk(None)
app.title('my application')
app.mainloop()
if __name__ == '__main__':
main()

The easiest way that I can think of doing this :
first, create a method display_next which will increment an index and display the image associated with that index in a list (assume the list is a list of filenames). Enclosing the list inquiry in a try/except clause will let you catch the IndexError that happens when you run out of images to display -- At this point you can reset your index to -1 or whatever you want to happen at that point.
get the list of filenames in __init__ and initialize some index to -1 (e.g. self.index=-1).
create a tk.Button in __init__ like this:
self.Button = Tkinter.Button(self,text="Next",command=self.display_next)
Another side note, you can use a widget's config method to update a widget on the fly (instead of recreating it all the time). In other words, move all the widget creation into __init__ and then in display_next just update the widget using config. Also, it's probably better to inherit from Tkinter.Frame...
class SimpleAppTk(Tkinter.Frame):
def __init__(self,*args,**kwargs):
Tkinter.Frame.__init__(self,*args,**kwargs)
self.filelist=[] #get your files here
#it probably would look like:
#for d in os.listdir(parentDir):
# self.filelist.extend(glob.glob(os.path.join(parentDir,d,'*.jpg'))
self.index=-1
self.setup()
self.display_next()
def setup(self):
self.Label=Tkinter.Label(self)
self.Label.grid(row=0,column=0)
self.Button=Tkinter.Button(self,text="Next",command=self.display_next)
self.Button.grid(row=0,column=1)
def display_next(self):
self.index+=1
try:
f=self.filelist[self.index]
except IndexError:
self.index=-1 #go back to the beginning of the list.
self.display_next()
return
#create PhotoImage here
photoimage=...
self.Label.config(image=photoimage)
self.Label.image=photoimage
if __name__ == "__main__":
root=Tkinter.Tk()
my_app=SimpleAppTk(root)
my_app.grid(row=0,column=0)
root.mainloop()
EDIT
I've given an example of how to actually grid the Frame. In your previous example, you had self.grid in your initialization code. This really did nothing. The only reason you had results was because you were inheriting from Tkinter.Tk which gets gridded automatically. Typically it's best practice to grid after you create the object because if you come back later and decide you want to put that widget someplace else in a different gui, it's trivial to do so. I've also changed the name of the class to use CamelCase in agreement with PEP 8 ... But you can change it back if you want.

Related

There is a way to wait on a user's answer in tkinter?

I'm developing an application what have its functions set in different files.
The main file have a tkinter interface and the buttons, entrys and labels are in other file, like this:
Mainfile.py
from tkinter import *
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
import Buttons
self.branch = Buttons.New_Button(self.main_frame)
#Here i wuold like to verify the hipotetic variable after the main_frame were destroyed
if self.branch.hipotetic_variable:
root.mainloop()
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame):
self.button_1 = Button(using_frame, text = 'Button 1', command=functools.partial(self.Func, using_frame))
self.button_1.pack()
def Func(self, to_destroy):
to_destroy.destroy()
#Here is the hipotetic variable what i would like to verify with if statment
self.hipotetic_variable = True
The problem is that I want to keep managing the program in the main file calling the other functions and implementing it, but I cannot verify if it's time to update the screen because mainloop makes impossible to verify it using a while loop and an hipotetic variable that's created after user pressed button.
I wold like to know if there is an way to update an variable contained in the Buttons.py file on Mainfile.py to keep implementing all other canvas in this file.
Your if self.branch.hipotetic_variable: check in the Program.__init__() method is only going to be executed when the Program class instance gets created initially, which is before the button that could change the value of the variable could have been pressed. You also don't want to make the hipotetic_variable an attribute of the Button because that will be destroyed along with the Frame it is in when that's destroyed in the button callback function.
Tkinter applications are user-event driven, meaning that they're "run" by responding to events (that's what mainloop is all about). This type of programming paradigm is different from the procedural or imperative one you're probably used to.
Therefore to do what you want requires setting things up so an event that the program can respond to will be generated, which in this case to when the frame is destroyed. One way to do that is by taking advantage of tkinter Variable classes to hold this hipotetic variable you're interested in. It looks like a boolean, so I used a tkinter BooleanVar to hold its value. One interesting thing about Variables is that you can have changes to their values "traced" by defining functions to be called whenever that happens. That's what I have done in the code below, and the callback function in this case — check_hipotetic_variable() — updates a Label to display the new value of the variable when it's called.
Below is your code with the modifications necessary to use a tkinter BooleanVar and trace changes to its value.
Mainfile.py
from tkinter import *
import Buttons
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
self.notice_lbl = Label(root, text='')
self.notice_lbl.pack(side=BOTTOM)
self.hipotetic_variable = BooleanVar(value=False)
# Set up a trace "write" callback for whenever its contents are changed.
self.hipotetic_variable.trace('w', self.check_hipotetic_variable)
self.branch = Buttons.New_Button(self.main_frame, self.hipotetic_variable)
root.mainloop()
def check_hipotetic_variable(self, *args):
"""Display value of the hipotetic variable."""
value = self.hipotetic_variable.get()
self.notice_lbl.config(text=f'hipotetic variable is: {value}')
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame, variable):
self.button_1 = Button(using_frame, text = 'Button 1',
command=functools.partial(self.Func, using_frame))
self.button_1.pack()
self.variable = variable # Save for use in callback.
def Func(self, to_destroy):
to_destroy.destroy()
self.variable.set(True) # # Change value of the variable.
P.S. I noticed you're not following the PEP 8 - Style Guide for Python Code, which makes reading your code harder to read and follow that if you're were following them — for that reason I strongly suggest you read the guide and start following the suggestions, especially the Naming Conventions which apply to functions and variable names, as well as the names of script files.

Python Tkinter Removing a Frame on button click

I'm trying to make an application that has the functionality to add and remove frames based on a button click. For now, I'm trying to get it to remove the last frames added from the root and will add in the functionality to specify what frame to delete later.
When I create the frame, I add it to a list of objects like this:
def AddFrame(self):
newFrame= customFrame(len(self.frameList))
self.frameList.append(newFrame)
newFrame.pack()
As I'm trying to delete it by reference, I'm using pop and pack_forget to both remove it from the list and removing it from the GUI. My basic function is:
def RemoveLastFrame(self):
if(len(self.frameList)>0):
self.frameList.pop().pack_forget()
I tried the pack_forget by testing it on a specified frame that I made in the init method. I'm not sure if this issue is coming from me trying to do this on a list of frame objects, or if it's due to the frame object's being a child of tk.Frame. Here is the child class in full:
class customFrame(tk.Frame):
def __init__(self, index):
super(customFrame, self).__init__()
self.index = index
self.buttons= []
self.addButton = tk.Button(command=self.AddButton,text='Add Button to Frame')
self.addButton.pack()
def AddButton(self):
newButton = customButton(len(self.buttons))
newButton.config(text=('Button '+str(newButton.index)))
self.buttons.append(newButton)
newButton.pack()
class customButton(tk.Button):
def __init__(self,index):
super(customButton, self).__init__()
self.index = index
I am an idiot. It was working, but it didn't unpack the children of the frame as well as I expected it to. Once I changed that, it works perfectly

Python: tkinter window presented in an OO manner

I have been trying to represent tkinter window in an OO manner. There are few answers here, and I have managed to produce a working code, but I simply do not understand why it's working.
import tkinter as tk
class CurrConv:
def __init__(self, window, date):
self.window = window
window.title("Currency Converter")
window.geometry("350x150+300+300")
self.label = tk.Label(text="Date: {}\n".format(date))
self.label.pack()
self.text_box = tk.Text()
self.text_box.insert("2.0", "100 is: {}\n".format(100))
self.text_box.insert("3.0", "24 is: {}".format(24))
self.text_box.pack()
self.button = tk.Button(text="Quit", width=8, command=window.quit)
self.button.place(relx=0.5, rely=0.9, anchor="center",)
def main():
window = tk.Tk()
app = CurrConv(window, 1234)
window.mainloop()
if __name__ == "__main__":
main()
The thing I don't understand is the usage of "app" object. It is used nowhere, and usually when we create an object (in any programing language), we invoke certain actions on it. However here we do nothing with the app object. Class encapsulation appears to indirectly modify "window", which is confusing, to say the least.
Next, I don't understand how labels and text boxes are added to "window", when in the code I nowhere create those in "window", I create them on "self", which would be "app", which is no longer used.
Bottom line is, for the reasons above, I do not understand how the above code works.
Thanks in advance.
I hope I was clear enough.
Class encapsulation appears to indirectly modify "window", which is confusing, to say the least.
Yes, that is what the code is doing, and it's incorrect IMHO. The code inside of CurrConv.__init__ at least arguably should not be directly modifying the root window for some of the very reasons you put in your question.
Next, I don't understand how labels and text boxes are added to "window", when in the code I nowhere create those in "window", I create them on "self", which would be "app", which is no longer used.
Your terminology is a bit incorrect. "I nowhere create those in 'window'" isn't true. By creating the widgets without an explicit master they default to being created in the root window. The reference to the widget is created in the class (self.label, etc) but the widget itself is created in the root window. IMHO this is also not the proper way to write a tkinter application. Explicit is better than implicit.
For me, the proper way to write this class would be for CurrConv to inherit from a tkinter Frame (or some other widget). Everything it creates should go inside itself, and then the code that creates the instance of CurrConv is responsible for adding it to a window. This solves your problem of not being able to create multiple windows.
Also, the widgets inside of CurrConv should explicitly set the master of any child widgets it creates rather than relying on a default master.
Example:
class CurrConv(tk.Frame):
def __init__(self, window, date):
super().__init__(window)
self.label = tk.Label(self, ...)
self.text_box = tk.Text(self, ...)
self.button = tk.Button(self, ...)
...
...
With that, everything is nicely encapsulated inside the class, and you can create multiple instances inside multiple windows very easily:
root = tk.Tk()
conv1 = CurrConv(root)
conv1.pack(fill="both", expand=True)
top = tk.Toplevel(root)
conv2 = CurrConv(top)
conv2.pack(fill="both", expand=True)
In the above case, it's perfectly fine for the class to modify the window because it's part of the code that creates the window in the first place.
You can still use a class for the application as a whole which you can use to hold some global state. To further prove the usefulness of creating CurrConv as a subclass of Frame, the following example adds currency converters to a notebook instead of separate windows.
class CurrConvApp():
def __init__(self):
self.root = tk.Tk()
self.root.title("Currency Converter")
self.root.geometry("350x150+300+300")
self.notebook = ttk.Notebook(self.root)
cc1 = CurrConv(self.notebook)
cc2 = CurrConv(self.notebook)
self.notebook.add(cc1, text="USD to EUR")
self.notebook.add(cc2, text="EUR to USD")
...
def start(self):
self.root.mainloop()
if __name__ == "__main__":
app = CurrConvApp()
app.start()
For the first question, yes app is an object. But it is an instance of the class CurrConv. When you initialize a class, you call the class's __init__ method, and this case, by doing so, you execute the statements in that method: modifying the window (which you passed as a parameter whin you created app) and adding widgets to it. So although app is not directly used, it had the side effect of doing those things when it was created. And for that reason, since you only need the initialization method, assigning to a variable is not necessary, you can just make it like CurrConv(window, 1234).
For the second, yes, you didn't mention window when you created the widgets, but when the master of a new Tkinter widget is not specified, it takes the main master (the root, created using tk.Tk()) as it's master.

Configuring values of combobox in a tablelist using tablelist wrapper in python

Basically my problem is in configuring a combobox in a tablelist written in python (not tcl directly). I have prepared an example that could demonstrate the problem, but before that lets see the required steps to run it:
Copy the tablelist wrapper from here and save it as 'tablelist.py', then put it at the example code directory.
Download "tklib-0.5" from here and copy "tablelist" directory from "modules" directory to the directory of example code.
Here it is the code:
from tkinter import *
from tkinter import ttk
from tablelist import TableList
class Window (Frame):
def __init__(self):
# frame
Frame.__init__(self)
self.grid()
# tablelist
self.tableList = TableList(self,
columns = (0, "Parity"),
editstartcommand=self.editStartCmd
)
self.tableList.grid()
# configure column #0
self.tableList.columnconfigure(0, editable="yes", editwindow="ttk::combobox")
# insert an item
self.tableList.insert(END,('Even'))
def editStartCmd(self, table, row, col, text):
#
# must configure "values" option of Combobox here!
#
return
def main():
Window().mainloop()
if __name__ == '__main__':
main()
As you would see it results in a single column/cell window with an initial value (Even). By clicking on the cell the combobox will appear (because of using "editstartcommand") and it doesn't have any values (None). I know for editing cell's widgets one has to use "editwinpath" command to get pathname of temporary widget, but the method just returns a string of the address referring to combobox widget that is not callable.
I'd appreciate any help or possible solutions.
I know it's a bit weird that I'm responding to my own question, but I'm hopeful that it could be useful to others in the future. After reading tablelist scripts and examples in TCL, I was able to find my answer. The answer has two different parts, one is related to the wrapper (tablelist wrapper in python that you can find it here), and the other one resides in my example code. First of all the wrapper needs to be modified to include widgets' pathnames in its "configure" method of "TableList" class. Here it is the modified version:
def configure(self, pathname, cnf={}, **kw):
"""Queries or modifies the configuration options of the
widget.
If no option is specified, the command returns a list
describing all of the available options for def (see
Tk_ConfigureInfo for information on the format of this list).
If option is specified with no value, then the command
returns a list describing the one named option (this list
will be identical to the corresponding sublist of the value
returned if no option is specified). If one or more
option-value pairs are specified, then the command modifies
the given widget option(s) to have the given value(s); in
this case the return value is an empty string. option may
have any of the values accepted by the tablelist::tablelist
command.
"""
return self.tk.call((pathname, "configure") +
self._options(cnf, kw))
Second and last part is the obligation that the "editstartcommand" (in case of my example code "editStartCmd" method) has to return its "text" variable at the end of it. This trick would prevent entries from turning to "None" as you click on them. The correct form of example code is:
from tkinter import *
from tkinter import ttk
from tablelist import TableList
class Window (Frame):
def __init__(self):
# frame
Frame.__init__(self)
self.grid()
# tablelist
self.tableList = TableList(self,
columns = (0, "Parity"),
editstartcommand=self.editStartCmd
)
self.tableList.grid()
# configure column #0
self.tableList.columnconfigure(0, editable="yes", editwindow="ttk::combobox")
# insert an item
self.tableList.insert(END,('Even'))
def editStartCmd(self, table, row, col, text):
#
# must configure "values" option of Combobox here!
#
pathname = self.tableList.editwinpath()
self.tableList.configure(pathname, values=('Even','Odd'))
return text
def main():
Window().mainloop()
if __name__ == '__main__':
main()
That's it and I hope it was clear enough.

Accessing Temp Directory On Tkinter

im new in this page and i love the answers nice job, with all the help of users, im new on python and wanna make a print of dir in a entry or label doesnt matter for example:
def directory(): os.listdir('/')
files=StringVar()
files.set(directory)
entry=Entry(root, textvariable=files).grid()
obviously in tkinter, the last code make a "print" a list of directories, but make me a list horizontal with this ',' for each folder different, i want it vertical list on this "entry" or "label", suppose later need a scroll bar but, there no is a problem, and make the same for temporal folder on windows look like that...
def directory(): os.listdir('%temp%')
files=StringVar()
files.set(directory)
entry=Entry(root, textvariable=files).grid()
but this %temp% doesnt works directly on python how can i make a listdir of folder?
Since displaying the contents of a directory is going to generally require multiple lines of text, one for each item in it, you have to use atk.Labelortk.Textwidget since a tk.Entrycan only handle a single line of text.
In addition you'll need to convert thelistthat os.listdir() returns into a multi-line string before setting itstextoption. Regardless, in order to be use yourdirectory() function needs to return a value to be useful.
The following code does these basic things and shows how to expand the value of the%temp%environment variable usingos.path.expandvars(). Alternatively, you can get the same value by using thetempfile.gettempdir()function Sukrit recommended.
import os
from Tkinter import *
def directory(folder):
return '\n'.join(os.listdir(folder)) # turn list into multiline string
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
files = directory(os.path.expandvars('%temp%'))
self.label = Label(root, text=files)
self.label.pack(side=LEFT)
root = Tk()
app = App(root)
root.mainloop()

Categories

Resources