Add check-boxes to scrollable image in python - python

Based on the excellent answer of this question: Show Large Image using Scrollbar in Python
… I have been able to create a scrollable image frame, using this code, in Windows, Python 3:
import tkinter
from PIL import ImageTk, Image
class ScrollableImage(tkinter.Canvas):
def __init__(self, master=None, **kw):
self.image = kw.pop('image', None)
super(ScrollableImage, self).__init__(master=master, **kw)
self['highlightthickness'] = 0
self.propagate(0) # wont let the scrollbars rule the size of Canvas
self.create_image(0,0, anchor='nw', image=self.image)
# Vertical and Horizontal scrollbars
self.v_scroll = tkinter.Scrollbar(self, orient='vertical', width=6)
self.h_scroll = tkinter.Scrollbar(self, orient='horizontal', width=6)
self.v_scroll.pack(side='right', fill='y')
self.h_scroll.pack(side='bottom', fill='x')
# Set the scrollbars to the canvas
self.config(xscrollcommand=self.h_scroll.set,
yscrollcommand=self.v_scroll.set)
# Set canvas view to the scrollbars
self.v_scroll.config(command=self.yview)
self.h_scroll.config(command=self.xview)
# Assign the region to be scrolled
self.config(scrollregion=self.bbox('all'))
self.focus_set()
self.bind_class(self, "<MouseWheel>", self.mouse_scroll)
def mouse_scroll(self, evt):
if evt.state == 0 :
# self.yview_scroll(-1*(evt.delta), 'units') # For MacOS
self.yview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows
if evt.state == 1:
# self.xview_scroll(-1*(evt.delta), 'units') # For MacOS
self.xview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows
Now, I would like to add a row of checkboxes with numbers based on a given list numbers. The default option should be that all numbers are checked. One can then uncheck some numbers, and then when a bottom is pressed, all checked numbers should be stored in a new list, i.e. checked_numbers.
This is how it could look like:
This is what I have tried so far:
root = tkinter.Tk()
# Creating the check-boxes using a loop
numbers = [3,16,18,22,45]
for ii in numbers:
var = tkinter.IntVar()
c = tkinter.Checkbutton(root, text=str(ii), variable=var)
c.pack()
# PhotoImage from tkinter only supports:- PGM, PPM, GIF, PNG format.
# To use more formats use PIL ImageTk.PhotoImage
img = tkinter.PhotoImage(file='PATH TO IMAGE')
show_image = ScrollableImage(root, image=img, width=400, height=600)
show_image.pack()
This is how it looks:
There are obviously a lot of flaws and things that I don't know how to implement:
How to assign a new variable in every loop ?
How to have all checked-boxes in a row instead of a column ?
How to store which variable has been checked ?
How to store the checked variables as a new list, when a button is pressed ?
Edit
I tried to use the nice answer from #ncica, which works perfectly on its own, and added the scrollable image...
import tkinter as tk
from tkinter import *
root = tk.Tk()
def read_states():
arry = list(map(lambda var: var.get(), states)) # store which variable has been checked
checked = []
result=(zip(numbers,arry))
for item in result:
if item[1] == 1:
checked.append(item[0]) # store the checked variables in a new list "checked"
print ("Checked are:{}".format(checked))
states = []
numbers = [3,16,18,22,45]
for ii in numbers:
var = IntVar()
chk = Checkbutton(root, text=str(ii), variable=var) # assign a new variable in every loop
chk.grid(row=1,column =numbers.index(ii)) # all checked-boxes put in a row
states.append(var)
button = tk.Button(root, text='OK', command=read_states)
button.grid(row=1,column =len(numbers)+1)
# PhotoImage from tkinter only supports:- PGM, PPM, GIF, PNG format.
# To use more formats use PIL ImageTk.PhotoImage
img = tk.PhotoImage(file='IMAGE_FILE_PATH')
show_image = ScrollableImage(root, image=img, width=400, height=600)
show_image.pack()
root.mainloop()
...but I get the following error:
TclError Traceback (most recent call last)
<ipython-input-11-1556cebf9634> in <module>()
31
32 show_image = ScrollableImage(root, image=img, width=400, height=600)
---> 33 show_image.pack()
34
35
~\Anaconda3\lib\tkinter\__init__.py in pack_configure(self, cnf, **kw)
2138 self.tk.call(
2139 ('pack', 'configure', self._w)
-> 2140 + self._options(cnf, kw))
2141 pack = configure = config = pack_configure
2142 def pack_forget(self):
TclError: cannot use geometry manager pack inside . which already has slaves managed by grid
This can be resolved by replacing the line show_image.pack() with show_image.grid(row=2,column =1, columnspan=len(numbers)+1)

