Display and Hide PNG image in Tkinter Python3 - python-3.x

Like the topic,
how can I display a .png image and hide it? And what is the difference between canvas and photoimage?

I wrote a bit of code which hides/shows an image on the click of a button. You can edit it to fit your needs.
NOTE: At the moment, I'm using pack(), and pack_forget(), if you want to use grid or place, you must use grid_forget() or place_forget()
import tkinter
def hideBG():
global state
if state == "Hidden":
background_label.pack()
state = "Showing"
elif state == "Showing":
background_label.pack_forget()
state = "Hidden"
window = tkinter.Tk()
background_image=tkinter.PhotoImage(file="BG.png")
background_label = tkinter.Label(window, image=background_image)
hideBttn = tkinter.Button(window, text="Hide Background", command=hideBG)
state = "Showing"
hideBttn.pack()
background_label.pack()
window.mainloop()
This creates an image within a label, and a button. The button takes the current "state" of the image (whether it is hidden or showing), and changes it to the opposite, by calling the hideBG function when the button is pressed.
Hope this helps!

Related

How to remove focus from Text block by clicking outside it

I want to create a program using tkinter. The goal is to remove the focus from Text widget by clicking somewhere outside it. I've tried this:
import tkinter as tk
root = tk.Tk()
txt = tk.Text(root)
txt.pack()
root.bind('<Button-1>', lambda event: root.focus_set())
#txt.bind('<Button-1>', lambda event: txt.focus_set()) <- it doesn't work with this too
root.mainloop()
But this just sets focus to root every time i press B1 (in both variants). Help, please!
You could detect the current widget under the mouse coordinates and use this to determine if you should focus on root or not:
def click_event(event):
x,y = root.winfo_pointerxy() # get the mouse position on screen
widget = root.winfo_containing(x,y) # identify the widget at this location
if (widget == ".text_widget") == False: # if the mouse is not over the text widget
root.focus() # focus on root
text_widget = tk.Text(root, name="text_widget")
text_widget.pack()
root.bind("<Button-1>", click_event)

How can I change the state of a menu with a button inside tkinter?

