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!
Related
I have been trying to write simple CAD application in Python. I was tinkering with pyglet, got some results: , but I decided to switch back to Gtk and I hit the wall:
I cannot get the mouse pointer position over the EventBox (with pyglet it was the label in the bottom left corner of the window app in the picture). What signal is designed for it? Or should I use another approach?
I will appreciate any piece of information or resources. Thanks in advance.
Question: How to detect the mouse position over EventBox?
how-to-capture-event-on-event-box-to-detect-mouse-movement-in-gtk
Gtk.EventBox
The Gtk.EventBox widget is a subclass of Gtk.Bin which also has its own window. It is useful since it allows you to catch events for widgets which do not have their own window.
Gtk.Widget.add_events(events)
Adds the events in the bitfield events to the event mask for self.
Gdk.EventMask
A set of bit-flags to indicate which events a window is to receive.
GDK_MOTION_NOTIFY
the pointer (usually a mouse) has moved
The Gtk.EventBox should have got the flag, not the window!
box = Gtk.EventBox()
box.connect("motion-notify-event", self.on_mouse_move)
box.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
With the help of stovfl the solution was achieved.
The application
consisted of a drawing area, a frame, an event box and a label, for displaying of the mouse pointer coordinates. The drawing area was added to the box, the box was added to the frame, which was added to the grid.
from gi.repository import Gtk
from gi.repository import Gdk
# (...)
box = Gtk.EventBox()
box.add_events(Gdk.EventMask.POINTER_MOTION_MASK) # 1
box.connect("button-press-event", self.on_click)
box.connect("motion-notify-event", self.on_mouse_move) # 2
self.canvas = Gtk.DrawingArea()
self.canvas.connect("draw", self.on_draw)
self.canvas.set_size_request(800, 600)
box.add(self.canvas)
grid = Gtk.Grid()
frame = Gtk.Frame()
frame.set_label("KHAD")
frame.add(box)
grid.attach(frame, 0, 1, 1, 1)
self.add(grid)
# (...)
self.locationLabel = Gtk.Label("X,Y")
self.locationLabel.set_alignment(0, 0)
grid.attach(self.locationLabel, 0, 2, 1, 1)
The solution was:
Add POINTER_MOTION_MASK to the event box: box.add_events(Gdk.EventMask.POINTER_MOTION_MASK).
Connect motion-notify-event of the box with a method, which read and updated a label (down-left corner; it was aligned with self.locationLabel.set_alignment(0, 0)).
I am creating a gui with tkinter in python. I have created a scrollbar and this is what the section of code looks like:
beta_frame = Frame(width="500", height="680")
beta_frame.pack()
holder = ScrolledWindow(beta_frame, width=500, height=680)
holder.pack()
alpha_frame = holder.window
I would like to position this scrollbar at the very bottom every time something new is put on the screen (which would obviously be added to the bottom The only things I'm adding to the screen are labels and buttons), though I'm unsure how to do this and I've searched everywhere. All I came up with is the method see, which I am unsure if it is even applicable in this instance. Any help would be appreciated.
.see() is the normal way to get Tkinter to auto-scroll to a given position, but that method only exists on the widgets that have built-in support for scrolling - Listbox, Canvas, Text, and Entry. The Tix ScrolledWindow makes an ordinary Frame scrollable, so no such method will exist.
It appears that this line of code will do what you want:
holder.tk.eval(holder.vsb['command'] + " moveto 1.0")
vsb is the vertical scrollbar component of the ScrolledWindow, 'command' is the scrollbar configuration option that specifies a callback to invoke when the position is changed. This will refer to something deep inside Tix, but we don't care exactly what it is; we just invoke it with the same parameters that the scrollbar itself would, if being moved to the very end.
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)
I am trying to make a kind of movable widget program in python with Tkinter, but I ran into a problem. I can't detect a click without interfering with the function of the widget that you click on. (example: Text or Button widget)
Here is an example:
import tkinter as tk
main = tk.Tk()
notes = tk.Text(main, height = 15, bd = 4)
notes.place(y = 10, x = 20)
notes.bind("<Button-1>", lambda event: print("hello"))
But if you try and click in the middle, it still works. Is there any way to make it only clickable on the border and not the widget itself?
You can prevent the default behavior by returning "break" from the event handler. For example:
def hello(event):
print("hello")
return "break"
...
notes.bind("<Button-1>", hello)
The same works for any widget. This prevents the default behavior (moving the cursor, clicking the button) from happening.
Another choice is to put each widget in a frame with a small border, and then put the binding on the frame.
I have a glade GUI and i'm using dome gtk.MessageDialog widgets created with pygtk for user interaction. My problem is that whenever I throw a dialog message on the screen, they show up all over the place. One might show up on the top right corner, the next on the bottom left, top left, mid left etc...
Is there a way to force these things to show up in the center of the screen or at the position where the parent window is at?
Never mind. Found the solution.
For others who might wander about the same thing, the solution to this problem lies in specifying a parent value to the gtk.MessageDialog construct.
If you are using a glade gui, in your class, and your glade xml is loaded in to a variable named 'gui', it would look like this:
#!/usr/bin/env/python
par = self.gui.get_widget('your_parent_window')
msg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK, parent=par)
if msg.run():
msg.destroy()
return None
Check out the reference material at PyGTK 2.0 Reference Manual
I have not had a chance to try this but MessageDialog seems to be derived from Window which has a set_position method.
This method accepts one of the following:
# No influence is made on placement.
gtk.WIN_POS_NONE
# Windows should be placed in the center of the screen.
gtk.WIN_POS_CENTER
# Windows should be placed at the current mouse position.
gtk.WIN_POS_MOUSE
# Keep window centered as it changes size, etc.
gtk.WIN_POS_CENTER_ALWAYS
# Center the window on its transient parent
# (see the gtk.Window.set_transient_for()) method.
gtk.WIN_POS_CENTER_ON_PARENT
None of the provided solutions will work if your parent window is not yet shown, that is if the messagedialog is to be shown during the instantiation of a class (your class, not the "parent" window class). During this time Gtk has not yet placed the window, even if code for messagedialog is after the code that shows the window. Which means your dialog box will be somehow "parentless" and the message dialog will appear wherever it likes...
My naive solution for that problem...
GObject.timeout_add(interval=50, function=self.stupid_dialog_1)
and
def stupid_dialog_1(self):
par = self.gui.get_widget('your_parent_window')
msg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK, parent=par)
# do anything here...
return False #stop the timer...