import Tkinter as tk
from Tkinter import *
root = tk.Tk()
def read_states():
arry = list(map(lambda var: var.get(), states)) # store which variable has been checked
checked = []
result=(zip(numbers,arry))
for item in result:
if item[1] == 1:
checked.append(item[0]) # store the checked variables in a new list "checked"
print ("Checked are:{}".format(checked))
states = []
numbers = [3,16,18,22,45]
for ii in numbers:
var = IntVar()
var.set(1) # by default set Checkbuttons as checked
chk = Checkbutton(root, text=str(ii), variable=var) # assign a new variable in every loop
chk.grid(row=1,column =numbers.index(ii)) # all checked-boxes put in a row
states.append(var)
button = tk.Button(root, text='OK', command=read_states) # on button clicked read_states
button.grid(row=1,column =len(numbers)+1) # put button after all Checkbuttons
root.mainloop()
output:
on button OK, checked objects will be printed

Related

How to display the label ( text) value dynamically based on combo box selection value ( List box) in Tkinter?

I am new to tkinter application. The below code is working fine. Please help how to implement mentioned features.
The dynamic value should be displayed above clear button or below the combo box ( Used pack is bottom )- Now working
Clear the label value on combo box selection.
import tkinter as tk
from tkinter import ttk
from tkinter import *
from datetime import datetime
# root window
root = tk.Tk()
root.geometry("500x350")
root.resizable(False, False)
root.title('Test')
# Log Generator in frame
Generator = tk.Frame(root)
Generator.pack(padx=10, pady=10, fill='x', expand=True)
def clear():
combo.set('')
# Function to print the index of selected option
# in Combobox
def get_log_file_name(*arg):
date_Value = datetime.now().strftime("%Y_%m_%d_%I%M%S")
output_file_name_value = "Log_"+date_Value
if var.get() == "apple":
Label(Generator, text="The value at index: "+output_file_name_value+".txt", font=('Helvetica 12')).pack()
else:
Label(Generator, text="The value at index: "+output_file_name_value+".html", font=('Helvetica 12')).pack()
# Define Tuple of months
months = ('apple','banana')
# Create a Combobox widget
label = ttk.Label(Generator, text="Selection_Option:",font=('Helvetica', 10, 'bold'))
label.pack(fill='x', expand=True)
var = StringVar()
combo = ttk.Combobox(Generator, textvariable=var)
combo['values'] = months
combo['state'] = 'readonly'
combo.pack(padx=5, pady=5)
# Set the tracing for the given variable
var.trace('w', get_log_file_name)
# Create a button to clear the selected combobox
# text value
button = Button(Generator, text="Clear", command=clear)
button.pack(side=left)
# Make infinite loop for displaying app on
# the screen
Generator.mainloop()
Clear the label value on combo box selection.
You need to capture the ComboboxSelect event to do that and the function to execute if captured
the function should be like this
What you want to do here, is to capture the combobox event, and then, do the label configuration when capturing it,
Below is the code to do the thing. and you can add code there.
def comboboxEventCapture(e=None):
label.configure(text='')
# Your code after resetting variables!
Here's the event capturing part
combo.bind("<<ComboboxSelect>>", comboboxEventCapture)
You can name the function whatever you want though.
Note that the arguement e is needed because if the event is captured, the event itself is passed as a parameter into the function, that is of no use here (unless you are going to do something with it, then use e.objname)
The dynamic value should be displayed above clear button
The second label could be outside of get_log_file_name() function.
And also configure inside function. So you don't do duplicate Label widget, naming Label2
Also the pack() must be split to prevent an error.
To clear Label2 use .configure(text='')
We will be using ttk. So don't do this from tkinter import *
Code:
import tkinter as tk
from tkinter import ttk
from datetime import datetime
root = tk.Tk()
root.geometry("500x350")
root.resizable(False, False)
root.title('Test')
Generator = tk.Frame(root)
Generator.pack(padx=10, pady=10, fill='x', expand=True)
def clear():
label2.configure(text='')
def get_log_file_name(*arg):
date_Value = datetime.now().strftime("%Y_%m_%d_%I%M%S")
output_file_name_value = "Log_"+date_Value
if var.get() == "apple":
label2.configure(text="The value at index: "+output_file_name_value+".txt", font=('Helvetica 12'))
else:
label2.configure(text="The value at index: "+output_file_name_value+".html", font=('Helvetica 12'))
# Define Tuple of months
months = ('apple','banana')
# Create a Combobox widget
label2 = ttk.Label(Generator)
label2.pack()
label = ttk.Label(Generator, text="Selection_Option:",font=('Helvetica', 10, 'bold'))
label.pack(fill='x', expand=True)
var = tk.StringVar()
combo = ttk.Combobox(Generator, textvariable=var)
combo['values'] = months
combo['state'] = 'readonly'
combo.pack(padx=5, pady=5)
# Set the tracing for the given variable
var.trace('w', get_log_file_name)
# Create a button to clear the selected combobox
# text value
button = ttk.Button(Generator, text="Clear", command=clear)
button.pack(side='left')
# Make infinite loop for displaying app on
# the screen
Generator.mainloop()
Screenshot for apple:
Screenshot for banana:
Screenshot to clear Label2:

