tkinter after function doesn't work for me - python

so i'm very new to coding, i took a tkinter draw program off the internet and wanted to add a change colour function by opening up a new window, having the user input the hex code, then having the window close. However the window isn't even opening up when i add the after function, and just waits 5 seconds showing the button being depressed.
from tkinter import *
root = Tk()
# Create Title
root.title( "Paint App ")
# specify size
root.geometry("500x350")
#set colour
Colour = "#000000"
def changecolour():
col=Tk()
Label(col, text= "Enter the new hex code, you have 5 seconds ").grid(column=1,row=0)
newcol=Entry(col,width=15)
newcol.grid(column=1,row=1)
col.after(5000)
Colour=newcol.get()
col.destroy()
# define function when
# mouse double click is enabled
def paint( event ):
# Co-ordinates.
x1, y1, x2, y2 = ( event.x - 5 ),( event.y - 5 ), ( event.x + 5 ),( event.y + 5 )
# specify type of display
w.create_oval( x1, y1, x2,y2, fill = Colour )
# create canvas widget.
w = Canvas(root, width = 400, height = 250)
# call function when double
# click is enabled.
w.bind( "<B1-Motion>", paint )
# create label.
l = Label( root, text = "Click and Drag slowly to draw." )
l.pack()
w.pack()
btn = Button(root, text = 'Change colour', bd = '5', command = changecolour)
btn.place(x= 150,y=300)
mainloop()

Use the built-in colorchooser;
You can try it by running the following command:
python -m tkinter.colorchooser
Use it in your program like this:
from tkinter import colorchooser
def changecolour():
global Colour
_, Colour = colorchooser.askcolor(title ="Choose color")
The askcolor function returns two values;
An RGB tuple
A hex string starting with a #.
Since you only need the latter, I used _, Colour.
Also, seeing as you want to change the global variable Colour, you have to mark it as such in changecolour.

Related

How do I create a function that will create and place a button, with tkinter, based in input values in the argument?

