I'm pretty new to Python and I'm currently playing a bit with Tkinter which looks amazingly simple. I've tried to implement a simple drag and drop effect using the following code (right mouse button creates a circle, left mouse button allows dragging) :
from tkinter import *
class Point:
def __init__(self, ref, x, y):
self.ref = ref
self.x = x
self.y = y
points = []
selected = None
def OnSelect(event):
global selected
for p in points:
if event.x>=(p.x-10) and event.y>=(p.y-10) and event.x<(p.x+10) and event.y<(p.y+10):
selected = p
break
def OnMMove(event):
if selected is not None:
selected.x = event.x
selected.y = event.y
canvas.coords(selected.ref, event.x-10, event.y-10, event.x+10, event.y+10)
def OnStopDrag(event):
global selected
selected = None
def OnCreate(event):
point = canvas.create_oval(event.x-10, event.y-10, event.x+10, event.y+10, fill="black")
points.append(Point(point, event.x, event.y))
window = Tk()
window.wm_title("Python")
canvas = Canvas(window, width=800, height=600, background='white')
canvas.bind("<Button-1>", OnSelect)
canvas.bind("<B1-Motion>", OnMMove)
canvas.bind("<ButtonRelease-1>", OnStopDrag)
canvas.bind("<Button-3>", OnCreate)
canvas.pack(fill=BOTH, expand=YES)
window.mainloop()
As can be seen in this code, I'm using canvas.cords to move the dragged object around. Everything works fine when the mouse cursor is slowly moved while dragging, however when the mouse cursor is moved rapidly, the dragged circle seems to be partialy clipped in a rectangle while moved as seen on this picture (the whole circle is correctly drawn entirely again when dragging stops or slows down) :
I've already encountered a similar issue whend using GDI in a Win32 C application, when calling screen invalidation to repaint the window client area on the sole area covered by the initial position of the circle being currently dragged.
And indeed, when the window created in my example code is placed above a window which is constantly and entirely being redrawn, like a video game window, the cropping effect while dragging elements is not seen and the whole circle is correctly being redrawn as it is dragged.
Is there a way to solve this issue, like a canvas setting making window invalidation being called on a wider or the whole client area ? I would like to stick with Tkinter so I'm not really interested in switching to another GUI API/framework. This code has been tested on Windows 10.
This answer may confuse you (it does me). But a solution is to configure the cursor within OnMMove. Here is the excerpt that worked on Windows for me.
def OnMMove(event):
if selected is not None:
canvas.configure(cursor='arrow')
selected.x = event.x
selected.y = event.y
canvas.coords(selected.ref, event.x-10, event.y-10, event.x+10, event.y+10)
Related
I'm trying to create a simple drag-and-drop interface in Tkinter using the canvas. However, I've hit a snag:
The event binds 'enter' and 'leave' seem to only work for the TOP-most widget under the cursor. E.g., I wrote this bit of code to try and recognize when the cursor is over the 'board' space or not:
self.canvas.create_rectangle(50, 50, 1250, 750, fill = 'grey', tag = 'Board')
self.canvas.tag_bind('Board', "<Enter>", self.on_board)
self.canvas.tag_bind('Board', "<Leave>", self.off_board)
self.mouse_on_board = False
def on_board(self, event):
self.mouse_on_board = True
def off_board(self, event):
self.mouse_on_board = False
But when the cursor enters a draggable item on the board (e.g. a canvas.create_image() image drawn on top of the 'board' rectangle) this is treated as the cursor leaving the board widget. Whereas for my purpose, I want the code to recognize it is still over the board (just over another widget which is itself drawn over the board).
Is there a simple way in Tkinter to know whether the cursor is on top of a widget REGARDLESS of whether there are any other widgets stacked above or beneath it?
Thanks in advance for any answers!
I'm trying to create a windows app that shows weather like windows 10 widgets using Tkinter. The issue I ran into is that I cannot remove the window's border to make it look like a real widget.
Here's what it looks like and what I'd like to remove:
self.root.overrideredirect(1) kinda works but I still can minimize the window, which is what I do not want to be available.
That's what I want it to look and behave like:
To sum up:
How to disable window title and all of that on top properly?
How to move the window after, set the position of it on the screen?
I'm new to Python, any help is appreciated. Thank you all. Peace
To clarify: The app is supposed to be not affected by tab+alt (and other ways to minimize it) and be always on desktop, not on top of any other app, right on the desktop.
UPD
The thing I missed working around with win32gui is that I cannot get the handle the way I was trying to due to the fact that the code of getting the handle before the window itself was created (before mainloop) so root.wait_visibility() was the solution for me. After that I got the handle using EnumWindows and positioned my window the way I needed, finally disabling the border with root.overrideredirect. The suggested solutions will be tried as well. Thank you for help!
top-right corner of the desktop:
You can try this:
root.overrideredirect(1)
root.geometry('250x150+900+600')
#900+600 is x+y axis position of window
And for making close button like of widget you can make transparent frame in side, put button there and in command .destroy().
Edit
From TheLizzard's comment, visit here for further information. From there:
To make the window draggable, put bindings for (mouse clicks) and (mouse movements) on the window.
import tkinter
class Win(tkinter.Tk):
def __init__(self,master=None):
tkinter.Tk.__init__(self,master)
self.overrideredirect(True)
self._offsetx = 0
self._offsety = 0
self.bind('<Button-1>',self.clickwin)
self.bind('<B1-Motion>',self.dragwin)
def dragwin(self,event):
x = self.winfo_pointerx() - self._offsetx
y = self.winfo_pointery() - self._offsety
self.geometry('+{x}+{y}'.format(x=x,y=y))
def clickwin(self,event):
self._offsetx = event.x
self._offsety = event.y
win = Win()
win.mainloop()
You can use this for some part in top of the window rather then in whole window. By making new frame there implement it, to make your widget moveable.
And from second comment of TheLizzard, visit here for more information. From there:
If you want the window to stay above all other windows.
root.attributes("-topmost", True)
As the title says, when attempting to save the Canvas using Postscript, it works fine with all non-window elements (rects, ovals etc..) and it works perfectly in capturing window elements that are CURRENTLY on screen when I push the button. But none of the window elements outside of the screen at the time.
This issue seems so arbitrary I gotta wonder if there even is a solution, hopefully someone out there has figured something out.
Here is some example code, where I simplify to present the exact issue:
#!/usr/bin/python3
#
# This file is intended as a simplified example for Stack Overflow.
# The original program is far greater and is a writing tool for branching dialogue, much like Twine.
from tkinter import Tk, Canvas, Frame, Text, Label
class Canv(Canvas):
def __init__(self, parent):
"""Simple Canvas class."""
Canvas.__init__(self, parent)
self.parent = parent
self.config(background="white", width=960, height=640)
self.num = 1
self.pack()
self.bindings()
def bindings(self):
"""All the button bindings."""
self.bind("<Button-1>", self.add_window)
self.bind("<ButtonPress-2>", self.mark)
self.bind("<ButtonRelease-2>", self.drag)
self.bind("<Button-3>", self.take_ps)
def add_window(self, e):
"""Here I add the Label as a Canvas window.
And include an Oval to mark its location.
"""
text = "Textwindow {}".format(self.num)
self.num += 1
window = TextWindow(self, text)
pos = (self.canvasx(e.x), self.canvasy(e.y))
self.create_window(pos, window=window)
bbox = (pos[0]-50, pos[1]-50, pos[0]+50, pos[1]+50)
self.create_oval(bbox, width=3, outline="green")
def mark(self, e):
"""Simple Mark to drag method."""
self.scan_mark(e.x, e.y)
def drag(self, e):
"""This drags, using the middle mouse button, the canvas to move around."""
self.scan_dragto(e.x, e.y, 5)
def take_ps(self, e):
"""Here I take a .ps file of the Canvas.
Bear in mind the Canvas is virtually infinite, so I need to set the size of the .ps file
to the bounding box of every current element on the Canvas.
"""
x1, y1, x2, y2 = self.bbox("all")
self.postscript(file="outfile.ps", colormode="color", x=x1, y=y1, width=x2, height=y2)
print("Writing file outfile.ps...")
class TextWindow(Frame):
def __init__(self, parent, text):
"""Very simple label class.
Might have been overkill, I originally intended there to be more to this class,
but it proved unnecesary for this example.
"""
Frame.__init__(self, parent)
self.pack()
self.label = Label(self, text=text)
self.label.pack()
if __name__ == "__main__": #<---Boilerplate code to run tkinter.
root = Tk()
app = Canv(root)
root.mainloop()
This is an example .jpg based on the postscript.
As you can see from the image, all the green circles on the right have the window label intact. Well ALL the green circles are supposed to have them, and in the program they work fine, the are just not showing in the postscript. And yes, my screen was over the right circles when I clicked the take_ps button.
As for alternatives, I need the canvas to be draggable, I need it to expand, potentially vast distances in both direction. And I cannot put text directly on the canvas, as it would take too much space. It is intended to have text fields, not just a label in the windows on the canvas (became too much code for this example), and the reason I need text in a window, and not directly on the screen, is the text might easily take more space then it should. I need the canvas to show the RELATION between the text fields, and the text windows to contain the text for editing, not necessarily full display. As it says, I'm making a branching dialogue tool for a game, much like Twine.
I ran into this issue also. I was able to configure the canvas temporarily to match the size of the output image. Then I configured it back to the original size after I was done creating the postscript file.
height_0 = canvas.winfo_height()
width_0 = canvas.winfo_width()
canvas.config(width= max_width, height= max_height)
root.update()
canvas.postscript(file='filename.ps',colormode='color')
canvas.config(width= width_0, height= height_0)
root.update()
As part of a larger project, I am trying to create a snapshot tool that works similar to the Mac OS X snapshot. It should take a first click, a second click, and return an image of the area created by the square.
I have some python functions that take an first point (x, y) and a second point (x, y) and create a snapshot of the square that those points create on the screenshot. The missing piece is getting the mouse locations of the initial click and second click, then passing that data to the python program to create the snapshot.
In other words, the flow of the program should be:
first click (save x, y)
second click (save x2, y2)
run snapshot.py using the saved clicked data to return the screenshot
I've only found solutions that can return the position of the pointer within a frame. If it helps, I'm using "import gtk" and "from Xlib import display"
edit: I have tried to use Tkinter to make an invisible frame that covers the whole screen. The idea was to use that invisible frame to get the exact coordinates of two mouse clicks, and then the invisible frame would disappear, pass the coordinates on to the screenshot function, and it would be done. However, the code I've been writing doesn't keep the frame transparent.
edit 2: This code can create a window, make it transparent, size it to the screen, then return the mouse coordinates on that window. I can use this to simply return the mouse coordinates on two clicks, then remove the window and send those coordinates to the snapshot code. When I run the below code line-by-line in the python shell, it works perfectly. However, whenever I run the code as a whole, it seems to skip the part where it makes the window transparent. Even if I copy and paste a block of code that includes the 'attributes("-alpha", 0.1)' into the python shell, it ignores that line.
from Tkinter import *
root = Tk()
root.attributes('-alpha', 0.1)
maxW = root.winfo_screenwidth()
maxH = root.winfo_screenheight()
root.geometry("{0}x{1}+0+0".format(maxW, maxH))
def callback(event):
print "clicked at: ", event.x, "and: ", event.y
root.bind("<Button-1>", callback)
def Exit(event):
root.destroy()
root.bind("<Escape>", Exit)
# root.overrideredirect(True)
root.mainloop()
I am open to using any c or c++ code, or any language's code, to return the coordinates of the mouse on a click. This guy wrote some code to actually make the computer click at given points, which may be on the same track as my problem.
It's just a indentation problem - you bound the callback in the callback by mistake - try this instead:
root.geometry("{0}x{1}+0+0".format(maxW, maxH))
def callback(event):
print "clicked at: ", event.x, "and: ", event.y
root.bind("<Button-1>", callback)
EDIT
Ok, here's a theory - maybe when you run it from the command line, it takes longer for the root window to appear, for some reason, so you set the alpha before it exists, and the alpha option gets ignored. Give this a try:
root.wait_visibility(root)
root.attributes('-alpha', 0.1)
I have a Tkinter canvas with a scrollbar, and some items that when I click them, it's supposed to return the coordinates. (Using Python.)
This works fine with the objects that's initially visible in the window. When I scroll down, however, and the items further down on the canvas come into view, I don't get their canvas coordinates when clicking, but the window coordinates.
I can't find info on how to get the absolute coordinates, so I'm wondering if anyone here knows how to do it?
Thanks.
Check out the documentation for the canvas widget here.
To convert from window coordinates to canvas coordinates, use the canvasx and canvasy methods.
Here is an example callback function which converts the window's x and y coordinates and prints the item closest to that position via the find_closest() method.
def callback(event):
canvas = event.widget
x = canvas.canvasx(event.x)
y = canvas.canvasy(event.y)
print canvas.find_closest(x, y)