Python Tkinter Moving images on buttonpress

I am trying to write a Tkinter code where it image, which looks like rain will start to move, if button called "Rain" is pressed.
I can not yet tell how the image move part works, but the problem is that when I click on the "Rain" button, it writes -> "Rain" like it should but no image appears on Canvas.
Another interesting thing is that when i take
Here is my code:
root = Tk()
#Create the canvas
canvas = Canvas(width=1000, height=1000)
canvas.pack()
#This is the part that does not work
#Nothing appears when this function is called
def Rain():
image3 = "Drops.png"
drops = PhotoImage(file = image3)
drops_background = canvas1.create_image(100, 100, image=drops)
while True:
canvas1.move(drops_background, 10, 10)
print("Rain")
#Adding a button and making it to use function "Rain"
frame = Frame(root)
frame.pack()
button1 = Button(frame, text = "Rain", command = Rain, fg = "red" ).pack(side = LEFT)
root.mainloop()
Another interesting thing is that if I place this part out of the function it starts working.
image3 = "Drops.png"
drops = PhotoImage(file = image3)
drops_background = canvas1.create_image(100, 100, image=drops)
If anyone could tell me what is wrong here or at least point me in the right direction that would help me out a lot.
There is issue in PhotoImage (or rather in PIL and Pillow module) - PhotoImage must be assigned to global variable.
If PhotoImage is assigned to local variable then Garbage Collector remove it from memory.
My full working example with after
import Tkinter as tk
import random
# --- globals ---
drops_background = None
drops = None
# --- functions ---
def rain():
global drops_background
global drops
filename = "Drops.png"
drops = tk.PhotoImage(file=filename) # there is some error in PhotoImage - it have to be assigned to global variable
drops_background = canvas.create_image(100, 100, image=drops)
# move after 250ms
root.after(250, move) # 250ms = 0.25s
def move():
global drops_background
# TODO: calculate new position
x = random.randint(-10, 10)
y = random.randint(-10, 10)
# move object
canvas.move(drops_background, x, y)
# repeat move after 250ms
root.after(250, move) # 250ms = 0.25s
# --- main ----
root = tk.Tk()
#Create the canvas
canvas = tk.Canvas(root, width=1000, height=1000)
canvas.pack()
#This is the part that does not work
#Nothing appears when this function is called
#Adding a button and making it to use function "Rain"
frame = tk.Frame(root)
frame.pack()
button1 = tk.Button(frame, text="Rain", command=rain, fg="red" )
button1.pack(side=tk.LEFT)
root.mainloop()
after add function and time to its list and mainloop run function from this list after given time.
after expects function name without ()

