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

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.

Related

Only Button-1 Works in Event Bind Tkinter

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.

python tkinter - want to call a function when scrollbar clicked in ScrolledText widget

[Edits noted:]
I want to hook into the ScrolledText widget so that when a user clicks anywhere in the scrollbar (or even scrolls it with the mouse wheel, hopefully) I can have it call a callback function where I can
[Add: "set a flag, and then return to let the ScrolledText widget do its thing."]
[Delete: " do something first (like turn off automatic scrolling) before the scrolling action takes place."]
Is this possible?
Thanks
Do you want to do something like turning off automatic scrolling, or is that actually what you want to do?
If you want to turn automatic scrolling on or off, just check the position of the text before inserting text. If it's at the end, add the text and autoscroll. If it's not at the end, add the text but don't scroll. This will work even if they scrolled by some other mechanism such as by using the page up / page down keys.
You can check a couple of different ways. I think the way I've always done it (not at my desktop right now to check, and it's been a few years...) is to call dlineinfo on the last character. If the last character is not visible, this command will return None. You can also use the yview command to see if the viewable range extends to the bottom. It returns two numbers that are a fraction between zero and one, for the first and last visible line.
While the user can't turn auto-scrolling on or off by clicking a button, this is arguably better because it will "just happen" when they scroll back to see something.
Not without reaching inside the ScrolledText to get at the Scrollbar and the Text and hook their bindings.
And, while you can do that, at that point, why even use ScrolledText? The whole point is that it's does the scroll bindings automagically without you having to understand them. If you don't want that, just use a Scrollbar and a Text directly. Tkinter Scrollbar Patterns explains how to do this in detail, but really, if you don't want to do anything unusual, it's just connecting a message from each one to a method on the other.
For example:
from Tkinter import *
def yscroll(*args):
print('yscroll: {}'.format(args))
scrollbar.set(*args)
def yview(*args):
print('view: {}'.format(args))
textbox.yview(*args)
root = Tk()
scrollbar = Scrollbar(root)
scrollbar.pack(side=RIGHT, fill=Y)
textbox = Text(root, yscrollcommand=yscroll)
for i in range(1000):
textbox.insert(END, '{}\n'.format(i))
textbox.pack(side=LEFT, fill=BOTH)
scrollbar.config(command=yview)
mainloop()
If you can't muddle out the details from the (sometimes confusing and incomplete) docs, play around with it. Basically, yview is called whenever the scrollbar is moved, and yscroll is called whenever the view is scrolled. The arguments to yscroll are obvious; those to yview less so, but the docs do explain them pretty well.
Note that, when you've set things up normally, dragging the scrollbar or swiping the trackpad or rolling the mousewheel over the scrollbar sends a yview, which makes our code call textbox.yview, which then sends a yscroll, and that does not cause a new yview (otherwise, there would be an infinite loop). So, you see both methods get called. On the other hand, swiping the trackpad or rolling the mousewheel over the text, or using the keyboard to move off the bottom, sends yscroll, which again does not cause a yview, so in this case you only see one of the two methods.
So, for example, if you change yview to not call textbox.yview, you can drag the scrollbar all you want, but the text view won't move. And if you change yscroll to not call scrollbar.set, you can swipe around the text all you want, but the scrollbar won't move.
If you want a horizontal scrollbar as well, everything is the same except with x in place of y. But ScrolledText doesn't do horizontal scrolling, so I assume you don't want it.
If you really do want to dig into ScrolledText, you can look at the source for your version, which is pretty trivial if you understand the example above. In fact, it's basically just an OO wrapper around the example above.
In at least 2.7 and 3.3, the ScrolledText is itself the Text, and its self.vbar is the Scrollbar. It sets yscrollcommand=self.vbar.set in its superclass initialization, and sets self.vbar['command'] = self.yview after vbar is constructed. And that's it.
So, just remove the explicit scrollbar creation, and access it as textbox.vbar, and the same hooking code as above works the same way:
from Tkinter import *
from ScrolledText import *
def yscroll(*args):
print('yscroll: {}'.format(args))
textbox.vbar.set(*args)
def yview(*args):
print('yview: {}'.format(args))
textbox.yview(*args)
root = Tk()
textbox = ScrolledText(root)
for i in range(1000):
textbox.insert(END, '{}\n'.format(i))
textbox.pack(side=LEFT, fill=BOTH)
textbox['yscrollcommand'] = yscroll
textbox.vbar.config(command=yview)
mainloop()
Just be aware that this (the fact that textbox is a normal Text, and textbox.vbar is its attached Scrollbar) isn't documented anywhere, so it could theoretically change one day.

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.

Randomly add buttons to Tkinter GUI?

