Only Button-1 Works in Event Bind Tkinter - python

from Tkinter import *
root = Tk()
canvas = Canvas(root, width = 300, height = 300)
canvas.pack()
one = canvas.create_rectangle(100, 100, 500, 500, fill = 'red')
two = canvas.create_rectangle(200, 200, 500, 500, fill = 'green')
def move_rectangle(canvas, one):
canvas.move(one, 2, 3)
canvas.move(two,4,5)
def callback(event):
move_rectangle(canvas, one)
move_rectangle(canvas, two)
canvas.bind("<Button-1>", callback)
canvas.pack(expand = YES, fill = BOTH)
mainloop()
If I change Button-1 to anything else, nothing happens. Right now two different rectangles will move across the screen. I am trying to set up a bind for rectangle one and a different key bind for rectangle two. However I can't even seem to bind to anything other than Button-1. If I change Button-1 to Button-2 nothing happens, I have also tried Return and arrow keys with no luck.

If you change <Button-1> to <Button-2> in your code, the right mouse button should just work. There may be some weird platform-/version-specific oddities, but without knowing anything about your system, I can't comment.
But changing it to, e.g., <Left> is not going to work. Only the window with keyboard focus gets keyboard events. And since you don't have any text-entry-type auto-focusing widgets, and aren't doing anything to explicitly set focus, that's your root.
So, there are two fixes. Either one will work, and in your particular case (where a Canvas owns the entire root and there are no other widgets anywhere), I don't think there's going to be much difference between them.
Call canvas.focus_set right before mainloop.
Call root.bind instead of canvas.bind.
One more thing to watch out for: On many *nix systems, when you run a Tkinter script from the terminal, it doesn't actually jump to the foreground. So, it won't receive keyboard input until you click somewhere on the window.

Related

how to fix titlebar behaving as transparent when using '-transparentcolor' in tkinter?

python 3.10, 3.11, windows 10
short:
transparency is affecting title bar when it shouldn't, simple code with example below, move the window to the middle of the screen, maximise and restore to reproduce the behaviour
long:
I know it looks so simple, but please bear with me. This thing is driving me nuts. I'm not sure what I could be doing wrong, it looks like a bug, maybe?
Maximising (or restoring to normal state after maximise) breaks the title bar. Title bar is not registering mouse clicks, I can't close the window or resize it because click goes through. It acts as it if it was transparent. In the main app I am using another hidden window with -alpha to grab mouse events on transparent canvas. Both windows are bound together and act as one. Having second window behind this one also doesn't help. Both windows are affected and unclickable (well, -alpha part of the window is clickable, but not the title bar).
I made a very short code to reproduce this behaviour. You can try it with frame instead of a canvas, or a button. Result is the same. Curious thing I have also discovered when using #000000 for colour - it breaks the window in even weirder way. Not sure what to make of it. #000001 works the same as yellow (or blue, red, etc.).
example:
from tkinter import Canvas, Tk
root = Tk()
root.attributes('-transparentcolor', 'yellow', '-topmost', True)
root.geometry("600x600")
x = Canvas(root, bg='yellow')
x.pack(expand=1, fill='both')
root.mainloop()
If I try different width and height values, sometimes I can grab only half of the title bar or close the window, but not minimise. It seems to depend on where the window is on the screen and the size of the object. Feels like the transparent part of the object is extending through the title bar making it unresponsive. I tried separating the title bar from the rest of the window with frames or shapes but it doesn't help (sometimes it works, but is dependent on size and location of the window - you may get lucky and not notice the behaviour)
x = Canvas(root, bg='yellow', height=600, width=600)
x.pack()
The best solution I've come up with so far:
def refresh(self):
self.state('iconic')
if self.state() == 'iconic':
self.state('normal')
self.focus_force() # needed for Entry widget
This function assigned to the button which is minimising the window to the taskbar and then returning it to normal state immediately. Obviously this is far from elegant, because the user have to perform an unnecessary action. I could use overrideredirect and hopefully recreate resize and close functionality of the window but it seems like an overkill for rather simple app.
Not sure what else to say. It's late, bye
edit:
trying this now and it somehow works, but sometimes the window blinks uncomfortably.
from tkinter import Canvas, Tk
class Window(Tk):
def __init__(self):
super().__init__()
self.bind("<Map>", self.refresh)
self.canv = Canvas(self)
self.canv.pack(expand=1, fill='both')
def refresh(self, event):
if self.state() == 'normal':
self.attributes('-transparentcolor', 'yellow', '-topmost', True)
self.canv.configure(bg='yellow')
elif self.state() == 'zoomed':
self.attributes('-transparentcolor', 'blue', '-topmost', True)
self.canv.configure(bg='blue')
if __name__ == '__main__':
w = Window()
w.mainloop()