How to change the colour of everything in a tkinter GUI at once

I have some code (as shown below) which prompts the user to select which colour to change the GUI to. But my problem is that it only changes the background. I'd like to know if there's a way to change the background of every label and button at once or do I have to change each label/button individually.
import tkinter
window = tkinter.Tk()
colour_frame = tkinter.Frame(window)
options_frame = tkinter.Frame(window)
def colours():
options_frame.pack_forget()
red.pack()
orange.pack()
back_button.pack()
colour_frame.pack()
def back():
options_frame.pack()
colour_frame.pack_forget()
def make_red():
window.configure(background="red")
def make_orange():
window.configure(background="orange")
colour_button = tkinter.Button(options_frame, text="Appearance", command=colours)
red = tkinter.Button(colour_frame, text="RED", command=make_red)
red.configure(bg = "red")
orange = tkinter.Button(colour_frame, text="ORANGE", command=make_orange)
orange.configure(bg = "orange")
back_button = tkinter.Button(colour_frame, text="Back", command=back)
window.mainloop()
You can make a list containing all your widgets you want to change
myWidgets = [button1, label1, ... ] # List of widgets to change colour
for wid in myWidgets:
wid.configure(bg = newColour)
Here's an example code of changing the background colour of multiple labels at once.
import tkinter as tk
# Change all label backgrounds
def change_colour():
c = user.get() #Get the entered text of the Entry widget
for wid in widget_list:
wid.configure(bg = c)
# Create GUI
root = tk.Tk()
tk.Label(root, text='Enter a colour').pack()
user = tk.Entry(root)
user.pack()
label_frame = tk.Frame(root)
label_frame.pack()
btn = tk.Button(root, text='Change Colour', command = change_colour)
btn.pack()
widget_list = [user, btn] # Add defined widgets to list
#Dynamicly create labels for example
for x in range(10):
lbl = tk.Label(label_frame, text='Label '+str(x))
lbl.pack(side = tk.LEFT)
widget_list.append(lbl) #Add widget object to list
root.mainloop()
Or if you have a Frame already containing all the widgets you want to change, then you can use this instead.
parent_widget.winfo_children() will return a list containing all the widgets stored inside the parent widget
def change_colour():
c = user.get()
for wid in label_frame.winfo_children():
wid.configure(bg = c)
Try using ttk for some of your GUI elements. ttk allows you to create styles for widgets and update the style to all widgets at once (at least for those that have the same style). You may need to mix the usage of ttk and tkinter, but it should make things a bit easier in the long run. Here is an example I made:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
# Creating a style for the buttons
color_style_button = ttk.Style()
color_style_button.configure("color.TButton", foreground="red")
def change_color(color):
# This function changes the style to all buttons using the "color.Button style"
if color == "red":
color_style_button.configure("color.TButton", foreground="red")
elif color == "blue":
color_style_button.configure("color.TButton", foreground="blue")
elif color == "green":
color_style_button.configure("color.TButton", foreground="green")
frame_a = ttk.Frame(root)
frame_a.pack()
red_button = ttk.Button(frame_a, text="Red", command=lambda: change_color("red"), style="color.TButton")
red_button.pack()
blue_button = ttk.Button(frame_a, text="Blue", command=lambda: change_color("blue"), style="color.TButton")
blue_button.pack()
green_button = ttk.Button(frame_a, text="Blue", command=lambda: change_color("green"), style="color.TButton")
green_button.pack()
root.mainloop()
I recommend checking out this site to learn more about ttk and styles.

