Feasible with Tkinter 'scrollbar' - python

I have a base program I was playing with this morning I wrote up a few years back. It only had horizontal scrolling and zooming. I managed to get it so I could scroll(one axis at a time) and zoom on both axis. I'm wanting to create a program that will allow me to move around the screen in both x and y directions at the same time(like Google Earth where you can hold down the down and left key at the same time to move to lower left). It would be nice to do it without having the scroll bars on the screen as well. I don't want to have to go back and forth and click on the appropriate scroll bar to be able to scroll in that axis. Currently to change which axis I'm scrolling in I have to click on the opposite axis.
I tried the program at [http://www.tkdocs.com/tutorial/canvas.html#scrolling][1] but I already have the capability of doing that. It doesn't allow me to scroll both directions simultaneously and if I want to change which direction I'm scrolling without having to click on the opposite axis.
Is there a way of doing what I'm trying to do with Tkinter or should I look elsewhere and if so, where?
Thanks.
edit:
With the code Bryan posted below I added in the following code to try to get it two work with the keyboard versus only the mouse. I would like the be able to use the cursor keys to move the image around versus the mouse. I have a nastily touch sensitive mouse on this computer, that has a mind of its own and as a result I would like to stick with the keyboard. Plus, given the naturedness of this darn project I have to leave all option open or else I know I will regret it sometime before this entire project gets finished.
self.canvas.bind("<Left>", self.on_press)
self.canvas.bind("<Right>", self.on_press)
I also tried directing it to self.on_motion and neither one accepted the cursor keys.

Yes, this is possible. There's nothing preventing you from directly calling the canvas xview and yview methods with any arguments you want.
You first need to create bindings that tracks the clicking and the motion of the mouse. In the bound function you can compute the direction that the mouse moved, then use the results to call both the xview and yview methods of the widget at the same time.
Here's an example:
import tkinter as tk
import random
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, background="bisque", width=400, height=400)
self.canvas.pack(fill="both", expand=True)
self.canvas.configure(scrollregion=(-1000, -1000, 1000, 1000))
self.canvas.bind("<ButtonPress-1>", self.on_press)
self.canvas.bind("<B1-Motion>", self.on_motion)
# the following two values cause the canvas to scroll
# one pixel at a time
self.canvas.configure(xscrollincrement=1, yscrollincrement=1)
# finally, draw something on the canvas so we can watch it move
for i in range(1000):
x = random.randint(-1000, 1000)
y = random.randint(-1000, 1000)
color = random.choice(("red", "orange", "green", "blue", "violet"))
self.canvas.create_oval(x, y, x+20, y+20, fill=color)
def on_press(self, event):
self.last_x = event.x
self.last_y = event.y
def on_motion(self, event):
delta_x = event.x - self.last_x
delta_y = event.y - self.last_y
self.last_x = event.x
self.last_y = event.y
self.canvas.xview_scroll(-delta_x, "units")
self.canvas.yview_scroll(-delta_y, "units")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Related

Canvas resize using Configure with Tkinter

I want to create a Board Game with Python and Tkinter
I want it to has a resize-function but I have two canvases for the GUI. First one is the square-Board (Spielfeld), the second one is the place where I want to add the control buttons for the player (Panel)
So if I want to resize my Board using <Configure> in my Master Window, it shall draw the Canvas with the New Size (self.FensterGroesse)
The If-Case is working well when I pass the else -function in resize
but if I run the Programm with the else function it resizes itself until its 1px big. Not just the canvas, the whole window.
I know the problem is the Panel being one third as high as the Board and when self.Panel.config sets the new size <Configure> is activated again.
But I dont know how I can have these two sanvases, one is a square, the other is a rectangle with the same widht and the square bit just 0.3*height
from Tkinter import *
class GUI:
def resize(self, event):
if event.height > (event.width*1.3):
self.FensterGroesse = event.width-2
else:
self.FensterGroesse = int(event.height/1.3)-2
self.Spielfeld.config(height=self.FensterGroesse, width=self.FensterGroesse)
self.Panel.config(height=self.FensterGroesse*0.3, width=self.FensterGroesse)
self.Spielfeld.pack()
self.Panel.pack()
def __init__(self):
self.FensterGroesse = 400
self.tkinter = __import__("Tkinter")
self.Master = self.tkinter.Tk()
self.Spielfeld = self.tkinter.Canvas(self.Master, height=self.FensterGroesse,
width=self.FensterGroesse, bg='#ffdead')
self.Panel = self.tkinter.Canvas(self.Master, height=self.FensterGroesse*0.3,
width=self.FensterGroesse, bg='brown')
self.Spielfeld.pack()
self.Panel.pack()
self.Master.bind("<Configure>", self.resize)
self.Master.mainloop()
GUI()

Toplevel inside another Toplevel in tkinter [duplicate]