Frame with no background in tkinter

I have a canvas with a background image in Tkinter. I want to add a frame with no background so that I can arrange elements in the window, but, still see the background behind these elements. When I use something similar to the code below, i.e., without specifying the bg color, I get a frame with a grey background. How do I turn it to no background at all?
import tkinter as tk
from PIL import Image,ImageTk
root=tk.Tk()
root.geometry("800x560")
bgImg=Image.open("data/bg.png")
bgImg=ImageTk.PhotoImage(bgImg)
canvas=tk.Canvas(root,width=800,height=560)
canvas.pack(expand = False, fill = "both")
canvas.create_image(0, 0, image=bgImg, anchor="nw")
frame=tk.Frame(canvas,width=50,height=50)
frame.place(relx=0.5,rely=0.5,anchor="center")
root.mainloop()
There may* be platform-dependent options, like this.
(* In Lubuntu 18.04 & python 3.6.9, they don't seem to work,
so I can't test them.)
One cross-platform(?) option, is to use Canvas.
Then draw images* on the canvas, instead of using widgets.
(*whose alpha channel, determines their transparent pixels)
This will draw foreground/background elements correctly,
& Canvas has enough functionality to control elements on it
(but will need work, if you want to emulate full widgets).
Canvas offers various create_xyz methods, where xyz:
arc, line, rectangle, bitmap, oval, text, image, polygon,
window (<- read 'widget')*.
These return an id, that represents the item in the canvas.
Items can also be associated in groups, represented by tags.
* When using widgets in Canvas, there are some restrictions:
https://tcl.tk/man/tcl8.6/TkCmd/canvas.htm#M163
"Note: due to restrictions in the ways that windows are managed,
it is not possible to draw other graphical items
(such as lines and images) on top of window items.
A window item always obscures any graphics that overlap it,
regardless of their order in the display list."
Items in a canvas, can be configured & have event-handlers:
itemconfig / itemcget
tag_bind / tag_unbind
These can use either an individual item-id, or* a group-tag.
(* despite the 'tag_' in the name, they also take item-ids)
Many other Canvas methods work with item-ids or group-tags
(e.g. move, delete,).
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=300, height=300)
canvas.pack()
# Note:
# If the images overlap exactly (same position & extent),
# the bottom one will never? get any events.
# Maybe events can be propagated, not sure right now.
fgImg = tk.PhotoImage(master=root, file='media/fg.png')
fgImgId = canvas.create_image(
80, 80, anchor=tk.NW, image=fgImg
)
canvas.tag_bind(
fgImgId,
'<ButtonRelease-1>',
lambda e: canvas.tag_raise(fgImgId)
)
bgImg = tk.PhotoImage(master=root, file='media/bg.png')
bgImgId = canvas.create_image(
0, 0, anchor=tk.NW, image=bgImg
)
canvas.tag_bind(
bgImgId,
'<ButtonRelease-1>',
lambda e: canvas.tag_raise(bgImgId)
)
root.mainloop()
There are more StackOverflow questions about this, e.g.:
transparent-background-in-a-tkinter-window
python-tkinter-label-background-transparent
configure-tkinter-ttk-widgets-with-transparent-backgrounds-ttk-frame-background
and more.
You might also want to look into other gui toolkits.
(e.g. wxpython*, pyqt, )
(*Here it says, that it has a SetTransparent command.)

Tkinter button default infinitely large

I am trying to set up a basic GUI with two buttons and some number of labels (maybe 4 or so).
I was under the impression that Tkinter buttons had a default size that was relative to the amount of text contained in the button. When my buttons appear on the screen however, they seem to take up as much of the screen as possible depending on where I place them. Here is my code:
from tkinter import *
root = Tk()
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
screen_width2 = screen_width/1.3
screen_height2 = screen_height/1.3
a = str(int(screen_width2)) + "x" + str(int(screen_height2))
root.geometry (a)
root.title("Tester")
welcomeUser = Label(root, text ="Welcome User")
upload = Button(root, text="Upload")
run = Button(root, text="Run")
welcomeUser.place(bordermode=INSIDE, relheight= 0.25, relwidth= 1.0)
upload.place(bordermode=INSIDE, relheight= 1.0, relwidth= 0.25)
run.place(bordermode=INSIDE, relheight= 1.0, relwidth= 1.75)
root.mainloop()
For example, when I run this code, the text for the "run" button is in the correct place, but the actual button takes up the entire window. Whichever button is run last eclipses everything else in the window.
I tried to resize the button with config() and just by changing the size within its own parameters like run = Button(root, text="Run", height=100, width=100).
I know it has something to do with the fact that I'm using place(), because when I use pack() or grid(), the button size is default (size of text).
For the people who are going to say "Use grid instead of place()" - I can't figure out how grid() would better. It seems much less intuitive and much less effective, especially for screen resolution dependent application window sizes (as is part of my code). In fact the whole format of grid() is much weaker than just using floats measures relative to window (I.E. how is done using place). I come from an OS background where this is standard practice, and people who use pixels etc are derided. The script seems to be flipped on StackOverflow with Tkinter, though. Feel free to change my mind.
My question is, how can I can keep the buttons at their default size (encapsulating text) while still being able to place them effectively?
When you use place and specify relwith and/or relheight, you are requesting that the buttons width or height be relative to its parent. In this case the parent is the root window.
Thus, if you set the relheight to 1, you are requesting that the button be exactly as tall as the whole window. Similarly, when you set relwidth to .25, you are asking it to be 1/4 as wide as the whole window.
You can easily see this by simply removing the relwidth and relheight properties and the buttons will be their natural size.

