I want to place a button in the upper right corner and have the button be an image. I understand about scoping/garbage-collection etc. and have seen all the other questions asked here that overlook this fact.
However, I have tried numerous methods including creating a self.photo and declaring photo as a global variable. I'm actually not even convinced that that's the issue, because I declare the photo in the same scope as I call the mainloop().
My code right now (which is mostly borrowed from Drag window when using overrideredirect since I'm not really familiar with tkinter):
import tkinter
pink="#DA02A7"
cyan="#02DAD8"
blue="#028BDA"
class Win(tkinter.Tk):
def __init__(self,master=None):
tkinter.Tk.__init__(self,master)
self.overrideredirect(True)
self._offsetx = 0
self._offsety = 0
self.bind('<Button-1>',self.clickwin)
self.bind('<B1-Motion>',self.dragwin)
self.geometry("500x500")
def dragwin(self,event):
x = self.winfo_pointerx() - self._offsetx
y = self.winfo_pointery() - self._offsety
self.geometry('+{x}+{y}'.format(x=x,y=y))
def clickwin(self,event):
self._offsetx = event.x
self._offsety = event.y
win = Win()
# put a close button
close_button = tkinter.Button(win, bd=0, command=win.destroy)
global photo
photo=tkinter.PhotoImage("close.gif")
close_button.config(image=photo, height="10", width="10")
# pack the widgets
close_button.pack(anchor=tkinter.NE)
win.configure(bg=pink)
win.mainloop()
The correct way to create the photoimage is by passing the path to the file parameter. Otherwise, your path gets assigned to the internal image name and thus no file will be associated with the image.
photo=tkinter.PhotoImage(file="close.gif")
I typically give PhotoImages a name and use the name in image parameters:
photo=tkinter.PhotoImage(name='close', file="close.gif")
close_button.config(image='close')
I'm not sure if this is the only way, but this works here.
Related
I want to pack some func. in a single widget so that I can interact with that particular widget using bind func.
There's Frame widget which packs up widgets in it and canvas.create_window func. in Canvas widget which also does the same as Frame.
Following program generates sticman after every 3 sec. And when user click the stickman, it disappears.
I tried using Frame to pack functions 🙁...
from Tkinter import *
root = Tk()
hitbox = Frame(root, height = 100, width= 100)
hitbox.pack()
can = Canvas(hitbox, height= 450, width= 1000) # Canvas inside hitbox
can.pack()
def stickman (a,b,c,d):
# Code that makes stickman according to coordinates
def HP(p,q):
# Code that makes Progressbar widget inside hitbox which act as healthbar
counter = 0
def init():
if counter == 10:
pass
else:
counter +=1
stickman(100,100,130,130)
HP(90, 120)
root.after(3000, init) # Stickman respawns after 3 seconds
def kill():
hitbox.destroy()
hitbox.bind('<Button-1>', kill)
root.mainloop()
Stickman respawns after every 3 seconds but bind func. with frame does not seems to be working when running code. Stickman doesn't disappears when clicked.
I think what you are asking for is to destroy the Tkinter frame when Button 1 or the left button on the mouse is clicked. The stickman doesn't disappear because the frame isn't actively listening for the key click unlike a label or an entry field would do.
So, there are 2 easy fixes for this problem:
1. Binding to Root Window
Binding the keybind to the root window would solve the problem mostly because the root window is always actively listening for keybinds unlike the frame or an input field. The only problem with this approach is that the user can click anywhere on the window to destroy the frame.
2. Binding to the Stickman Itself
Binding the keybind to the stickman itself is the cleanest approach because it would be just the same as the first solution but this time the user can only click on the stickman to destroy the frame. This is probably the solution you were looking for. To implement this solution just replace the root.bind('<Button-1>', kill) with stickman.bind('<Button-1>', kill) (or whatever the name is of your stickman is) after defining the stickman but before packing it.
I atatched a modified version of your code down below for the first option:
from tkinter import *
root = Tk()
def kill(event=None):
hitbox.destroy()
root.bind('<Button-1>', kill)
hitbox = Frame(root, height = 100, width= 100)
hitbox.pack()
can = Canvas(hitbox, height= 450, width= 1000) # Canvas inside hitbox
can.pack()
def stickman (a,b,c,d):
pass
# Code that makes stickman according to coordinates
def HP(p,q):
pass
# Code that makes Progressbar widget inside hitbox which act as healthbar
counter = 0
def init():
if counter == 10:
pass
else:
counter +=1
stickman(100,100,130,130)
HP(90, 120)
root.after(3000, init) # Stickman respawns after 3 seconds
root.mainloop()
i have a taskbar-like Frame, which contains custom Buttons with images. But everytime i click on this button, Tkinter displaced the button 1px to the right/buttom.
Is it possible to override this behaviour? Or do i have to derived from Tkinter.Label instead of Tkinter.Button ?
edit:
Adding some code:
import Tkinter
import logging
logger = logging.getLogger(__name__)
class DesktopBtn(Tkinter.Button):
'''
Represents a Button which can switch to other Desktops
'''
_FONTCOLOR="#FFFFFF"
def getRelativePath(self,folder,name):
import os
dir_path = os.path.dirname(os.path.abspath(__file__))
return os.path.abspath(os.path.join(dir_path, '..', folder, name))
def __init__(self, parent,desktopManager,buttonName, **options):
'''
:param buttonName: Name of the button
'''
Tkinter.Button.__init__(self, parent, **options)
logger.info("init desktop button")
self._imagePath=self.getRelativePath('res','button.gif')
self._BtnPresspath = self.getRelativePath('res','buttonP.gif')
self._BtnPressImage = Tkinter.PhotoImage(file=self._BtnPresspath)
self._image = Tkinter.PhotoImage(file=self._imagePath)
self.bind('<ButtonPress-1>',self._on_pressed)
self.bind('<ButtonRelease-1>',self._on_release)
self._parent = parent
self._btnName = buttonName
self._desktopManager = desktopManager
self.config(width=70, height=65,borderwidth=0,compound=Tkinter.CENTER,font=("Arial", 9,"bold"),foreground=self._FONTCOLOR, text=buttonName,wraplength=64,image=self._image, command=self._onClickSwitch)
def _on_pressed(self,event):
self.config(relief="flat")
self.config(image=self._BtnPressImage)
def _on_release(self,event):
self.config(image=self._image)
def _onClickSwitch(self):
self.config(relief="flat")
logger.info("Buttonclickmethod onClickSwitch")
self._desktopManager.switchDesktop(self._btnName)
def getButtonName(self):
return self._btnName
You can disable the animation of a button by returning "break" in the widget's bind, which stops the propagation of bound functions.
So you can either alter the function you normally have bound to the button to return "break".
Or you can add another bind, this does however prevent any binds that are made after this one:
tkButton.bind("<Button-1>", lambda _: "break", add=True)
Not sure whether this works with your specialized button, but how the button moves when it's clicked seems to depend on it's relief style. With relief=SUNKEN, the button seems not to move at all when clicked, and with borderwidth=0 it appears to be indistinguishable from a FLAT button.
Minimal example:
root = Tk()
image = PhotoImage(file="icon.gif")
for _ in range(5):
Button(root, image=image, borderwidth=0, relief=SUNKEN).pack()
root.mainloop()
Note that you set and re-set the relief to FLAT multiple times in your code, so you might have to change them all for this to take effect.
I think I found some kind of a solution using relief and border:
closebut = Button(title, text="X", relief=SUNKEN, bd=0, command=close)
closebut.pack(side=RIGHT)
You can observe that I used relief = SUNKEN and then bd = 0 to get a nice FLAT effect on my button!
I want to add a Drag Text Feature in canvas to change the position of text using mouse.
from PIL import Image,ImageFont,ImageDraw,ImageTk
import tkinter as tk
root = tk.Tk()
root.title('Demo')
root.geometry('400x50')
def func_image():
img_window = tk.Toplevel()
img_window.grab_set()
photo = Image.open(r'E:\side_300.png')
wi,hi = photo.size
fonty = ImageFont.truetype('arial.ttf',18)
draw = ImageDraw.Draw(photo)
draw.text((50,50),text=text.get(),fill='red',font=fonty)
new_photo = photo
can_photo = ImageTk.PhotoImage(new_photo)
canvas = tk.Canvas(img_window,height=500,width=500)
canvas.pack(anchor='n')
canvas.create_image(wi/2,hi/2,image=can_photo,anchor='center')
img_window.mainloop()
lbl_text = tk.Label(root,text='Enter Text :')
lbl_text.grid(row=0,column=0)
text = tk.Entry()
text.grid(row=0,column=1)
btn = tk.Button(root,text='Click Me',command=func_image)
btn.grid(row=0,column=2)
root.mainloop()
When you run the code it will firstly open a window with name 'Demo' which contains one entry box and a button.
When you click on a Button 'Click Me' after entering some text into entry box it will go to a function func_image and opens a new window which contain a canvas filled with new_image.
Quick Disclaimer: I don't have a lot of experience with PIL, so i don't know how to remove text that has already been drawn. Maybe you can figure that one out yourself. But apart from that, i know some things about tkinter. My idea would be the following:
Bind a function to the <B1-motion> event (Button 1 is being held down and moved) that will constantly get the position of the mouse inside the window and draw new text at that position, while deleting the previous text.
import...
...
def func_image():
img_window = tk.Toplevel()
...
...
draw = ImageDraw.Draw(photo)
draw.text((50,50),text=text.get(),fill='red',font=fonty)
...
def move_text(event):
# here you would delete your previous text
x = event.x
y = event.y
draw.text((x,y),text=text.get(),fill='red',font=fonty
img_window.bind('<B1-Motion>', move_text)
That being said, i think it would be a better idea to use Canvas.create_text (more on effbot.org) in order to write your text on the image. It's really easy to drag around text on a Canvas, here's a little example:
import tkinter as tk
root = tk.Tk()
def change_position(event):
x = event.x
y = event.y
# 20x20 square around mouse to make sure text only gets targeted if the mouse is near it
if text in c.find_overlapping(str(x-10), str(y-10), str(x+10), str(y+10)):
c.coords(text, x, y) # move text to mouse position
c = tk.Canvas(root)
c.pack(anchor='n')
text = c.create_text('10', '10', text='test', fill='red', font=('arial', 18)) # you can define all kinds of text options here
c.bind("<B1-Motion>", change_position)
root.mainloop()
I'm looking for a way to move multiple images together with the background. Moving the background image works fine, but I can't figure out how to add two images on top (they disappear immediately) and then move together with the background. I guess there is an easy way to do that?
I appreciate any hint!
from tkinter import *
import time
tk = Tk()
w = 1000
h = 800
pos = 0
canvas = Canvas(tk, width=w, height=h)
canvas.pack()
tk.update()
background_image = PhotoImage(file="bg.gif")
background_label = Label(tk, image=background_image)
background_label.place(x=0, y=0)
tk.update()
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.place(x=files[f][0],y=files[f][1])
tk.update()
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-25)
tk.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
move(100)
tk.mainloop()
I'm having difficulty in understanding your code. Why create a canvas and then not use it? You have also littered your code with tk.update(), most of which are unnecessary. But, the described problem is because you create the labels inside a function and the association between label and image gets garbage collected when the function exits. You have to explicitly remember this association:
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.image = image # Lets the label remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
If you are then going to move these labels you might want to keep some kind of reference to them or you won't be able to address them.
Complete example
I changed tk to root because tk is the name usually used as alias for tkinter (eg. import tkinter as tk) which gets confusing.
I'm creating a image_list to hold references to the labels containing images. Later I use the list to loop through the labels and move them.
After I have built the GUI I wait 1000 milliseconds before starting the move function. Also I move the images just 1 pixel at a time to clearer see the action.
from tkinter import *
import time
root = Tk()
root.geometry('800x600') # Setting window size instead of usin canvas to do that
pos = 0
background_image = PhotoImage(file="bg.gif")
background_label = Label(root, image=background_image)
background_label.place(x=0, y=0)
image_list = [] # List for holding references to labels with images
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(root, image=image)
label.image = image # Remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
image_list.append(label) # Append created Label to the list
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-1)
for image in image_list: # Loop over labels in list
image.place(x=image.winfo_x()-1) # Move the label
root.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
root.after(1000, move, 100) # Waits 1 second and then moves images
root.mainloop()
By the way; after is a function much preferred over sleep as sleep suspends the program until its finished, whereas after works by making a call after a time and the program runs meanwhile. But if you are ok with that the program freezes during the move, then why not.
This is my code and the scale bar does not display. Any suggestion?
from tkinter import *
self.mAsk = Scale(root, orient="horizontal", from_=1, to=16, label = "Mines", resolution = 1, sliderlength=25)
root=Tk()
root.mainloop()
You have at least three problems here:
You try to use the global root before defining it, so your program is just going to raise a NameError.
You're assigning something to self.mAsk when you don't have anything named self, so that's also going to raise a NameError. (Do you not understand what classes are, and why self appears in methods of classes in many tkinter examples?)
You're not calling pack, grid, or place to actually place mAsk on the parent window. See the chapters on the three different Geometry Managers in the Tkinter book if you have no idea what this means.
If you fix all three, then it works:
from tkinter import *
root=Tk()
mAsk = Scale(root, orient="horizontal", from_=1, to=16, label = "Mines", resolution = 1, sliderlength=25)
mAsk.pack()
root.mainloop()