I'm trying make a python function that will take in arguments, and create a button from tkinter.
Some background, this is for a larger project with a lot of buttons being used, and the purpose of this is to increase efficiency when adding new button. I also wanted a central place where I can change a small variable to switch around every button at once instead of having to adjust every single button command.
So pieces that I am using in the larger project to create the tkinter window are being used, which includes some add ball bits and pieces.
That main way I have chosen to place buttons location is based on x, y points, I know there are easier ways, however this is just have I have chosen to do it.
The current problem is what then I run the code, the button is created, however the button is not responsive and the function the button would run when pressed, runs(which shouldn't be happening). As in the command doesn't run, but the button shows its being pressed when clicked on the tkinter window.
from tkinter import *
root = Tk()
winWidth = 500
winHeight = 500
HEIGHT = 30
WIDTH = 100
SIZE = 15
PHOTO = PhotoImage(
file=r"C:\\Users\\Paint\\VS Code\\1x1 pixel.png", width=1, height=1)
screeenWidth = root.winfo_screenwidth()
screenHeight = root.winfo_screenheight()
cenX = int(screeenWidth/2 - winWidth/2)
cenY = int(screenHeight/2 - winHeight/2)
root.geometry(f'{winWidth}x{winHeight}+{cenX}+{cenY}')
root.title('Button Testing')
def Create_Button(BUTTON : str, Text : str, Command, Width : int, Height : int, x : int, y: int):
BUTTON = Button(
root,
text = Text,
command = Command,
width = WIDTH + Width,
height = HEIGHT + Height,
font = 'BOLD, 15',
compound = 'center',
image = PHOTO
)
BUTTON.place(
x=(winWidth/2 - (BUTTON['width'])/2)+int(x),
y=(winHeight/2 - (BUTTON['height'])/2-int(y)))
def Hi():
print('hellow')
Create_Button('TheBUtton','Test', Hi() , 0, 0, 0, 0)
root.mainloop()
I thought the issue might be from the button function being full of variables, so that when it is pressed on the tkinter window, it can't recall what it needs to do. However I have no clue if this is how or why this is present. (let me know if this is the case, and if so how I would work around it or if I should try another thing)
All my different attempts ended with the same result as when I run the script, the button creation function runs, creating and placing a button, but also running the function what would run when the button is pushed (which shouldn't be happening normally), and then getting an unresponsive button on the tkinter window.
My expectation is for the Create_Button function to create and place a working button based on the arguments in the Create_Button function.
If there are other suggestions on how to reach the same result in a different method, I would love to hear it.
from tkinter import *
root = Tk()
winWidth = 500
winHeight = 500
HEIGHT = 30
WIDTH = 100
SIZE = 15
screeenWidth = root.winfo_screenwidth()
screenHeight = root.winfo_screenheight()
cenX = int(screeenWidth/2 - winWidth/2)
cenY = int(screenHeight/2 - winHeight/2)
root.geometry(f'{winWidth}x{winHeight}+{cenX}+{cenY}')
root.title('Button Testing')
def Create_Button(BUTTON : str, Text : str, Command, Width : int, Height : int, x : int, y: int):
BUTTON = Button(
root,
text = Text,
command = Command,
font = 'BOLD, 15',
compound = 'center'
)
BUTTON.place(
x=(winWidth/2 - (BUTTON['width'])/2)+int(x),
y=(winHeight/2 - (BUTTON['height'])/2-int(y)),
width = WIDTH + Width,
height = HEIGHT + Height)
return BUTTON
def Hi():
print('hellow')
btn_1 = Create_Button('TheBUtton','Test', Hi() , 10, 10, 10, 10)
btn_2 = Create_Button('TheBOOOOOUtton','not a test?', Hi() , 10, 10, 10, 100)
btn_1["text"] = "new text"
root.mainloop()
i edited your code so it works now
okay first in your code i would recommend you put width and height in the .place since when you put it in the button it accounts not for the pixels but for the len and rows of the text
ex
'''this
here'''
it's width is 4 and height is 2 (4 alapbets across and 2 rows)
and also your program im not understanding alot like the pixels of the root and
Espicially your code isnt working cause the button is being huge since your adding 500 and it is inside the button not the .place
since width = WIDTH + Width, WIDTH=100 Width=0 that makes it 500 alaphlabets
and it is 30 for the height(rows)
i also added a return func which makes u able to edit it in the future
and maybe doing anchor=tk.CENTER in the .place will make it better imo but i didnt add that in the code

Is there a Python tkinter function that makes a drawing’s coords on a certain ratio on the canvas widget?

I’m a first timer at tkinter(python) and what I want to do is to make a line of text stay on the same coords ratio on the canvas. For example, I want a line of text to stay in the middle. Is there any tkinter text parameters that make it stay in a certain ratio without running a while loop? I want minimal time complexity.
Your GUI can have a function bound to the Canvas <Configure> event that fires whenever the Canvas changes size. A simple example below.
There is also a Canvas.scale method which will change the location and size of canvas objects. Text may move but will not change size.
import tkinter as tk
root = tk.Tk()
# Create a canvas that will change size with the root window.
canvas = tk.Canvas( root, width = 600, height = 400, borderwidth = 2,
relief = tk.SOLID )
canvas.grid( sticky = 'nsew', padx = 5, pady = 5 )
root.grid_columnconfigure( 0, weight = 1 )
root.grid_rowconfigure( 0, weight = 1 )
text = canvas.create_text( 50, 50, text = 'Test Text' )
def on_canvas_config( event ):
""" This is bound to the canvas <Configure> event.
It executes when the canvas changes size.
The event is an object that contains the new width and height.
"""
x = max( 25, event.width // 2 ) # x >= 25
y = max( 12, event.height // 8 ) # y >= 12
canvas.coords( text, x, y ) # Set the canvas coordinates
canvas.bind( '<Configure>', on_canvas_config )
root.mainloop()
The on_canvas_configure function can be written to amend the coords of any objects in the Canvas to meet your requirements. I've never tried to use the Canvas.scale method but this may be worth exploring if there are many objects to reposition on the canvas.

Adding Windows to Buttons on Tkinter GUI

I'm new to Python, I'm trying to add widgets in an window which can be used when we click on an button when in Tkinter GUI.
I'm unable to add an window into the GUI button and I'm doubtful about the Code which can be Implemented as well. I hope I could get some inputs on this.
I'm running on IDLE 3.6.3.I would be grateful if someone could point out the additions that could be made and the changes in the current code.
ConnectLogo=PhotoImage(file="Connect.png")
Connect = Button(win,image=ConnectLogo,text = "Connect", font = myFont,height =100 , width = 100,compound=TOP,bg = "orange")
Connect.grid(row=3,column=1,padx=50,pady=40)
FrequencyLogo=PhotoImage(file="Frequency.png")
Frequency = Button(win,image=FrequencyLogo, text = "Frequency", font = myFont, height = 100, width =180,compound=TOP,bg = "Yellow")
Frequency.grid(row=3,column=2,padx=10)
MaskLogo=PhotoImage(file="Mask.gif")
Mask = Button(win,image=MaskLogo, text = "Mask", font = myFont, height = 100, width =180,compound=TOP,bg = "yellow")
Mask.grid(row=6,column=2,padx=10)
You can make a function, which will implement TopLevel.
This creates a new window into which you can add widgets, add them inside the function.Inside the function you root becomes window
from tkinter import *
root = Tk()
def new_window():
window = TopLevel(root)
...widgets like label, entry etc
label = Label(window,....)
btn = Button(...., command = new_window)
btn.pack()...(anything)

Tkinter -- how to horizontally center canvas text?

I'm working on a function in a UI class that is a config window, it displays the logo of the program and has an updating text at the bottom telling you what it's loading, etc. This is what I have so far:
self.window = "config"
self.windowWidth = 340
self.windowHeight = 270
infoText = "Configuring Kh..."
self.root = tk.Tk()
self.root.geometry("%dx%d+400+400" % (self.windowWidth, self.windowHeight))
self.root.title("Kh Control v1.1 starting...")
logo = tk.PhotoImage(file="KhLogo.gif")
mainPanel = tk.Canvas(self.root, width=self.windowWidth, height=self.windowHeight)
mainPanel.image = logo
mainPanel.pack()
mainPanel.create_image(0, 0, image=logo, anchor="nw")
mainPanel.create_text(0,200, text=infoText, anchor="nw", fill="yellow")
return
I'd like the text in infoText to be centered horizontally and offset vertically about 200px down. The vertical offset works fine, but I can't figure out how to center the text horizontally.
I started by trying the age old ((width / 2) - (str length / 2)) but then realized that each letter isn't 1px. And anchor = "center" seems to only put half the text off the left side of the screen.
I'm very new to Python (only a few days now) so if I'm missing something obvious, that's why.
EDIT: and in case it wasn't obvious, this text will change so I can't just make an absolute decision on the offsets, it has to change with the text
I figured it out after scrounging through the canvas reference.
There is a method for a canvas called bbox that returns a tuple containing (x1, y1, x2, y2) of the area an item takes up. I got those coords, drew up a function to find the px length of it, divided it by 2 and subtracted it from the window width. Then I used canvas.move to change the x offset using the number the function returned.
def findXCenter(self, canvas, item):
coords = canvas.bbox(item)
xOffset = (self.windowWidth / 2) - ((coords[2] - coords[0]) / 2)
return xOffset
The main part is here:
textID = mainPanel.create_text(0,0, text=infoText, anchor="nw", fill="yellow")
xOffset = self.findXCenter(mainPanel, textID)
mainPanel.move(textID, xOffset, 0)
Hopefully my hours of searching for this answer will help someone later on.
Have you tried using the justify parameter?
Often center is used with non-text objects.
https://stackoverflow.com/a/15016161/3900967 might provide some insight.
You can set the position of the text using the first part of the .create_text() method.
see http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/create_text.html
To make this position horizontialy center to the window use self.windowWidth / 2 as the x cordinate
By default the text is anchored to center around the given position.
(.create_text will default to anchor="CENTER")
You will also want to remove the anchor="nw" as this will make your text display down and to the right of the position given
As such your updated code should be.
self.window = "config"
self.windowWidth = 340
self.windowHeight = 270
infoText = "Configuring Kh..."
self.root = tk.Tk()
self.root.geometry("%dx%d+400+400" % (self.windowWidth, self.windowHeight))
self.root.title("Kh Control v1.1 starting...")
logo = tk.PhotoImage(file="KhLogo.gif")
mainPanel = tk.Canvas(self.root, width=self.windowWidth, height=self.windowHeight)
mainPanel.image = logo
mainPanel.pack()
mainPanel.create_image(0, 0, image=logo, anchor="nw")
mainPanel.create_text(self.windowWidth/2,200, text=infoText, fill="yellow")
return
You can use the anchor="center" method to align your text that created inside the canvas using canvas.create_text method
try following example code:
from tkinter import *
root = Tk()
canvas = Canvas(root, width=400, height=300)
canvas.pack()
text = canvas.create_text(200, 150, text="Hello World!", anchor="center")
root.mainloop()

A differen "Drag and Drop" images in Tkinter (Python 3.x)

I'm trying to make it possible for my program's user to drag an image from a widget and drop it in another without removing the image from its first position.
I thought on using a TopLevel for this. Upon a click+drag event, a TopLevel, containing the same image, would pop up right above the clicked image on the event.x and event.y position. Then it would change its position along with the mouse and only upon ButtonRelease-1 the TopLevel would be destroyed.
If the Button Release event was triggered on the master x and y coordinates corresponding to where is my other widget (in the case, a canvas), then it would trigger a canvas.create_image() using the event.x and event.y.
The problems I'm facing are:
Only 1 TopLevel should appear at once, but I had no success limiting it. Several windows overlap as I click and drag the mouse.
I can't make Toplevel.geometry use the bound event's x and y positions.
How to make the Toplevel display the same image as the user clicked, without magic numbers and variables?
Here's my code so far:
class animalImgList():
def __init__(self, a):
#Constructor
initX = 75
initY = 40
animalList = ['pig2.jpg', 'pig3.jpg', 'pig4.jpg']
for a in animalList:
vars(self)[a+'img'] = PIL.Image.open(a)
vars(self)[a+'tkimg'] = PIL.ImageTk.PhotoImage(vars(self)[a+'img'])
vars(self)[a+'labelName'] = Label(anmlCanvas, image=vars(self)[a+'tkimg'])
vars(self)[a+'canvasImg'] = anmlCanvas.create_image(initX, initY,image=(vars(self)[a+'tkimg']))
initY = initY + 70
anmlImgList = []
anmlImgList.append(vars(self)[a+'canvasImg'])
imgTags = anmlCanvas.addtag_all("img")
anmlCanvas.tag_bind("img", "<Button-1>", self.createImg)
def createImg(self, event):
newImg = Toplevel(root)
newImg.geometry("50x40"+"+"+ x+"+"+y)
newImgMsg = Message(newImg, text="This is supposed to be an image")
newImgMsg.pack()
newImg.update_idletasks()
newImg.overrideredirect(True)
createImgOpen = True
if createImgOpen == True:
pass
To drag an image between two canvases without removing the image from the first canvas.
The idea is the following:
When the user clicks on the canvas can1 (click1 function):
get the item the user clicked on with can1.find_closest
get its image with can1.itemcget
create a toplevel containing the image
bind mouse motion to drag along the toplevel: for that you need to use the event.x_root and event.y_root to change the toplevel's geometry.
When the user releases the left mouse button (release function):
unbind mouse motion
if the toplevel is inside the canvas can2, create the image in can2 at the mouse position
destroy the toplevel
This way, there can be only one toplevel because each time the button is released, the toplevel is destroyed.
Here is the code:
import tkinter as tk
class DragToplevel(tk.Toplevel):
def __init__(self, master, image, x, y):
tk.Toplevel.__init__(self, master)
self.overrideredirect(True)
self.geometry('+%i+%i' % (x, y))
self.image = image
self.label = tk.Label(self, image=image, bg='red')
self.label.pack()
def move(self, x, y):
self.geometry('+%i+%i' % (x, y))
root = tk.Tk()
can1 = tk.Canvas(root, width=300, height=300, bg='white')
can2 = tk.Canvas(root, width=300, height=300, bg='white')
can1.pack(side='left')
can2.pack(side='right')
root.geometry('800x800')
im = tk.PhotoImage('tux', master=root, file='/home/juliette/Images/tux_mini.png')
drag_id = ''
dragged = None
can1.create_image(100, 200, image=im)
def click1(event):
global drag_id, dragged
items = can1.find_closest(event.x, event.y)
if items:
image = can1.itemcget(items[0], 'image')
dragged = DragToplevel(root, image, event.x_root, event.y_root)
drag_id = root.bind('<Motion>', lambda e: dragged.move(e.x_root, e.y_root))
def release(event):
global drag_id, dragged
root.unbind('<Motion>', drag_id)
drag_id = ""
xr, yr = event.x_root, event.y_root
x2, y2 = can2.winfo_rootx(), can2.winfo_rooty()
w2, h2 = can2.winfo_width(), can2.winfo_height()
if dragged and xr >= x2 and xr < x2 + w2 and yr >= y2 and yr < y2 + h2:
can2.create_image(xr - x2, yr - y2, image=dragged.image, anchor='nw')
if dragged:
dragged.destroy()
dragged = None
can1.bind('<ButtonPress-1>', click1)
root.bind('<ButtonRelease-1>', release)
root.mainloop()

Categories