I'm not very good with tkinter of python, but i would like to know if theres a way to make a window inside a window, where that window cannot get out of the main window's bounds.
Heres my current code:
from tkinter import *
root = Tk()
root.title("Main Window")
root.geometry("640x480+100+100")
sub = Toplevel(root)
sub.title("Sub Window")
sub.geometry("320x240+125+125")
mainloop()
it would look like this:
I would like to know how I can isolate the Sub Window to keep it inside the main window even if i drag it out.
Thank you very much.
There is no built in method of doing so. However I've made a work around to accommodate it. Keep in mind that when trying to move the sub window outside the main window it isn't a smooth lock so it does get jumpy. Another issue is that because of the configure events I can't get the sub window position relative to main window to maintain it while moving the main window. Still working around that. However the code below does work and should be of use to you.
import tkinter as tk
root = tk.Tk()
root.title("Main Window")
root.geometry("640x480")
sub = tk.Toplevel(root)
sub.transient(root) #Keeps sub window on top of root
sub.title('Sub Window')
sub.minsize(320, 240)
sub.maxsize(320, 240)
pos = []
def main_move(event):
#When the main window moves, adjust the sub window to move with it
if pos:
sub.geometry("+{0}+{1}".format(pos[0], pos[1]))
# Change pos[0] and pos[1] to defined values (eg 50) for fixed position from main
def sub_move(event):
# Set the min values
min_w = root.winfo_rootx()
min_h = root.winfo_rooty()
# Set the max values minus the buffer for window border
max_w = root.winfo_rootx() + root.winfo_width() - 15
max_h = root.winfo_rooty() + root.winfo_height() - 35
# Conditional statements to keep sub window inside main
if event.x < min_w:
sub.geometry("+{0}+{1}".format(min_w, event.y))
elif event.y < min_h:
sub.geometry("+{0}+{1}".format(event.x, min_h))
elif event.x + event.width > max_w:
sub.geometry("+{0}+{1}".format(max_w - event.width, event.y))
elif event.y + event.height > max_h:
sub.geometry("+{0}+{1}".format(event.x, max_h - event.height))
global pos
# Set the current sub window position
pos = [event.x, event.y]
root.bind('<Configure>', main_move)
sub.bind('<Configure>', sub_move)
root.mainloop()
There's nothing built-in to facilitate this, though there are enough building blocks to build your own. You can, for example, create a frame with some custom bindings that allow you to move it around its parent using the place geometry manager.

Tkinter: draw a point using a button click

I have a serious problem this morning: I load a picture to display it on a Canvas. The picture is large, so I set scroll bars to allow the user to view it.
I allow the user to press the right button in order to draw random points with his mouse on the image.
It works, but there is a bug in its behaviour: When I scroll to the right or down of the image and click the right mouse button the point is drawn but not on the pixel I clicked on. It is drawn far away and my brain does not understand why.
A second but less alarming problem is that the points are drawn only in black whatever the color I set to them.
Please help me to resolve this or tell me why this occur and I will try to do something about it. I have been fighting with this problem for 2 hours by now and I did not find similar issues on this website and the whole Google:
import PIL.Image
import Image
import ImageTk
from Tkinter import *
class ExampleApp(Frame):
def __init__(self,master):
Frame.__init__(self,master=None)
self.x = self.y = 0
self.canvas = Canvas(self, cursor="cross",width=600,height=650)
self.sbarv=Scrollbar(self,orient=VERTICAL)
self.sbarh=Scrollbar(self,orient=HORIZONTAL)
self.sbarv.config(command=self.canvas.yview)
self.sbarh.config(command=self.canvas.xview)
self.canvas.config(yscrollcommand=self.sbarv.set)
self.canvas.config(xscrollcommand=self.sbarh.set)
self.canvas.grid(row=0,column=0,sticky=N+S+E+W)
self.sbarv.grid(row=0,column=1,stick=N+S)
self.sbarh.grid(row=1,column=0,sticky=E+W)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.rect = None
self.start_x = None
self.start_y = None
self.im = PIL.Image.open("image.jpg")
self.wazil,self.lard=self.im.size
self.canvas.config(scrollregion=(0,0,self.wazil,self.lard))
self.tk_im = ImageTk.PhotoImage(self.im)
self.canvas.create_image(0,0,anchor="nw",image=self.tk_im)
def on_button_press(self,event):
print"({}, {})".format(event.x,event.y)
self.canvas.create_oval(event.x,event.y, event.x+1,event.y+1,fill='red')
# the fill option never takes effect, I do not know why
# When I scroll over the image, the pixels are not drawn where I click
if __name__ == "__main__":
root=Tk()
app = ExampleApp(root)
app.pack()
root.mainloop()
Thank you in advance for helping me.
1) You need to use canvasx and canvasy method.
See for example:
http://effbot.org/tkinterbook/canvas.htm#coordinate-systems
2) Your oval is so small you only see the border line.
Use outline= instead of fill=

Python; tkinter; Canvas objects and events

