I have a parent widget which contains a button. When the button is pressed I would like to open a borderless (i.e. no Windows decoration buttons) window directly underneath the parent widget aligned to the left hand side of it. I'm puzzled that the only way (it seems) of setting the position of a window is using .geometry() but worse, I can't seem to get the absolute coordinates of the parent widget - which I need for .geometry(), only the offsets from the parent's parent. So far my code is:
# This is the child which appears when the button is pressed.
class ChildPopUpWindow(Frame):
def __init__(self, parentWgdt):
win = Toplevel(parentWgdt)
geom = str(parentWgdt.winfo_x()) + '+' + str(parentWgdt.winfo_y() + parentWgdt.winfo_height())
win.overrideredirect(1) # No win decoration.
win.bd = 10
win.relief = GROOVE
win.geometry( geom )
Frame.__init__(self, win)
# etc. etc.
# ... and this is the handler for the button being pressed.
def onDropDown(self):
popUp = ChildPopUpWindow(self)
This does pop up a window but relative to the desktop, not to the parent widget. It also seems to take no account of the border thickness and relief as far as I can see. Can anyone offer a way that this can be done? Is .geometry() the way to go or are there better ways?
The short answer is, use winfo_rootx and winfo_rooty to get the coordinates relative to the screen. And yes, wm_geometry is the way to place a toplevel window precisely.
For example:
x = parentWgdt.winfo_rootx()
y = parentWgdt.winfo_rooty()
height = parentWgdt.winfo_height()
geom = "+%d+%d" % (x,y+height)
As a bit of friendly advice, I recommend against abbrev var nms. It makes the code hard to read, especially when the abbreviation is wrong (Wgdt should at least be Wdgt). The difference in code size between geom and geometry, and Wgdt and Widget are tiny, but the difference in readability is huge.
According to Tk manual "https://www.tcl.tk/man/tcl8.4/TkCmd/winfo.htm#M52"
If you need the true width immediately after creating a widget, invoke update to force the geometry manager to arrange it, or use winfo reqwidth to get the window's requested width instead of its actual width.
# This code works perfectly
self.update()
self.geometry("+%d+%d" % (self.parent.winfo_rootx()+50,
self.parent.winfo_rooty()+50
)
)
to centring a modal window about a its parent window, I do so:
alto_modal = 100
ancho_modal = 250
alto_parent = parent.winfo_height()
ancho_parent = parent.winfo_width()
x = (ancho_parent - ancho_modal) // 2
y = (alto_parent - alto_modal) // 2
self.geometry('{}x{}+{}+{}'.format(ancho_modal, alto_modal, x, y))
Related
I have access to the top level tkinter window, but when I do:
self._root = tk.Tk()
<snip>
x = 10 # in screen coordinates
y = 20 # in screen coordinates
self._root.event_generate('<Button-1>', x=x, y=y)
self._root.event_generate('<ButtonRelease-1>', x=x, y=y)
I expect the button click to be applied to the widget underneath location x,y on the window. In this example a Button.
My understanding is event_generate places an event on the message queue inside tkinter just like a real mouse click would do. Normally clicking anywhere inside a window or a frame, causes the click to go through the top-level panes until it finds a widget with a bind() associated with it, i.e. a Button.
And so using that I should be able to simulate a button click anywhere on the window without moving the actual mouse.
But it doesn't do anything, no click, no error, no anything.
What am I missing?
event_generate is not the same as simulating a click by the user. Instead, you're explicitly specifying the window that is to receive the event.
If you need to simulate the actions of a user, you can use the method winfo_containing which will return the widget at a given coordinate. You can then pass that widget to event_generate.
With #Bryan Oakley's excellent help, I was able to get this to work:
# the top level window
self._root = tk.Tk()
<snip>
x = 10 # in screen coordinates
y = 20 # in screen coordinates
# get a reference to the widget in the root window that is
# underneath coordinate x,y
w = self._root.winfo_containing(x, y)
# Note: the <Enter> is necessary. Without it the
# button events don't do anything.
w.event_generate('<Enter>', x=x, y=y)
w.event_generate('<Button-1>', x=x, y=y)
w.event_generate('<ButtonRelease-1>', x=x, y=y)
# The invoke() also works.
# w.invoke()
Note: the widget in this sample is a Button. Here's a snippet of code for an Entry widget:
x = 30
y = 40
w = self._root.winfo_containing(x, y)
print(f'DBG {w.winfo_class()}') # should be 'Entry'
msg = 'abcd'
w.focus_set()
for ch in msg:
w.event_generate(f'<KeyPress-{ch}>')
w.update()
There is much more to be done here e.g. checking how special characters are done e.g. backspace, tab, enter key, ctrl and alt keys etc. But it's a start.
I have a python program that deploys a windows via graphics.py. The initial window opened by the GraphWin class opens in the top left corner of the screen. Subsequent calls to GraphWin cascade from the upper left to the lower right.
I'd like to control the placement of each window. (Example: Have all the windows open in a grid-layout so I can create a dashboard.)
I think there is no such method in graphics.py right now.
Ref: The Book and webpage.
If you want to stick to using graphics.py, I suggest creating a dashboard by dividing a single window into different slots.
This option does exist in Tkinter library. Please refer to this answer for more information on that.
graphics.py doesn't provide a way for you to control the location of instances of its GraphWin class. However the fact that it's built on top of Python's Tk GUI toolkit module named tkinter means that sometimes you can work around its limitations by looking at its source code to see how things operate internally.
For example, here's a snippet of code from the module (version 5.0) showing the beginning of GraphWin class' definition from the graphics.py file:
class GraphWin(tk.Canvas):
"""A GraphWin is a toplevel window for displaying graphics."""
def __init__(self, title="Graphics Window",
width=200, height=200, autoflush=True):
assert type(title) == type(""), "Title must be a string"
master = tk.Toplevel(_root)
master.protocol("WM_DELETE_WINDOW", self.close)
tk.Canvas.__init__(self, master, width=width, height=height,
highlightthickness=0, bd=0)
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.items = []
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self._onClick)
self.bind_all("<Key>", self._onKey)
self.height = int(height)
self.width = int(width)
self.autoflush = autoflush
self._mouseCallback = None
self.trans = None
self.closed = False
master.lift()
self.lastKey = ""
if autoflush: _root.update()
As you can see it's derived from a tkinter.Canvas widget which has an attribute named master which is a tkinter.Toplevel widget. It then initializes the Canvas base class and specifies the newly created Toplevel window as its parent.
The size and position of a Toplevel window can be controlled by calling its geometry() method as described in the linked documentation. This method expects to be passed a "geometry string" argument in a certain format ('wxh±x±y').
This mean you can take advantage of how this implementation detail in order to put it anywhere you want it and as well as resize if desired.
Here's an example of doing that:
from graphics import *
def main():
win = GraphWin("My Circle", 100, 100)
# Override size and position of the GraphWin.
w, h = 300, 300 # Width and height.
x, y = 500, 500 # Screen position.
win.master.geometry('%dx%d+%d+%d' % (w, h, x, y))
c = Circle(Point(50,50), 10)
c.draw(win)
win.getMouse() # pause for click in window
win.close()
if __name__ == '__main__':
main()
My desktop while script is running:
I have currently got a PyQt5 application which is quite simple (just one button). I'd like for it to have vibrancy, so I've ported ObjC vibrancy code to Python. My vibrancy code is as follows:
frame = NSMakeRect(0, 0, * self.get_screen_size()) # get_screen_size returns the resolution of the monitor
view = objc.objc_object(c_void_p=self.winId().__int__()) # returns NSView of current window
visualEffectView = NSVisualEffectView.new()
visualEffectView.setAutoresizingMask_(NSViewWidthSizable|NSViewHeightSizable) # equivalent to: visualEffectView.autoresizingMask = NSViewW...
visualEffectView.setFrame_(frame)
visualEffectView.setState_(NSVisualEffectStateActive)
visualEffectView.setMaterial_(NSVisualEffectMaterialDark)
visualEffectView.setBlendingMode_(NSVisualEffectBlendingModeBehindWindow)
window = view.window()
window.contentView().addSubview_positioned_relativeTo_(visualEffectView, NSWindowBelow, None)
# equal to: [window.contentView addSubview:visualEffectView positioned:NSWindowBelow relativeTo:nul]
window.setTitlebarAppearsTransparent_(True)
window.setStyleMask_(window.styleMask() | NSFullSizeContentViewWindowMask) # so the title bar is also vibrant
self.repaint()
All I'm doing to draw a button is: btn = QPushButton("test", self)
self is a class inherited from QMainWindow and everything else should be fairly unimportant.
Behaviour with the window.contentView().addSubview... line commented out (no vibrancy)
Behaviour without it commented out (with vibrancy)
Thanks!
The UI elements are actually drawn, but the NSVisualEffectView is covering them.
I fixed that issue by adding another view QMacCocoaViewContainer to the window.
You'll also need to set the Window transparent, otherwise the widgets will have a slight background border.
If you use the dark vibrant window also make sure to set appearance correctly, so buttons, labels etc. are rendered correctly.
frame = NSMakeRect(0, 0, self.width(), self.height())
view = objc.objc_object(c_void_p=self.winId().__int__())
visualEffectView = NSVisualEffectView.new()
visualEffectView.setAutoresizingMask_(NSViewWidthSizable|NSViewHeightSizable)
visualEffectView.setWantsLayer_(True)
visualEffectView.setFrame_(frame)
visualEffectView.setState_(NSVisualEffectStateActive)
visualEffectView.setMaterial_(NSVisualEffectMaterialUltraDark)
visualEffectView.setBlendingMode_(NSVisualEffectBlendingModeBehindWindow)
visualEffectView.setWantsLayer_(True)
self.setAttribute(Qt.WA_TranslucentBackground, True)
window = view.window()
content = window.contentView()
container = QMacCocoaViewContainer(0, self)
content.addSubview_positioned_relativeTo_(visualEffectView, NSWindowBelow, container)
window.setTitlebarAppearsTransparent_(True)
window.setStyleMask_(window.styleMask() | NSFullSizeContentViewWindowMask)
appearance = NSAppearance.appearanceNamed_('NSAppearanceNameVibrantDark')
window.setAppearance_(appearance)
Result:
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()
I am trying to make an "Office-like" TextView. That is:
The TextView itself has a fixed width (so it kinda shows what the text would look like on a sheet of paper)
If the window (on which the TextView is packed) is smaller than the fixed width: The TextView should be scrollable
If the window is bigger, add margins to the left/right to keep the fixed width
This is what i came up with, and it actually behaves like it should, except that it doesn't scroll if your cursor gets out of the viewport, when you for example write a line that needs more space than the windows current width.
What would be the best way to keep the viewport "in sync"? Do I have to create a custom Viewport?
Thanks in advance!
#!/usr/bin/env python2
# encoding: utf-8
import gtk
class SheetTextView(gtk.TextView):
WIDTH = 700
def __init__(self):
gtk.TextView.__init__(self)
self.set_wrap_mode(gtk.WRAP_WORD)
self.set_size_request(self.WIDTH, -1)
self.connect('size-allocate', self._on_size_allocate)
def _on_size_allocate(self, widget, event, data=None):
# Reset left/right margin to simulate a fixed line width
x, y, width, height = self.get_allocation()
if width > self.WIDTH:
margin = (width - self.WIDTH) / 2
self.set_left_margin(margin)
self.set_right_margin(margin)
if __name__ == "__main__":
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.connect('delete_event', gtk.main_quit)
view = SheetTextView()
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scroll.add_with_viewport(view)
window.add(scroll)
window.show_all()
gtk.main()
You mean something like this?
Dropbox link
Please note this is just a test, all it does for now is change the size when necessary,
is that what you meant?
If so, please tell me and I'll fix the bugs and improve.
EDIT: Please note this is just scratch code, messy coded...