bring window to top level

I want to present several questions, one after another. The first question is shown as I like, with the cursor set in the entry field. Then I destroy the window and call the function again to create a new window. This time the window is not shown in the front and therefore I first have to click on the screen in order to have the cursor set to the entry field. Also the escape key does not work until I click on the screen to bring the window to the top. I'd be very happy for your help!
Thank you in advance!
Here's my code:
from Tkinter import *
def text_input_restricted(fn,question, nr_letters, limit, len_min, len_max,keys, justify):
class MyApp():
def validate(root, S):
return all(c in keys for c in S)
def __init__(self, q= None):
#save response after "next"-button has been clicked
def okClicked():
lines = e.get()
if len_min < len(lines) < len_max:
lines = unicode(lines).encode('utf-8')
datFile = open(fn, "a")
datFile.write(" '%s'"%(lines))
datFile.close()
self.root.destroy()
self.root = Tk()
vcmd = (self.root.register(self.validate), '%S')
#quit if escape-key has been pressed
self.root.bind('<Escape>', lambda q: quit())
#colors
color = '#%02x%02x%02x' % (200, 200, 200)
self.root.configure(bg=color)
#set window size to screen size
RWidth=MAXX
RHeight=MAXY
self.root.geometry(("%dx%d")%(RWidth,RHeight))
#remove buttons (cross, minimize, maximize)
self.root.overrideredirect(1)
#remove title
self.root.title("")
#item
labelWidget = Label(self.root,text=question, font=("Arial", int(0.02*MAXX)), bd=5, bg=color, justify="center")
labelWidget.place(x=0, y=RHeight/40,width=RWidth)
#"next"-button
ok_width = RWidth/15
ok_height = RWidth/15
okWidget = Button(self.root, text= "next", command = okClicked, font=("Arial",int(0.015*MAXX)), bd=5, justify="center")
okWidget.place(x=RWidth/2-ok_width/2,y=13*RHeight/40, width=ok_width,height=ok_height)
def callback(sv):
c = sv.get()[0:limit]
sv.set(c)
sv = StringVar()
width=nr_letters * int(0.02*MAXX)*1.3
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(self.root, textvariable=sv,font=("Arial", int(0.02*MAXX)),justify=justify,validate="key", validatecommand=vcmd)
e.place(x=RWidth/2-width/2, y=9*RHeight/40, width=width)
#show cursor
e.focus_set()
self.root.mainloop()
MyApp()
MAXX=1366
MAXY=768
fn = "D:/test.dat"
text_input_restricted(fn = fn, question=u"f for female, m for male", nr_letters=1, limit =1, len_min =0, len_max=2, keys = 'fm', justify="center")
text_input_restricted(fn = fn, question="How old are you?", nr_letters=2,limit=2, len_min = 1, len_max = 3, keys = '1234567890',justify="center")
In Tk you use the raise command to bring a window to the front of the Z-order. However, raise is a keyword in Python so this has been renamed to lift. Provided your application is still the foreground application you can call the lift() method on a toplevel widget. If the application is not the foreground application then this will raise the window but only above other windows from the same application. On Windows this causes the taskbar icon for your application to start flashing.
You might do better to destroy the contents of the toplevel and replace them. Or even better - create a number of frames holding each 'page' of your application and toggle the visibility of each frame by packing and pack_forgetting (or grid and grid forget). This will avoid loosing the focus completely - you can just set the focus onto the first widget of each frame as you make it visible.

How to add Autoscroll on insert in Tkinter Listbox?

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 )

Categories