How to add transparent buttons on an already existing background in Python? - python

I can't find answer for my question anywhere so i decided to ask here.
I star a Python project and i want to add some Buttons on my app's background.
I try Tkinter but this adds a "sub-window" in my app's window, not a button.
I want transparent buttons because in my background there are already graphical buttons. Each button will do a command.

Since you say your background already has the look of a button, you don't need to add an additional button. Instead, you should determine the coordinates of the bounding rectangle for each 'pseudo-button' and then create event bindings that will perform actions if the user clicks within those bounds.
class Rectangle:
def __init__(self, x1, y1, x2, y2):
# (x1, y1) is the upper left point of the rectangle
# (x2, y2) is the lower right point of the rectangle
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
return
def contains(self, newx, newy):
# Will return true if the point (newx, newy) lies within the rectangle.
# False otherwise.
return (self.x1 <= newx <= self.x2) and (self.y1 <= newy <= self.y2)
try:
import tkinter as tk
except:
import Tkinter as tk
from PIL import ImageTK, Image
def button_click(event):
# The location of the click is stored in event sent by tkinter
# and can be accessed like:
print("You clicked ({}, {})".format(event.x, event.y))
## Now that you're in here, you can determine if the user clicked
## inside one of your buttons. If they did, perform the action that
## you want. For example, I've created two rectangle objects which
## simulate the location of some.
button1_rect = Rectangle( 100, 100, 200, 200 )
button2_rect = Rectangle( 300, 300, 400, 400 )
# Change the rectangles to reflect your own button locations.
if button1_rect.contains(event.x, event.y):
print("You clicked button number 1!")
if button2_rect.contains(event.x, event.y):
print("You clicked button number 2!")
return
root = tk.Tk()
canvas = tk.Canvas(master = root, height = 500, width = 500)
canvas.bind("<Button-1>", button_click)
# The above command means whenever <Button-1> (left mouse button) is clicked
# within your canvas, the button_click function will be called.
canvas.pack()
your_background = ImageTK.PhotoImage(Image.open("info_screen.png")) # <-- Or whatever image you want...
canvas.create_image(20, 20, image = your_background)
# 20, 20 represents the top left corner for where your image will be placed
root.mainloop()
Run that and click around while watching your console
Read more about binding to tkinter events here: https://effbot.org/tkinterbook/tkinter-events-and-bindings.html

Related

Custom tkinter slider [duplicate]