I want to make a unit converter in tkinter. I made two drop-down menus; the first one allows the user to select the unit they want to convert from, and the second one allows them to choose the unit they want to convert to. I want to disable all the options that do not make sense in the second menu after they have selected an option in the first one (if they want to convert kilograms it would not make sense to choose centimeters in the second menu)
I have tried to use a StringVar() to change the state of the menu, but it is not working. I have no idea of what to do next. I have been using the documentation of Tutorialspoint, but I cannot find anything that works (first time using tkinter).
import tkinter as tk
root = tk.Tk()
root.geometry('600x600')
my_var = tk.StringVar()
my_var.set('active')
unit_1 = tk.Menubutton(root,text='This is the first menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_1 = tk.Menu(unit_1)
unit_1.config(menu=menu_1)
menu_1.add_command(label='Inches',command= lambda: my_var.set('disabled') )
menu_1.add_command(label='Kilograms')
unit_2 = tk.Menubutton(root,text='This is the second menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_2 = tk.Menu(unit_2)
unit_2.config(menu=menu_2)
menu_2.add_command(label='Centimeters')
menu_2.add_command(label='Pounds',state= my_var.get())
unit_1.place(relx=0.03,rely=0.08,relheight=0.04,relwidth=0.45)
unit_2.place(relx=0.52,rely=0.08,relheight=0.04,relwidth=0.45)
root.mainloop()
Here I am trying to make the button 'Inches' in the first menu to disable the button 'Pounds' in the second menu, but when I click on 'Inches' nothing happens to 'Pounds'.
tk.StringVar() is used to change text on something, for example if you want to have a button with dynamic text, you may want to use a tk.StringVar() for that.
What you want to do is something different; you want to change the configuration of a label. So you need to find the element and adjust its state:
import tkinter as tk
root = tk.Tk()
root.geometry('600x600')
my_var = tk.StringVar()
my_var.set('active')
unit_1 = tk.Menubutton(root,text='This is the first menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_1 = tk.Menu(unit_1)
unit_1.config(menu=menu_1)
menu_1.add_command(label='Inches', command=lambda: disable_pounds())
menu_1.add_command(label='Kilograms', command=lambda: disable_centimeters())
unit_2 = tk.Menubutton(root,text='This is the second menu button',bg='white',activebackground='#2E64FE',activeforeground='#FFFFFF')
menu_2 = tk.Menu(unit_2)
unit_2.config(menu=menu_2)
menu_2.add_command(label='Centimeters')
menu_2.add_command(label='Pounds',state= my_var.get())
unit_1.place(relx=0.03,rely=0.08,relheight=0.04,relwidth=0.45)
unit_2.place(relx=0.52,rely=0.08,relheight=0.04,relwidth=0.45)
def disable_pounds():
menu_2.entryconfig("Pounds", state="disabled")
menu_2.entryconfig("Centimeters", state="active")
def disable_centimeters():
menu_2.entryconfig("Pounds", state="active")
menu_2.entryconfig("Centimeters", state="disabled")
root.mainloop()

Is it possible to make Tkinter scrollbars move independently of each other in different Toplevel windows?

Imagine there are two Tkinter.Toplevel() windows, called Window_1 and Window_2, which can be opened by clicking the same button (lets called Button_0).
Button_0 is pressed and Window_1 pops up. In Window_1, I can scroll up and down using a mouse pad (MAC OS). After that, I left Window_1 open.
Button_0 is pressed again and Window_2 pops up, while Window_1 stays open. In Window_2, I can again scroll up and down.
Now, I go back to Window_1 and try to scroll using mouse pad, contents in Window_1 DO NOT MOVE, but contents in Window_2 DO MOVE.
Then I close Window_2, and try to scroll on Window_1, this time I got error messages asking for a canvas on Window_2.
I did bind function,
def on_vertical(canvas,event):
canvas.yview_scroll(-3 * event.delta, 'units')
to a canvas inside each windows. As far as I know about the error, it seems that this function could not be used twice at the same time (both windows are opened).
I would like the way that when both Windows stay open. While on each window, I can scroll up-down while the another one do not move. Is it possible to code that?
This is the code example (please do noted that the Window name is not corrected label.)
from tkinter import *
######################## FUNCTIONS (DEF) ########################
def on_vertical(canvas,event):
canvas.yview_scroll(-3 * event.delta, 'units')
######################## FUNCTIONS (CLASS) ########################
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
#INITIAL WINDOW
def init_window(self):
self.master.title("Main Window")
self.pack(fill=BOTH, expand=1)
Button(self, text="Button_0",command = self.load_and_print).place(x = 7, y = 95)
# creating a button instance
Button(self, text="EXIT PROGRAM", command=self.client_exit).place(x=500, y=250)
#OPEN A NEW WINDOW CONTAINING STOCK LISTS
def load_and_print(self):
new_window = Toplevel(self)
new_window.title("Window")
canvas = Canvas(new_window, width = 800, height = 500, scrollregion = (0, 0, 0, 2500))
frame = Frame(canvas)
vbar = Scrollbar(new_window, orient = VERTICAL, command = canvas.yview)
vbar.pack(side = RIGHT,fill = Y)
canvas.create_window(0,0, window = frame, anchor = NW)
canvas.config(yscrollcommand = vbar.set)
canvas.pack(side = TOP,expand = True,fill = BOTH)
canvas.bind_all('<MouseWheel>', lambda event, canvas=canvas: on_vertical(canvas,event))
#MAKE PROGRAM EXIT
def client_exit(self):
exit()
######################## MAIN PROGRAMME ########################
#call window
root = Tk()
#size of the window
root.geometry("700x300")
app = Window(root)
root.mainloop()
root.update()
The problem is that you are using bind_all instead of bind for the mousewheel event.
Because you're using bind_all, each time you create a new window it replaces the old binding with a new binding. No matter which window gets the event, your function will always only work for the last window to be created. And, of course, when that window is destroyed then the mouse binding will throw an error since the canvas no longer exists.
Using bind
One solution is simple: use bind instead of bind_all.
canvas.bind_all('<MouseWheel>', lambda event, canvas=canvas: on_vertical(canvas,event))
Using bind_all
If you want the benefits of bind_all -- namely, that the scrolling works even if the mouse is over some other widget, you need to modify on_vertical to figure out which canvas to scroll at the time that it is running instead of having the canvas being passed in.
You can do that with a little bit of introspection. For example, the event object knows which widget received the event. From that you can figure out which window the mouse is in, and from that you can figure out which canvas to scroll.
For example, move the binding up to the __init__ and change it like this:
self.bind_all('<MouseWheel>', on_vertical)
Next, change on_vertical to figure out which canvas to scroll. In the following example I assume each toplevel has exactly one canvas and that you always want to scroll that canvas (ie: you lose the ability to scroll text widgets and listboxes)
If that's not the case, you can add whatever logic you want to figure out which widget to scroll.
def on_vertical(event):
top = event.widget.winfo_toplevel()
for child in top.winfo_children():
if child.winfo_class() == "Canvas":
child.yview_scroll(-3 * event.delta, 'units')
break

Tkinter Create buttons from list with different image

I want to create button from a list and I want them to have their own image.
I have tried this but only the last created button work.
liste_boutton = ['3DS','DS','GB']
for num,button_name in enumerate(liste_boutton):
button = Button(type_frame)
button['bg'] = "grey72"
photo = PhotoImage(file=".\dbinb\img\\{}.png".format(button_name))
button.config(image=photo, width="180", height="50")
button.grid(row=num, column=0, pady=5, padx=8)
Only your last button has an image as it is the only one that has reference in the global scope, or in any scope for that matter in your specific case. As is, you have only one referable button and image objects, namely button and photo.
Short answer would be:
photo = list()
for...
photo.append(PhotoImage(file=".\dbinb\img\\{}.png".format(button_name)))
But this would still have bad practice written all over it.
Your code appears okay, but you are need to keep a reference of the image effbot and also I do believe PhotoImage can only read GIF and PGM/PPM images from files, so you need another library PIL seems to be a good choice. I used a couple images from a google search for the images, they were placed in the same directory as my .py file, so i changed the code a little bit. Also the button width and height can cut off parts of the image if not careful.
from Tkinter import *
from PIL import Image, ImageTk
type_frame = Tk()
liste_boutton = ['3DS','DS','GB']
for num,button_name in enumerate(liste_boutton):
button = Button(type_frame)
button['bg'] = "grey72"
# this example works, if .py and images in same directory
image = Image.open("{}.png".format(button_name))
image = image.resize((180, 100), Image.ANTIALIAS) # resize the image to ratio needed, but there are better ways
photo = ImageTk.PhotoImage(image) # to support png, etc image files
button.image = photo # save reference
button.config(image=photo, width="180", height="100")
# be sure to check the width and height of the images, so there is no cut off
button.grid(row=num, column=0, pady=5, padx=8)
mainloop()
Output:
[https://i.stack.imgur.com/lxthT.png]
With all your comments I was able to achieve what I expected !
Thank ! I'm new to programming so this will not necessarily be the best solution but it works
import tkinter as tk
root = tk.Tk()
frame1 = tk.Frame(root)
frame1.pack(side=tk.TOP, fill=tk.X)
liste_boutton = ['3DS','DS','GB']
button = list()
photo = list()
for num,button_name in enumerate(liste_boutton):
button.append(tk.Button(frame1))
photo.append(tk.PhotoImage(file=".\dbinb\img\\{}.png".format(button_name)))
button[-1].config(bg="grey72",image=photo[-1], width="180", height="50")
button[-1].grid(row=num,column=0)
root.mainloop()

Button color not working , tkinter Python3 [duplicate]

I've been working through the Tkinter chapters in Programming Python and encountered a problem where the foreground and background colours of a button will not change. I am working on a Mac OS X 10.6 system with Python 2.6.1. The colours of a label will change, but not the colours of a button. For example:
from Tkinter import *
Label(None, text='label', fg='green', bg='black').pack()
Button(None, text='button', fg='green', bg='black').pack()
mainloop()
On my Mac system the colours of the label change, but the colours of the button do not. On a Windows system with Python 2.6.1 the colours of both the label and button change.
Anyone know what is going wrong?
I've checked Interface Builder and it appears that there is no option to change the foreground or background colour of a button in that tool. There is the ability to edit the foreground and background colours of a label.
The Mac OS X rendering system (Quartz?) may just not support (easily) changing the fg and bg of a button.
There is a solution for changing the background of buttons on Mac.
Use:
highlightbackground=color
For example:
submit = Button(root, text="Generate", highlightbackground='#3E4149')
This results in the following, a nice button that fits in with the background:
I think the answer is that the buttons on the mac simply don't support changing the background and foreground colors. As you've seen, this isn't unique to Tk.
You can do it with tkmacosx library from PyPI.
Installation:
For Python 2, use pip install tkmacosx.
For Python 3, use pip3 install tkmacosx.
This is how you use tkmacosx:
from tkinter import *
from tkmacosx import Button
root = Tk()
B1 = Button(root, text='Mac OSX', bg='black',fg='green', borderless=1)
B1.pack()
root.mainloop()
It works fine on Mac OS X.
For anyone else who happens upon this question as I did, the solution is to use the ttk module, which is available by default on OS X 10.7. Unfortunately, setting the background color still doesn't work out of the box, but text color does.
It requires a small change to the code:
Original:
from Tkinter import *
Label(None, text='label', fg='green', bg='black').pack()
Button(None, text='button', fg='green', bg='black').pack()
mainloop()
With ttk:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
# background="..." doesn't work...
ttk.Style().configure('green/black.TLabel', foreground='green', background='black')
ttk.Style().configure('green/black.TButton', foreground='green', background='black')
label = ttk.Label(root, text='I am a ttk.Label with text!', style='green/black.TLabel')
label.pack()
button = ttk.Button(root, text='Click Me!', style='green/black.TButton')
button.pack()
root.mainloop()
Its quite annoying that after years this is still a problem.
Anyways, as others have mentioned, highlightbackground (the border color) can be used in place of background on a Mac. If you increase the size of the border to be huge (the size of the button or greater), you will get a nice, solid background color. This will give your button the appearance of a label.
This works if you are using place, but not if you are using something like grid. With grid, increasing the border size increases the button size automatically, unfortunately.
However, if you must use grid, you can always hack it....create your colorless grid button. Next use place to parent a background color button on top of it. This will be the button with the 'command' on it or the button you bind events to.
If you want your code to be OS independent, you can either add an 'if OS == "Mac"' statement or even add a custom function that modifies the button if its on a Mac but leaves it alone on Windows or Linux. Here's the former:
from tkinter import *
import platform
if platform.system() == "Darwin": ### if its a Mac
B = Button(text="Refersh All Windows", highlightbackground="Yellow", fg="Black", highlightthickness=30)
else: ### if its Windows or Linux
B = Button(text="Refresh All Windows", bg="Yellow", fg="Black")
B.place(x=5, y=10, width=140, height=30)
mainloop()
This worked for me:
self.gnuplot_bt = Button(
self.run_but_container, text="Plot with Gnuplot", font="Helvetica", command=self.gnuplot,
highlightbackground ="#8EF0F7", pady=2, relief=FLAT
)
I was looking as to why this doesn't work as well. I found a quick way to try and fix it is to have a label and then bind a click with the label. Then have the label change colors for a short time to mimic clicking. Here is an example.
def buttonPress(*args):
searchB.config(state = "active")
searchB.update()
time.sleep(0.2)
searchB.config(state = "normal")
## Whatever command you want
searchB = Label(main, text = "Search", bg = "#fecc14", fg = "Black", activebackground = "Red", highlightbackground="Black")
searchB.bind("<Button-1>", startSearch)
searchB.pack()
Confirm following code can change the background of tkinter Button on Mac OS X.
self.btn_open = tk.Button(self.toolbar,
text = "Open",
command=self.open,
highlightbackground = "gray")
But it cannot change bg of ttk.Button.
Not sure if anyone is still viewing this thread, but I have created a simple solution to this problem by creating my own Button class. It is available on GitHub.
import tkinter as tk
class Button():
button_frame = None
root = None
width=100
height=20
text=""
bg="white"
fg="black"
font="f 12"
bordercolor = "black"
bordersize = 3
label = None
command = None
def __init__(self,root,width=100,height=20,text="",bg="white",fg="black",font="f 12",command=None,bordercolor="black",bordersize=0):
self.root = root
self.width=width
self.height=height
self.text=text
self.bg=bg
self.fg=fg
self.font=font
self.command = command
self.bordercolor = bordercolor
self.bordersize = bordersize
self.button_frame = tk.Frame(root,width=width,height=height,bg=bg)
self.label = tk.Label(self.button_frame,text=self.text,bg=self.bg,width=self.width,height=self.height,fg=self.fg,font=self.font,highlightbackground=self.bordercolor,highlightthickness=self.bordersize)
self.label.place(anchor="center",relx=0.5,rely=0.5,relheight=1,relwidth=1)
self.label.bind("<Button-1>",self.call_command)
def call_command(self,event):
if (self.command != None):
self.command()
def place(self,anchor="nw",relx=0,rely=0):
self.button_frame.place(anchor=anchor,relx=relx,rely=rely)
def configure(self,width=width,height=height,text=text,bg=bg,fg=fg,font=font,command=command,bordercolor=bordercolor,bordersize=bordersize):
self.button_frame.configure(width=width,height=height,bg=bg)
self.label.configure(text=text,bg=bg,width=width,height=height,fg=fg,font=font,highlightbackground=bordercolor,highlightthickness=bordersize)
self.command =
Button and Label seem pretty similar to me, so I find it odd that the Label and Button work differently... even after all these years.
You can always make your own Button class which is wrapped around a Label with a border (default width is 2) and a bind call for the Button Release. You'd miss out on some of the "animation" of button press and release, but you'd get your background and foreground colors as desired.
I wrote a project called Tagged Text Widgets ('ttwidgets' on PyPI.org) which essentially does just that. I wrote the project to allow multi-font, multi-color Buttons and Labels. Essentially the project creates a compound Button or Label consisting of multiple underlying Label widgets (each with its own color/font) but acting like a single object. Those different colors and fonts are created by passing in HTML-like tagged text in lieu of regular text. And because of the underlying Labels rather than Buttons, it works around the issue on macOS.
I just tested it on macOS Sierra, and it works around the Button bg/fg color problem.
You can use it as follows:
from ttwidgets import TTButton
A TTButton supports the full interface of a Tkinter Button but with many enhancements. But for someone trying to work around the macOS color issue, just using a TTButton in lieu of a Tkinter Button suffices.

Resources