How do I randomly add buttons to a Tkinter GUI? I need it to be able to create a button, then put it anywhere on the window, is this possible? I am using Python 2.6 on Windows.
If you want random button placement (or anything not aligned along a grid, etc.), you can use the place geometry manager. Depending on platform, overlapped buttons may not behave as you expect, though, so you may want to avoid them.
Here's a simple example:
from Tkinter import *
from random import random
root = Tk()
frame = Frame(root, height=200, width=200)
for i in range(10):
Button(frame, text=str(i)).place(x=random() * 150, y=random() * 180)
frame.pack()
root.mainloop()
There are several options to choose from. For example, you could design on a grid where you have six buttons per row. Then it's just a matter of starting at row 0, incrementing the column for each button. When you get to the last column, reset the column to 0 and increment the row by one.
Another option is to use a text widget as the container, and embed your buttons in the text widget with wrapping enabled. With this trick the buttons will fill a row automatically and wrap if the user grows or shrinks the main windows. It's a tiny bit more work, but it works well if that's the behavior you want.

How to pack a tkinter widget underneath an existing widget that has been packed to the left side?

I'm attempting to write a basic Tkinter GUI that has a Text widget at the top, then a Button widget left aligned under it, then another Text widget underneath the button. The problem I'm having is, after packing the Button widget to the left, when I then go to pack the second Text widget, it puts it next to the button on the right, rather than underneath the button. This happens regardless of what I set the side argument to for the second Text widget Here's a simple piece of code that demonstrates this behaviour:
from Tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(side=LEFT)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
So how would I go about setting up the second Text widget so that it appears below the button, rather than to the right of it?
There are generally two solutions to layout problems:
switch to using grid. It becomes real easy to do layouts like what you are trying to accomplish. Grid can solve probably 95% of all layout issues (it's amazing when you think about it -- Tk does with one manager what most toolkits need half a dozen to accomplish!)
use multiple frames. If some widgets need to be stacked top-to-bottom and some left-to-right you can't always get what you want packing everything in a single frame. Use one frame for the top-to-bottom parts of the layout and additional frames for the left-to-right content.
Also realize that widgets don't have to be children of the widget in which they are packed/gridded. You can use the "in" parameter to put widgets in some other container than their parent.
For example, in your specific example you can create three frames, top, middle, bottom. Pack these top-to-bottom in your toplevel window. Then you can pack the first text widget in the top, the button or buttons horizontally in the middle, and the other text widget in the bottom.
The advantage to such an approach is that it makes it much easier to change the layout in the future (which in my experience always happens at some point). You don't have to re-parent any of your widgets, just pack/place/grid them in some other container.
In your short example it doesn't make much difference, but for complex apps this strategy can be a life saver.
My best advice is this: layout isn't an afterthought. Do a little planning, maybe even spend five minutes drawing on some graph paper. First decide on the major regions of your app and use a frame or some other container for each (paned window, notebook, etc). Once you have those, do the same divide-and-conquer approach for each section. This lets you use different types of layout for different sections of your app. Toolbars get horizontal layout, forms might get vertical layout, etc.
I was initially misunderstanding how packing worked and didn't realise that the entire left side was being "claimed" when i did x.pack(side=LEFT). What I found after reading this and the answer by Alex here is that I was not really after having x packed to the left side at all, but rather having it anchored to the left, using anchor=W (W for West) instead of side=LEFT. My revised code snippet which does what I was after looks like this:
from tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(anchor=W)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
This way x is not "claiming" the left side anymore, it's just aligned to the left (or West) within its block of space.
Packing happens in the order the .pack methods are called, so once x has "claimed" the left side, that's it -- it will take up the left portion of its parent and everything else within its parent will be to its right. You need a Frame to "mediate", e.g....:
from Tkinter import *
root = Tk()
w = Button(root, text="Mysterious W")
w.pack()
f = Frame(root)
x = Button(f, text="Hi there!")
x.pack()
y = Button(f, text="I be Y")
y.pack(side=BOTTOM)
f.pack(side=LEFT)
root.mainloop()
(changed Texts to Buttons for more immediate visibility of layout only -- the Tkinter on this Mac doesn't show Texts clearly until they have focus, but Buttons are quite clear;-).
Do it the same way that WebView does using the Mosaic Canvas Widget Sets internals(which are very similar to Tk). The trick is that the second identical named Frame Object works as a Block Level Float(inline:block;) for everything placed after it and everything that calls "fr" already will automatically begin over inside of it.
You can have many doing this of TOP aligned widgets and simply add another identical named Frame where you want to break between side=LEFT's. Works after Bottom also.
fr=Frame(root)
fr.pack(fill=X, side=TOP)
block1=Label(fr)
block1.pack(side=LEFT)
block2=Label(fr)
block2.pack(side=LEFT)
block3=Button(fr)
block3.pack(side=LEFT)
# NAME IT THE SAME ID NAME AS THE FIRST MAIN FRAME...
fr=Frame(root)
fr.pack(fill=X, side=TOP)
# These NOW jump into the second Frame breaking the side=LEFT in new Frame
block4=Label(fr)
block4.pack(side=LEFT)
block5=Label(fr)
block5.pack(side=LEFT)
# AND THEY CONTINUE GOING side=LEFT AFTERWARDS.

Categories

Resources