I have a class with some mouse events I made :
class graphic_object(object):
def mouse_click(self,event):
#do something
def mouse_move(self,event):
#do something
def mouse_unpressed(self,event):
#do something
Instances of this class aren't literally graphic objects on the screen, but they have their graphic representation, which is circle-shaped, and as I said, they listen to the mouse events. Both, graphic representation and event handling are managed by tkinter.Canvas object, which is their visual container.
When I make one istance of this class:
graphic1 = graphic_object(a,b,c,d) # init method takes coordinates of the circle as arguments; a,b,c,d - numbers
Everything works as it should, object responds on the mouse events in desired way. But when I make two instances:
graphic1 = graphic_object(a,b,c,d)
graphic2 = graphic_object(e,f,g,h)
only the last created object responds on the mouse events.
This is the condition where I check if the mouse is over the circle:
if d < self.radius:
where d is distance between mouse position, and the center of the circle, and radius is radius of the circle.
In the debugger I see that self.center is always the center of the last created object, so condition is always on
the second circle. So, how can I make that both objects respond to the mouse events?
Events handling:
C = Canvas()
C.bind("<Button-1>" ,self.mouse_click)
C.bind("<B1-Motion>",self.mouse_move)
C.bind("<ButtonRelease-1>",self.mouse_unpressed)
It appears that in your mouse binding you are relying on a pre-computed global variable (d). This is not how you should implement such bindings. The first thing you should do in the binding is get the current mouse coordinates, and then calculate d.
Your other choice is to put the binding on each canvas object using the tag_bind method of the canvas. See this question for an example: How do I attach event bindings to items on a canvas using Tkinter?
You wrote in a comment to this answer that you are only sometimes getting mouse clicks. There is not enough detail in your code to know what you're doing, but I can assure you that the canvas doesn't normally fail in such a manner.
I can't debug your code since you are only showing bits and pieces, but here's a working example that tries to illustrate the use of tag_bind. I took some liberties with your code. For example, I added a name parameter so I can print out which circle you clicked on. When I test this, every click seems to register on the proper circle.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, width=400, height=400,
background="bisque")
self.canvas.pack(fill="both", expand=True)
graphic1 = GraphicObject(10,10,100,100, name="graphic1")
graphic2 = GraphicObject(110,110,200,200, name="graphic2")
graphic1.draw(self.canvas)
graphic2.draw(self.canvas)
class GraphicObject(object):
def __init__(self, x0,y0,x1,y1, name=None):
self.coords = (x0,y0,x1,y1)
self.name = name
def draw(self, canvas, outline="black", fill="white"):
item = canvas.create_oval(self.coords, outline=outline, fill=fill)
canvas.tag_bind(item, "<1>", self.mouse_click)
def mouse_click(self, event):
print "I got a mouse click (%s)" % self.name
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Typing Text into the Tkinter Canvas Widget

I need to allow the users to type text into the Canvas Widget, making the canvas update as the user types new text.
Here's what I have tried so far, but am not getting it to work.
First I have a mouseDown method which is bound to Button-1 event
widget.bind(self.canvas, "<Button-1>", self.mouseDown)
This mouseDown method returns the startx, starty positions to my method drawText
def drawText(self, x, y, fg):
self.currentObject = self.canvas.create_text(x,y,fill=fg,text=self.typedtext)
I also have a global binding on the canvas widget to capture any key press like this:
Widget.bind(self.canvas, "<Any KeyPress>", self.currentTypedText)
def currentTypedText(self, event):
self.typedtext = str(event.keysym)
self.drawText(self, self.startx, self.starty,self.foreground)
However there's no error and nothing gets printed on the canvas.
What you want to do is pretty complex and will require quite a bit of code to get working nicely. You will need to handle click events, keypress events, special keypress events (such as "Shift" and "Ctrl"), "Backspace" and delete events, and a lot more.
Nevertheless, first is first and that is getting text to appear in the canvas as a user types. Now, since I don't have your full script, I can't really work with your stuff as is. However, I went and made my own little app that does exactly what you want. Hopefully, it will shine some light on where to go:
from Tkinter import *
class App(Tk):
def __init__(self):
Tk.__init__(self)
# self.x and self.y are the current mouse position
# They are set to None here because nobody has clicked anywhere yet.
self.x = None
self.y = None
self.makeCanvas()
self.bind("<Any KeyPress>", lambda event: self.drawText(event.keysym))
def makeCanvas(self):
self.canvas = Canvas(self)
self.canvas.pack()
self.canvas.bind("<Button-1>", self.mouseDown)
def mouseDown(self, event):
# Set self.x and self.y to the current mouse position
self.x = event.x
self.y = event.y
def drawText(self, newkey):
# The if statement makes sure we have clicked somewhere.
if None not in {self.x, self.y}:
self.canvas.create_text(self.x, self.y, text=newkey)
# I set x to increase by 5 each time (it looked the nicest).
# 4 smashed the letters and 6 left gaps.
self.x += 5
App().mainloop()
Once you click somewhere in the canvas and start typing, you will see text appear. Note however that I have not enabled this to handle deletion of text (that is a little tricky and beyond the scope of your question).

Categories