I'm trying to use gtk.window.get_size(), but it always just returns the default width and height. The documentation says
The get_size() method returns a tuple containing the current width and
height of the window. If the window is not on-screen, it returns the
size PyGTK will suggest to the window manager for the initial window
size. The size obtained by the get_size() method is the last size
received in a configure event, that is, PyGTK uses its locally-stored
size, rather than querying the X server for the size. As a result, if
you call the resize() method then immediately call the get_size()
method, the size won't have taken effect yet. After the window manager
processes the resize request, PyGTK receives notification that the
size has changed via a configure event, and the size of the window
gets updated.
I've tried resizing the window manually and waiting a minute or so, but I still get the default width and height.
I'm trying to use this to save the window size on quit so that I can restore it on start. Is there a better way to do this?
Here's the code snipit I have for my main quit.
def on_main_window_destroy(self, widget, data=None):
if self.view.current_view.layout == 'list':
self.view.current_view.set_column_order()
width = self.main_window.get_size()[0]
height = self.main_window.get_size()[1]
#test statement
print (width, height)
self.prefs.set_size_prefs(width, height)
self.prefs.set_view_prefs(self.view.current_view.media, self.view.current_view.layout)
gtk.main_quit()
I think I understand what's happening now. This is inside the destroy signal, so by the time the code gets called, the window is already gone. Is there a more canonical way of handling window resizing? I was hoping to avoid handling resize events everytime the user resized the window.
This seems to fix your problem:
import gtk
def print_size(widget, data=None):
print window.get_size()
def delete_event(widget, data=None):
print window.get_size()
return False
def destroy(widget, data=None):
gtk.main_quit()
window = gtk.Window()
window.connect('delete_event', delete_event)
window.connect('destroy', destroy)
button = gtk.Button(label='Print size')
button.connect('clicked', print_size)
window.add(button)
window.show_all()
gtk.main()
I think the key is calling get_size on the delete_event signal rather than the destroy signal. If you do it on the destroy signal, it's like you describe, it just returns the default size.
Try running:
while gtk.events_pending():
gtk.main_iteration_do(False)
right before calling window.get_size()
Related
I started to learn PyGlet (Python Framework for games). I made default window, but when I slide it in the edge of the monitor, it looks something like this:
import pyglet
window = pyglet.window.Window(800, 600, "PyGlet Window")
pyglet.app.run()
What's the problem? Thanks for all support!
This has to do with buffers and how windows handle windows that are outside of the screen. IIRC they get optimized away and the buffer or area of the window will not be automatically cleared. And if you don't clear it or no event triggers a re-render, whatever is stuck in the buffer will be what's visible.
Easiest solution around this is to manually call .clear() the window object:
import pyglet
window = pyglet.window.Window(800, 600, "PyGlet Window")
def callback(dt):
print('%f seconds since last callback' % dt)
#window.event
def on_draw():
window.clear()
window.flip() # Not necessary, but probably doesn't hurt
pyglet.clock.schedule_interval(callback, 0.5) # called twice a second
pyglet.app.run()
Any time you cause an event, the draw function should be triggered, and if your on_draw contains window.clear() it should clear any old artifacts.
Disclaimer: In all likelihood this could very well be an XY problem, I'd appreciate if you would point me to the correct direction.
Goal: I have a file that I'd like tkinter to read and then create widgets dynamically based on the file contents. I want the window to always open centered at my cursor.
For the sake of MCVE let's consider a plain text file that reads:
Madness?
This
IS
T K I N T E R
And tkinter should create 4 label widgets, with the main window resized to fit. That's very simple... until the file is encrypted. Before I can read the encrypted file, I need to askstring() for a password first (MCVE: let's also assume we're only asking for the key here).
My issues:
The askstring Dialog is always at a default position. I know it's supposed to be relative to the parent (root)...
But I can't set geometry() before I create the widgets or else it won't resize to the widgets...
And I can't pre-determine the size required for geometry() since I can't open the file without the password (key)...
Here's a MCVE sample of my most successful attempt:
import tkinter as tk
from tkinter.simpledialog import askstring
from cryptography.fernet import Fernet
class GUI(tk.Tk):
def __init__(self):
super().__init__()
# side note: If I don't withdraw() the root first, the dialog ends up behind the root
# and I couldn't find a way to get around this.
self.withdraw()
self.create_widgets()
# Attempt: I tried doing this instead of self.create_widgets():
# self.reposition()
# self.after(ms=1,func=self.create_widgets)
# The dialog is positioned correctly, but the window size doesn't resize
self.deiconify()
# I have to do the reposition AFTER mainloop or else the window size becomes 1x1
self.after(ms=1, func=self.reposition)
self.mainloop()
def get_key(self):
return askstring('Locked','Enter Key', show='*', parent=self)
def create_widgets(self):
self.lbls = []
with open('test2.txt', 'rb') as file:
encrypted = file.read()
key = self.get_key()
suite = Fernet(key)
self.data = suite.decrypt(encrypted).decode('utf-8')
for i in self.data.split('\n'):
self.lbls.append(tk.Label(self, text=i.strip()))
self.lbls[-1].pack()
def reposition(self):
width, height = self.winfo_width(), self.winfo_height()
self.geometry(f'{width}x{height}+{self.winfo_pointerx()-int(width/2)}+{self.winfo_pointery()-int(height/2)}')
gui = GUI()
Which achieves (that I can live with):
✓ Correct size based on file content
✓ Root positioned at center of cursor
⨯ Prompts for key at center of cursor
My questions are:
Is it possible to perform an auto-resize on the root again based on the widgets function similar to pack_propagate() after geometry() is set? It seems once geometry() is set the root won't propagate no more.
If not, how can I manually resize it in code? I tried retrieving the total heights of the widgets height = sum([i.winfo_reqheight() for i in self.lbls]) but height just becomes 0. But when I print(self.lbls[-1].winfo_reqheight()) in the self.create_widgets() it returns 26 each, and they actually print after my self.reposition() call, which is weird.
Failing that, is it possible to position the askstring() dialog prior to the the widgets being created?
I'm stumped. I feel like I'm going about this the wrong way but I'm not sure what is the correct way to handle this situation to break the cycle of dependency.
To help with reproducing here's the encrypted string as well as the key:
Data:
gAAAAABb2z3y-Kva7bdgMEbvnqGGRRJc9ZMrt8oc092_fuxSK1x4qlP72aVy13xR-jQV1vLD7NQdOTT6YBI17pGYpUZiFpIOQGih9jYsd-u1EPUeV2iqPLG7wYcNxYq-u4Z4phkHllvP
Key:
SecretKeyAnyhowGoWatchDareDevilS3ItsAmazing=
Edit: Based on #BryanOakley's answer here I can invoke self.geometry("") to reset, however it goes back to the native 200x200 size, and still doesn't propragate the widgets.
After a dozens more tries I think I got it working, here are the relevant parts I modified:
def __init__(self):
super().__init__()
self.withdraw()
# reposition the window first...
self.reposition()
# For some reason I can't seem to figure out,
# without the .after(ms=1) the dialog still goes to my top left corner
self.after(ms=1, func=self.do_stuff)
self.mainloop()
# wrap these functions to be called by .after()
def do_stuff(self):
self.create_widgets()
self.deiconify()
def reposition(self):
width, height = self.winfo_width(), self.winfo_height()
self.geometry(f'{width}x{height}+{self.winfo_pointerx()-int(width/2)}+{self.winfo_pointery()-int(height/2)}')
# reset the geometry per #BryanOakley's answer in the linked thread
# Seems it resets the width/height but still retains the x/y
self.geometry("")
It would appear #BryanOakley have answers for all things tkinter.
I'm still waiting to see if there will be others chiming in on the subject and will be happy to accept your answer to provide some clarity on the subject.
I'm trying to use Python to programmatically save a QWidget in PyQt4 as an image (any format would be fine - PNG, PDF, JPEF, GIF...etc)
I thought this would be very straightforward, but I actually wasn't able to find anything on the web about it. Can someone point me in the right direction?
To be clear, I'm trying to do this
gui = <SOME QMainWindow>
gui.show() # this displays the gui. it's useful, but what i need is to save the image
gui.save("image.png") ## How do I do this?
You can do this using the QPixmap.grabWindow() method.
From the docs:
Grabs the contents of the window window and makes a pixmap out of it.
Returns the pixmap.
The arguments (x, y) specify the offset in the window, whereas (w, h)
specify the width and height of the area to be copied.
If w is negative, the function copies everything to the right border
of the window. If h is negative, the function copies everything to the
bottom of the window.
Note that grabWindow() grabs pixels from the screen, not from the
window. If there is another window partially or entirely over the one
you grab, you get pixels from the overlying window, too.
Note also that the mouse cursor is generally not grabbed.
The reason we use a window identifier and not a QWidget is to enable
grabbing of windows that are not part of the application, window
system frames, and so on.
Warning: Grabbing an area outside the screen is not safe in general.
This depends on the underlying window system.
Warning: X11 only: If window is not the same depth as the root window
and another window partially or entirely obscures the one you grab,
you will not get pixels from the overlying window. The contests of the
obscured areas in the pixmap are undefined and uninitialized.
Sample code:
import sys
from PyQt4.QtGui import *
filename = 'Screenshot.jpg'
app = QApplication(sys.argv)
widget = QWidget()
widget.setLayout(QVBoxLayout())
label = QLabel()
widget.layout().addWidget(label)
def take_screenshot():
p = QPixmap.grabWindow(widget.winId())
p.save(filename, 'jpg')
widget.layout().addWidget(QPushButton('take screenshot', clicked=take_screenshot))
widget.show()
app.exec_()
This will produce a window that looks like this:
When you click the button, it will create a file named Screenshot.jpg in the current directory. Said image will look like this (notice the window frame is missing):
I know in Tkinter that the "<Configure>" event handles size changes in a window. However, I need to distinguish between when a user hits the maximize button, the restore button and when the user is resizing the window (instead of all three at once). Any ideas on how to do this? Is there a standard way? For instance, when a user hits maximize, I want to execute my code to maximize. When the user hits restore, I want to execute different code to restore. When the user drags to resize (or uses the keyboard shortcut to do so) I want it to execute different code altogether.
I can't think of a built-in way to track these events, but you could use the state() method on your root window to track changes. You can check the returned values of state(), specifically normal and zoomed (looks like Windows and OSX only), and call your own methods to handle the resize type based off those values. Here's a example to clarify:
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
# initialize the new_state
self.new_state = 'normal'
self.parent.bind('<Configure>', self._resize_handler)
def _resize_handler(self, event):
self.old_state = self.new_state # assign the old state value
self.new_state = self.parent.state() # get the new state value
if self.new_state == 'zoomed':
print('maximize event')
elif self.new_state == 'normal' and self.old_state == 'zoomed':
print('restore event')
else:
print('dragged resize event')
root = Tk()
App(root).pack()
root.mainloop()
If you want to distinguish between dragging the window and dragging to resize, you'll have to add some extra checks, maybe storing the size before <Configure> and the size after, with winfo_height() and winfo_width(), and if no change occurs, you know the window was only repositioned.
Hope that helps.
I am creating a tkinter canvas, and I need to check for when the user changes the size of the window. The problem is, initially the window apparently isn't the size it's supposed to be. I have the following:
def print_size(self):
print self.root.winfo_width()
def init_simulation(self, size=300):
self.root = Tk()
canvas = Canvas(self.root, width=size, height=size)
self.print_size()
self.root.after(1000, self.print_size)
When running this I get:
1
and a second later:
306
Ignoring the fact that tkinter will add 6 pixels, why is the size first 1 and then 306? Am I setting it up wrong?
When you instantiate a root widget with Tk(), Tkinter starts a process in a separate thread to actually create the window - it doesn't happen in the main loop.
The reason you get a 1 for the size initially is that the root window doesn't exist yet when you call self.print_size the first time, so it gives you a default value of 1. The next time you call it a second later, the window has finished spawning, so it gives the the actual size. It's essentially a race condition - the main event loop gets to the print self.root.winfo_width() before self.root is done being created.
If you'd like to change this behavior, add this line right after self.root = Tk():
self.root.wait_visibility(self.root)
That command forces the main event loop to pause until the given widget (in this case the root window) has been spawned and is visible.
Also, note that you've set the size of the canvas to 300 pixels, so naturally the container window will have some extra width.