Python: Having trouble using .bind() on anything not involving the mouse

I can make a basic python application like so:
from tkinter import *
block = None
def moveUp(event):
field.move(block,0,-50)
root = Tk()
field = Canvas(root, width = 300, height = 300, bg = 'light blue')
field.pack()
block = field.create_rectangle(100,100,110,110)
field.bind('<Button-1>',moveUp)
mainloop()
and it will behave just like you would expect. It creates a square on a Canvas and moves that square up 50 pixels every time you click in the Canvas.
However, When I replace
field.bind('<Button-1>',moveUp)
to, for example,
field.bind('<Return>',moveUp)
the square does not move, no matter how many times I press the Enter key. This problem persists for any kind of keyboard input (e.g <space>, etc), but any input involving the mouse is fine.
Any input at all is appreciated. Thanks!
The field does not have focus, and therefore does not capture the keypress. One option is simply to make the binding more general:
field.bind('<Return>',moveUp)
to
root.bind('<Return>',moveUp)
Another option is to set the focus to the field:
field.bind('<Return>',moveUp)
field.focus_set()
Not entirely sure what's the reason, but it seems to work if you use bind_all instead of bind.
field.bind_all('<Return>',moveUp)
My guess is that using the keyboard, the canvas does not have focus and so does no register the event. Using bind_all, the event is registered when any widget of the application has focus.
See here for some information on levels of binding.

Tkinter box issues- Entry, Labelbox, Text all take up space from side to side, grid crashes computer

I'm using a tkinter canvas and trying to make a chat box on the right side of my game. However, I found that when I do...
import turtle
import tkinter as tk
master = tk.Tk()
w = tk.Canvas(master,width=1155,height=600,cursor='cross_reverse', bg='#101010')
shift = 1.000
sc = turtle.TurtleScreen(w)
tu = turtle.RawTurtle(sc)
e = tk.Entry(master, bg = '#000', fg = '#03f', font = 'Courier', justify='right', insertbackground = '#101010',width='115')
lb = tk.Listbox(master,height=3)
#e.grid(row=3,column=3)
sc.bgcolor("#101010")
txt = tk.Text(master,state="disabled")
txt.pack()
lb.pack()
w.pack()
sc.tracer(100)
drawcontinents() #Draws stuff with turtle, works just fine
e.pack()
tk.mainloop()
... a few things go wrong.
1.Text and Entry do not seem to want to coexist. I seem to be only able to have one or the other. My plan was to use entry as a chat entry, and display messages in Text. My backup plan is to append messages to label.
2.Text, entry, and Label box take up the entire window in whatever rows they are in, which blocks out the rest of what I am trying to draw. In other words,it puts the text box in the center, with a big gray stripe from side to side across whatever I've drawn. Is there any way to just display the box, and put it to the right?
3.Whenever I try to use the grid system, my whole computer freezes and I have to restart. Is this because the program is taking up more space than I have available, or is this a known bug or problem with installation?
You cannot use both pack and grid at the same time for the same containing widget (ie: for all widgets inside the same frame, toplevel or root window).
What happens is this: grid lays out all the widgets, potentially changing the size of some widgets based on your options (ie: it may grow a widget to stick to the sides of the cell). pack then notices that some widgets changed size in the containing widget it thinks it is responsible for, so it redoes what it thinks is the proper layout. This may change the size of some widgets based on your options. grid then notices that some widgets it thinks it is responsible for change size so it redoes what it does, potentially changing the size of some widgets. pack notices and re-adjusts, grid notices and re-adjusts, pack notices, ... until the end of time.
The solution is simple: only use grid, or only use pack, for all widgets that have a common parent. In this case, all your widgets share the root window as their parent, so they all need to use grid, or they all need to use pack.

Categories

Resources