I'm creating a game launcher specially in Python 3.7 tkInter, and I want to make my own styled Scrollbar (in Windows 10 (version 1903)).
I've tried adding a hidden Scrollbar, and hiding works but i can't simulate it:
def scroll(self, i, reqHeight, vbarValue):
print("vbarValue", vbarValue)
value = -i / 1.4
a1 = int(self.canvass.coords(self.scroll2)[1]) == 5
a2 = value > 0
a = not(a1 ^ a2)
b1 = ((self.canvass.coords(self.scroll2)[3] > self.cHeight))
b2 = value < 0
b = not(b1 ^ b2)
print(value, value < 0)
print(a1, 5)
print("====")
print(a1, a2)
print(a)
print("----")
print(b1, b2)
print(b)
print("====\n\n")
print("OK")
x1, y1, x2, y2 = self.canvass.coords(self.scroll2)
_y1, _y2 = vbarValue
print("1:",y1, y2)
print("2:",_y1, _y2)
print("3:",(_y2 - _y1) / 2 - y2)
print("4:",(_y1 + (_y2 - _y1) / 120) * self.cHeight)
print("5:",(_y1 + (_y2 - _y1) / 120) * self.cHeight - (y2 / y1))
print("6:",((_y2 - _y1) / 120) * self.cHeight - y2* -i)
print("7:",(_y1 + (_y2 - _y1) / 120))
value = (_y1 + (_y2 - _y1) / 120) * self.cHeight / (y1 / y2)
print("8:",(y2 / y1))
# value = value - (y1 / y2)
print("Dynamic Canvas Region Height:")
print("DCRH:", self.cHeight)
print("Value: %s", value)
self.canvass.move(self.scroll2, 0, -y2)
self.canvass.move(self.scroll2, 0, value)
print("coords: %s" % self.canvass.coords(self.scroll2))
print("reqHeight: %s" % reqHeight)
Event:
def _bound_to_mousewheel(self, event): # <Enter> Event
self.canv.bind_all("<MouseWheel>", self._on_mousewheel)
def _unbound_to_mousewheel(self, event): # <Leave> Event
self.canv.unbind_all("<MouseWheel>")
def _on_mousewheel(self, event): # <Configure> Event
self.canv.yview_scroll(int(-1 * (event.delta / 120)), "units")
self.scrollCommand(int(-1 * (event.delta / 120)), self.scrollwindow.winfo_reqheight(), self.vbar.get())
def _configure_window(self, event):
# update the scrollbars to match the size of the inner frame
size = (self.scrollwindow.winfo_reqwidth(), self.scrollwindow.winfo_reqheight()+1)
self.canv.config(scrollregion='0 0 %s %s' % size)
# if self.scrollwindow.winfo_reqwidth() != self.canv.winfo_width():
# # update the canvas's width to fit the inner frame
# # self.canv.config(width=self.scrollwindow.winfo_reqwidth())
# if self.scrollwindow.winfo_reqheight() != self.canv.winfo_height():
# # update the canvas's width to fit the inner frame
# # self.canv.config(height=self.scrollwindow.winfo_reqheight())
By the way, self.scrollCommand(...) is the same as scroll on the first code.
I expect to get some x and y output for the canvas.move method.
How do i simulate a Scrollbar in Tkinter Canvas
The scrollbar has a well defined interface. To simulate a scrollbar all you need to do is implement this interface. This is most easily done by creating a class that has the following attributes:
you need to define the set method, which is called whenever the widget being scrolled wants to update the scrollbar
you need to add mouse bindings to call the yview method of the widget being controlled by the scrollbar (or xview if creating a horizontal widget).
If you do those two things, your scrollbar can be used exactly like a built-in scrollbar.
For the rest of this answer, I'm going to assume you want to simulate a vertical scrollbar. Simulating a horizontal scrollbar works identically, but instead of 'top' and 'bottom', you are dealing with 'left' and right'.
Defining the set method
The set method will be called with two fractions. The canonical documentation describes it like this:
This command is invoked by the scrollbar's associated widget to tell the scrollbar about the current view in the widget. The command takes two arguments, each of which is a real fraction between 0 and 1. The fractions describe the range of the document that is visible in the associated widget. For example, if first is 0.2 and last is 0.4, it means that the first part of the document visible in the window is 20% of the way through the document, and the last visible part is 40% of the way through.
Defining the bindings
The other half of the equation is when the user interacts with the scrollbar to scroll another widget. The way this happens is that the scrollbar should call the yview command of the widget to be controlled (eg: canvas, text, listbox, etc).
The first argument you must pass to the command is either the string "moveto" or "scroll".
If "moveto", the second argument is a fraction which represents the amount that has been scrolled off of the top. This is typically called when clicking on the scrollbar, to immediately move the scrollbar to a new position
if "scroll", the second argument is an integer representing an amount, and the third argument is either the string "units" or "pages". The definition of "units" refers to the value of the yscrollincrement option. "pages" represents 9/10ths of the window height. This is typically called when dragging the mouse over the scrollbar.
The options are spelled out in the man pages for each scrollable widget.
Example
The following is an example of a uses a text widget, so that you can see that when you type, the scrollbar properly grows and shrinks. If you click anywhere in the scrollbar, it will scroll to that point in the documentation.
To keep the example short, this code doesn't handle dragging the scrollbar, and it hard-codes a lot of values that probably ought to be configurable. The point is to show that all you need to do to simulate a scrollbar is create a class that has a set method and which calls the yview or xview method of the connected widget.
First, the scrollbar class
import tkinter as tk
class CustomScrollbar(tk.Canvas):
def __init__(self, parent, **kwargs):
self.command = kwargs.pop("command", None)
tk.Canvas.__init__(self, parent, **kwargs)
# coordinates are irrelevant; they will be recomputed
# in the 'set' method
self.create_rectangle(0,0,1,1, fill="red", tags=("thumb",))
self.bind("<ButtonPress-1>", self.on_click)
def set(self, first, last):
first = float(first)
last = float(last)
height = self.winfo_height()
x0 = 2
x1 = self.winfo_width()-2
y0 = max(int(height * first), 0)
y1 = min(int(height * last), height)
self.coords("thumb", x0, y0, x1, y1)
def on_click(self, event):
y = event.y / self.winfo_height()
self.command("moveto", y)
Using the class in a program
You would use this class exactly like you would a native scrollbar: instantiate it, and set the command to be the yview command of a scrollable widget.
This example uses a text widget so you can see the scrollbar updating as you type, but the exact same code would work with a Canvas, or any other scrollable window.
root = tk.Tk()
text = tk.Text(root)
sb = CustomScrollbar(root, width=20, command=text.yview)
text.configure(yscrollcommand=sb.set)
sb.pack(side="right", fill="y")
text.pack(side="left", fill="both", expand=True)
with open(__file__, "r") as f:
text.insert("end", f.read())
root.mainloop()

Tkinter: mini canvas windows as a slider

I am new to Tkinter. I want to control the movement of the image with a cursor and a small window:
enter image description here
I tried with this code, but the result is not really what I'm looking for.
import tkinter as tk
from io import BytesIO
import requests
from PIL import Image , ImageTk
def full_dimensions(imag_fs):
top = tk.Toplevel(root)
img = tk.Label(top, image=imag_fs)
img.pack()
def get_image():
_url = 'https://i.imgur.com/4m7AHVu.gif'
_img = requests.get(_url)
if _img.status_code == 200:
_content = BytesIO(_img.content)
else:
_content = 'error.gif'
print('image loaded')
return _content
root = tk.Tk()
_content = get_image()
_x = Image.open(_content)
imag_fs = ImageTk.PhotoImage(_x)
_x.thumbnail((100, 100), Image.ANTIALIAS)
imag = ImageTk.PhotoImage(_x)
img = tk.Button(root, image=imag, command=lambda: full_dimensions(imag_fs))
img.grid(column=3, row=1)
root.mainloop()
I tested a window, but when I import an image I can't control
import tkinter as tk
main_window = tk.Tk()
def check_hand_enter():
canvas.config(cursor="hand1")
def check_hand_leave():
canvas.config(cursor="")
canvas = tk.Canvas(width=200, height=200)
tag_name = "polygon"
canvas.create_polygon((25, 25), (25, 100), (125, 100), (125, 25), outline='black', fill="", tag=tag_name)
canvas.tag_bind(tag_name, "<Enter>", lambda event: check_hand_enter())
canvas.tag_bind(tag_name, "<Leave>", lambda event: check_hand_leave())
canvas.pack()
main_window.mainloop()
So here is how I would do this (a simple example):
# import all necessary modules and classes
from tkinter import Tk, Canvas, Frame
from PIL import Image, ImageTk
import requests
# checking if the file exists, if it doesn't exist download it, if can't download it, exit the program
try:
open('space.jpg')
except FileNotFoundError:
url = 'https://images5.alphacoders.com/866/866360.jpg'
img = requests.get(url)
if img.status_code == 200:
with open('space.jpg', 'wb') as file:
file.write(img.content)
print('File not found. Downloaded the necessary file.')
else:
print('File not found. Could not download the necessary file.')
exit()
class MovableImage(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
# dictionary for storing information about movement
self.start_coords = {'x': 0, 'y': 0, 'move': False}
# dictionary for storing information about movement
self.start_coords_main = {'x': 0, 'y': 0, 'move': False}
# loads the image
self.image = Image.open('space.jpg')
# sets the images to their corresponding variables so that they can be referenced later
# resizes the smaller image to fit the navigation window
self.main_image = ImageTk.PhotoImage(self.image)
self.nav_image = ImageTk.PhotoImage(self.image.resize((200, 100), Image.ANTIALIAS))
# creates the canvas to store the bigger image on
self.main_canvas = Canvas(self, width=700, height=500, highlightthickness=0)
self.main_canvas.pack()
# puts image on canvas
self.main_image_id = self.main_canvas.create_image((0, 0), image=self.main_image, anchor='nw', tags='main_image')
# creates the smaller canvas that will be used for navigation
self.nav_canvas = Canvas(self.main_canvas, width=200, height=100, highlightthickness=0)
# adds the smaller canvas as a window to the main_canvas
self.main_canvas.create_window((500, 400), window=self.nav_canvas, anchor='nw', tags='nav_canvas')
# adds the resized image to nav_canvas
self.nav_canvas.create_image((0, 0), image=self.nav_image, anchor='nw')
# creates a rectangle to indicate the current view of the image
self.nav_box = self.nav_canvas.create_rectangle((0, 0, 70, 50), outline='white')
# binds functions
self.main_canvas.bind('<Button-1>', self.set_start_coords_main)
self.main_canvas.bind('<B1-Motion>', self.move_coords_main)
# binds functions
self.nav_canvas.bind('<Button-1>', self.set_start_coords)
self.nav_canvas.bind('<B1-Motion>', self.move_coords)
# function that sets the starting coords so that they can be referenced later, also sets whether the box can be moved at all
def set_start_coords(self, event):
x1, y1, x2, y2 = self.nav_canvas.coords(self.nav_box)
if x1 < event.x < x2 and y1 < event.y < y2:
self.start_coords['x'] = event.x - x1
self.start_coords['y'] = event.y - y1
self.start_coords['move'] = True
else:
self.start_coords['move'] = False
# the moving part, this takes reference from the starting coords and uses them for calculation
# basic border checks and then the actual moving
def move_coords(self, event):
if not self.start_coords['move']:
return
dx = self.start_coords['x']
dy = self.start_coords['y']
x = event.x - dx
y = event.y - dy
if x < 0:
x = 0
elif x + 70 > 200:
x = 130
if y < 0:
y = 0
elif y + 50 > 100:
y = 50
self.nav_canvas.coords(self.nav_box, x, y, x + 70, y + 50)
self.main_canvas.coords(self.main_image_id, -x * 10, -y * 10)
# function that sets the starting coords so that they can be referenced later, also sets whether the box can be moved at all
def set_start_coords_main(self, event):
x1, y1, x2, y2 = self.main_canvas.bbox('main_image')
if x1 < event.x < x2 and y1 < event.y < y2:
self.start_coords_main['x'] = event.x - x1
self.start_coords_main['y'] = event.y - y1
self.start_coords_main['move'] = True
else:
self.start_coords_main['move'] = False
# the moving part, this takes reference from the starting coords and uses them for calculation
# basic border checks and then the actual moving
def move_coords_main(self, event):
if not self.start_coords_main['move']:
return
dx = self.start_coords_main['x']
dy = self.start_coords_main['y']
x = event.x - dx
y = event.y - dy
if x < -1300:
x = -1300
elif x > 0:
x = 0
if y < -500:
y = -500
elif y > 0:
y = 0
self.nav_canvas.coords(self.nav_box, -x / 10, -y / 10, -x / 10 + 70, -y / 10 + 50)
self.main_canvas.coords(self.main_image_id, x, y)
# basic Tk() instance and afterwards the root.mainloop()
root = Tk()
MovableImage(root).pack()
# mainloop
root.mainloop()
Few things to mention: this is a pretty hardcoded sample that pretty much works well with only images whose resolution is 2000x1000 pixels, other images may not be shown correctly or resizing won't look as good. With this issue You will have to deal Yourself or ask another question about what issues You encountered while trying to make this adjustable. So it would be great if any image could be put up there and it would work.
About the code, it is pretty simple:
Import modules
check for file (there could be a better way but this works too), then if the file does not exist just download it, if that can't be done, well just exit the program.
Then setting up some references (a dictionary so that global doesn't have to be used and also I would say that a dictionary is better for other reasons too like all the necessary variables are in one place and for example x and y variable names are not taken globally).
Then setting the first function which will register the mouse click. This is done to get the position of mouse relative to the moveable square (basically where is the mouse on the square if the square was an independent window or sth) and it is made possible to move, but before that the function checks if the mouse is inside the square at all. If not moving is disabled so that for example if You were to click outside of the square and start moving it does not do that.
Then the moving function is defined. There the relative x and y coords are retrieved but first it checks if it can move and if it cannot, it stops the execution of that function. Then some more calculations and checking for border (again this is pretty hardcoded and should be changed to a more dynamic function so that it can detect this based on picture). Then comes the moving part which just move both the navigation box and the actual picture to their according spots (this is somewhat hardcoded but basically if You were to keep the scale to 1/10 of the smaller box, then this specific part would work with different images).
Then comes the basic Tk() initiation and later the .mainloop().
In the middle just open the image and set it to 2 variables and resize the one that will go to the navigation box.
Then create the main canvas where the main image will be shown.
Then add that image to canvas and keep the id reference so that the image can be later moved.
Create the smaller canvas and don't pack it or anything but add the instance as a window to the main canvas so that it is on top of it (as in the photo in Your question). Again it is placed in a hardcoded manner.
Then add the navigation image to the navigation canvas and add the small box that will be moved too.
Then bind functions to mouse activity in nav_canvas.
If You have questions ask. Note that the try/except is not necessary if You are using Your own images and it will always come with the program. It is there because You may not have this exact picture with those exact dimensions with that exact name so it is kinda temporary for testing purposes.
EDIT: put code in a class that inherits from Frame so that this can be placed as a widget.

How to fix a widget in a grid row and column while the user is dragging it?

I want a widget which is been dragged by the user to be fixed in a specific grid cell when the widget comes near it, is there any way to do it in tkinter? I managed to make the widget draggable by taking the mouse position of user as he drags the widget, but i dont know how to make it fix into a grid cell.
this is what i use currently to move widgets
def make_draggable(self):
def drag_start(event):
widget = self
widget = event.widget
self._drag_start_x = event.x
self._drag_start_y = event.y
def drag_motion(event):
widget = self
widget = event.widget
x = self.winfo_x() - self._drag_start_x + event.x
y = self.winfo_y() - self._drag_start_y + event.y
self.place(x=x, y=y)
self.bind("<Button-1>", drag_start)
self.bind("<B1-Motion>", drag_motion)
for example, if the grid cell left top corner position is (x=10,y=10) I need the button to get fixed in the cell when its position is near that value (like 9,9 8,8 9,5 etc)
Based on what I have understood from your question, I have tried the following
from tkinter import *
class Drag:
def __init__(self,widget):
self.widget=widget
self.widget.bind("<Button-1>", self.drag_start)
self.widget.bind("<B1-Motion>", self.drag_motion)
self.grid=(100,100)
self.margin=(20,20)
def drag_start(self,event):
self._drag_start_x = event.x
self._drag_start_y = event.y
def drag_motion(self,event):
x = self.widget.winfo_x() - self._drag_start_x + event.x
y = self.widget.winfo_y() - self._drag_start_y + event.y
if (
x%self.grid[0] in range(self.margin[0]) and
y%self.grid[1] in range(self.margin[1])
):
self.widget.place(x=x-x%self.grid[0],y=y-y%self.grid[1])
else:
self.widget.place(x=x, y=y)
root=Tk()
root.minsize(300,200)
label=Label(root,text='Drag Me',bg='yellow')
label.pack()
Drag(label)
root.mainloop()
This basically creates grid of "cells" of height and width 100 and it clips it to the cell if it is in 20 pixel range of the top left corner of the cell. This is basically done by taking the remainders of the current coordinates when divided by the cell's height/width using the modulo % operator and comparing if this lies in the desired range.

Roll up image implementation using canvas - Python

I want an image to scroll up inside a canvas. But I am unable to do it.
import tkinter
def scroll_image(event):
global roll
roll = roll - 10
canvas1.itemconfig(canvas1.create_image(20, roll, image=i))
roll = 10
windows = tkinter.Tk()
windows.title("My Application")
# Adding canvas to show image there
canvas1 = tkinter.Canvas(windows, width=200, height=100)
i = tkinter.PhotoImage(file="Capture.gif")
canvas1.create_image(20, 20, image=i)
# trying to implement roll-up image
canvas1.itemconfig(canvas1.create_image(20, 20, image=i))
canvas1.bind("<Configure>", scroll_image)
canvas1.grid(column=0, row=2)
windows.mainloop()
I tried using loops but I noticed that loops are running as expected but unfortunately the canvas update is taking place only once. So I removed the loop. But I need to find a way out to implement a roll up image.
Image is moving up and down.
This example use
module PIL/pillow to load jpg/png instead of gif
after(time_in_millisecond, function_name) to repeat function which moves image
img_id to use only one image (instead of creating many images with create_image)
canvas.move(ID, offset_x, offset_y) to move image (or other object)
canvas.coords(ID) to get current positon of image (or other object)
canvas.pack(fill='both', expand=True) to use full window. Canvas will use full window even when you resize window.
Code:
import tkinter as tk
from PIL import Image, ImageTk
def scroll_image():
global offset_y # inform function that you want to assign value to external variable instead of local one.
# move image
canvas.move(img_id, offset_x, offset_y)
# get current position
x, y = canvas.coords(img_id)
print(x, y)
# set position (if you don't use canvas.move)
#canvas.coords(img_id, x+offset_x, y+offset_y)
# x += offset_x
# y += offset_y
# change direction
if y <= -100 or y >= 0:
offset_y = -offset_y
# repeat after 20ms
root.after(20, scroll_image)
offset_x = 0
offset_y = -3
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.pack(fill='both', expand=True) # use full window
#photo = tk.PhotoImage(file="Capture.gif")
image = Image.open("image.jpg")
photo = ImageTk.PhotoImage(image)
img_id = canvas.create_image(0, 0, image=photo)
# start scrolling
scroll_image()
root.mainloop()
You can use also canvas.coords(ID, x, y) to set new position.
More examples on GitHub for: Tkinter and other Python's modules

Closing an Image After User Clicks (Python and Pillow)

I am working on a Choose-Your-Own-Adventure game in Python. Every so often, I want the user to be given a scene, and be allowed to find objects in that scene. Whatever they find can be put in their inventory, and whenever they are ready to stop looking for items, they can hit an "Exit" button in the bottom left. However, I am running into some errors with actually getting the button click coordinates to actually do something.
So far, I have this:
root = Tk()
imageIQ1 = Canvas(root, width=1000, height=1000)
imageIQ1.pack()
original = Image.open("prison.jpg")
original = original.resize((1000,1000)) #resize image
img = ImageTk.PhotoImage(original)
imageIQ1.create_image(0, 0, image=img, anchor="nw")
def getcoors(eventcoors):
global x0,y0
x0 = evencoors.x
y0 = evencoors.y
print(x0,y0)
After the user clicks a certain set of coordinates (or somewhere near them), I want the program to clear the picture from the screen and continue with the program. For insance, clicking anywhere from (300, 400) to (500, 500) would close the picture and continue with the rest of the program. I know this will use some form of loop like
while (x not in range) and (y not in range):
But I am unsure of what I would actually do to clear the image. I read about using something like .kill() and .terminate(), but they don't work in this situation.
Any ideas?
You need to have a reference for the image in order to later be able to delete it as in:
canvImg = imageIQ1.create_image(0, 0, image=img, anchor="nw")
and then when you call:
imageIQ1.delete(canvImg)
it will get deleted.
Based on this you can put that in an event method like:
def motion(event):
x, y = event.x, event.y
someSpecificX = 142
someSpecificY = 53
marginX = 100
marginY = 100
print(x, y)
if x in range(someSpecificX - marginX, someSpecificX + marginX):
if y in range(someSpecificY - marginY, someSpecificY + marginY):
imageIQ1.delete(canvImg)
imageIQ1.bind('<Button-1>', motion)
Your final code should look like:
from tkinter import *
from PIL import ImageTk, Image
root = Tk()
imageIQ1 = Canvas(root, width=1000, height=1000)
imageIQ1.pack()
original = Image.open("prison.jpg")
original = original.resize((1000,1000)) #resize image
img = ImageTk.PhotoImage(original)
canvImg = imageIQ1.create_image(0, 0, image=img, anchor="nw")
def motion(event):
x, y = event.x, event.y
someSpecificX = 142
someSpecificY = 53
marginX = 100
marginY = 100
print(x, y)
if x in range(someSpecificX - marginX, someSpecificX + marginX):
if y in range(someSpecificY - marginY, someSpecificY + marginY):
imageIQ1.delete(canvImg)
imageIQ1.bind('<Button-1>', motion)
root.mainloop()

Categories

Resources