I have a program that will show an image on screen when a hotkey is pressed on my keyboard. Depending on which key was pressed a different image will be shown.
After 3 seconds of no input my root Tk gets withdraw()n so there is nothing on screen.
Now, whenever I want to show an image I call this function:
def drawOverlay(self, index):
self.canvas.itemconfig(self.overlayWidget, image=self.overlayImages[index])
self.deiconify()
The problem is that I see the old image for a few milliseconds before it is replaced.
I tried to find out why there is the delay eventhough I deiconify() only after i switched the image and came across this answer which suggested to call deiconify() using the root's after(). I tried that and when it still didn't work I played around with the call and found out that calling
self.after(1000, self.deiconify())
causes the old image to appear for exactly 1 second after which it is replaced with the new one.
This leads me to believe that the Tk or the Canvas is unable to update while it is withdraw()n. Is there any way around this? I'd rather have a short delay before the image is displayed than the old image flashing on the screen for a few frames but I have no idea how to accomplish this.
Edit: So I'm an idiot for actually calling deiconify instead of just passing it to after because of the parantheses.
That does fix the old image appearing for the duration but does not fix the flashing.
Edit2: I managed to reproduce this problem with the following code.
Pressing any key on the keyboard will make a green rectagle appear. Waiting for it to vanish and then pressing another key will make a red rectangle appear.
Only sometimes can you see the flashes happening so try for a couple times. I did not manage to reproduce when -transparentcolor isnt set but I can't tell if this is due to the option being the problem or due to the reduced rendering times making the problem almost imperceptable. It is also a bit easier to see then overrideredirect is set.
Edit3: I have worked the code down further by using actual images. Even without the keypress or additional event callbacks this code produces a black flash before displaing the image. -transparentcolor and overrideredirect(True) seem to be vital to reproducing this. Manually calling update() before reduces the frequency of this ocurring but still causes flashing consistently on larger images. This points to rendering time being one of the factors.
overlay.png
overlayRaw.png
from tkinter import Tk, Canvas
from PIL import ImageTk, Image
TRANSCOLOR = "blue"
IMAGE_SMALL = "overlay.png"
IMAGE_LARGE = "overlayRaw.png"
class Overlay(Tk):
def __init__(self):
Tk.__init__(self)
self.rawImage = Image.open(IMAGE_SMALL)
self.image = ImageTk.PhotoImage(self.rawImage)
self.canvas = Canvas(self, width = self.rawImage.size[0], height = self.rawImage.size[1])
self.canvas.pack()
self.wm_attributes('-transparentcolor', TRANSCOLOR) # If disabled stops the flashes from ocurring even on large images.
self.overrideredirect(True) # If disabled the Windows animation for opening windows plays. Stops the flashing from ocurring
self.withdraw()
self.overlayWidget = self.canvas.create_image(0, 0, image = self.image, anchor = "nw")
self.deiconify() # Flashes Clearly Everytime
## self.update_idletasks()
## self.deiconify() # Only Flashes Sometimes. Always flashes on large images
## self.update()
## self.deiconify() # Only Flashes Sometimes. Always flashes on large images
## self.after(0, self.deiconify) # Flashes Clearly everytime
## self.after(200, self.deiconify) # Only Flashes Sometimes. Always flashes on large images
## self.update()
## self.after(200, self.deiconify) # Flashes Clearly Everytime
o = Overlay()
o.mainloop()
I don't see much that could cause a flicker. My recommendation would be to add an explicit call to self.update before the call to deiconify happens. That should instruct tkinter to redraw everything on the canvas. However, that won't help if tkinter on your platform defers drawing until the window is mapped.
Try replacing this:
self.after(0,self.deiconify)
self.after(2000, self.hideOverlay)
with this:
self.update()
self.deiconify()
Related
I am writing a python program that gradually changes an image step by step, adjusting each pixel by a small amount in each step. To get a visualization of what the program is doing during runtime, I want it to display the image at each step, always overwriting the currently shown image so that it doesen't open bunch of display windows.
I already tried matplotlib, opencv and skimage, with their according possibilities to display an image and update the frame content in the course of the program:
# using scimage
viewer = ImageViewer(image)
viewer.show(main_window=False) # set the parameter to false so that the code doesn't block here but continues computation
..other code..
viewer.update_image(new_image)
# using matplotlib
myplot = plt.imshow(image)
plt.show(block=False)
.. other code..
myplot.set_data(new_image)
plt.show()
# using opencv
cv2.imshow('image',image)
.. other code ..
cv2.imshow('image', new_image)
I always ran into the problem that when it was supposed to open a frame with an image, it did not display the image but only a black screen. Weirdly enough, when I ran the code in IntelliJ in debug-mode and hit a breakpoint after the display-function, it worked.
What can I do so that it is displayed correctly when running the program normally and not with a breakpoint?
Here's the thing, I think your program does work, except it does and finishes unless you tell it to pause, which is why your breakpoint strategy is working.
Try pausing after showing image -
You can ask for user input. It'll pause until you enter some input to the program.
Put the program thread to sleep for some specified amount of time. This'll freeze your program for some given specified time, but you'll be able to see the image if it's already rendered.
Edit -
Since opencv's waitKey method is working for you now, you can use this method again to prevent the program from closing image window. Use waitKey(0) as your last program statement. It waits for a key press indefinitely, and returns the pressed key's code. Press any key to continue (but remember to have your image window in focus or it won't work), and your program should close if it's used in the end.
Also, I've striked earlier suggested options for pausing a program, because I'm unsure if it would've helped. I think waitKey method is more complex, and helps pause the program without freezing it.
Well, I am still not sure what exactly your goal is but here is a code piece that modifies an image inside of a window whenever the upper button is pressed.
from tkinter import Tk, Canvas, Button, Label, PhotoImage, mainloop
import random
WIDTH, HEIGHT = 800, 600
def modify_image():
print ("modifiying image...")
for x in range(1000):
img.put ( '#%06x' % random.randint(0, 16777215), # 6 char hex color
( random.randint(0, WIDTH), random.randint(0, HEIGHT) ) # (x, y)
)
canvas.update_idletasks()
print ("done")
canvas = Canvas(Tk(), width=WIDTH, height=HEIGHT, bg="#000000")
canvas.pack()
Button(canvas,text="modifiying image",command=modify_image).pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
Label(canvas,image=img).pack()
mainloop()
The function modify_image() adds 1000 random pixels to the image within the main window. Note the tkinter module is a default python module.
Okay, so i am using tkinter to make a GUI, i feel like the top bar looks kinda ugly so that's why i was planning to remove it and put some nice icons. I used this line of code to remove the top bar gui.overrideredirect(True) and it worked :D, but you can't move the program around the screen (it sticks to the top-left corner of the screen). Is there any other way i can do this without sticking my program to the corner? Thanks.
import requests
import tkinter as tk
#GUI Config
gui = tk.Tk()
gui.geometry('970x569')
gui.title("Zombs Royale Tools v1")
gui.resizable(False, False)
#Code below is the one i used to do this[![enter image description here][1]][1]
gui.overrideredirect(True)
#Background-image
bgImage = tk.PhotoImage(file="width.png")
background_label = tk.Label(gui, image = bgImage)
background_label.place(x=0,y=0,relwidth=1, relheight=1)
#GUI Widgets#
#Exit Button
exit_image = tk.PhotoImage(file="close.gif")
exit_button = tk.Button(gui, image=exit_image, borderwidth=0, command=gui.destroy)
exit_button.place(rely=0.01, relx=0.01)
Removing the titlebar is done with override redirect. However, this removes every window function as well. That includes moving the window around and resizing it.
This means with overrideredirect you can skip the
gui.resizable(False, False)
One thing you can do is provide more info for the window’s geometry:
gui.geometry('970x569+600+500')
The 2 last numbers provide the info regarding the positioning of your window on the desktop, in case you don’t want it to be spawned on the top left corner.
If you want to make the window movable you will have to integrate your own code. It has been discussed here before.
Eg.
here
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 want to change a label's text colour, wait a few seconds, then change it back when I press a key.
My end goal is making a full onscreen keyboard that will highlight the key that you pressed. However I can't get the function to pause between turning text blue, then back to black. I attempted using time.sleep(2), but it appears to do that at the start of the function, as opposed to the order I wrote it in.
from tkinter import *
import time
window = Tk()
window.geometry("1000x700")
LabQ = Label(window,text="Q",font=("Courier", 30))
LabQ.place(x=210,y=260)
def key(event):
LabQ = Label(window,text="Q",fg="ROYALBLUE",font=("Courier", 30))
LabQ.place(x=210,y=260)
time.sleep(2)
LabQ = Label(window,text="Q",font=("Courier", 30))
LabQ.place(x=210,y=260)
window.bind("<key>", key)
window.mainloop()
You have two problems. One is that you're not changing the color, you're creating an entirely new widget. To change the color you need to use the configure method on an existing widget.
Second, when you call sleep that's exactly what the GUI does -- it sleeps. No code is running and the screen can't be refreshed. As a general rule of thumb, a GUI should never call sleep.
The solution is to use use after to schedule the change for some point in the future:
def key(event):
bg = LabQ.cget("background")
LabQ.configure(background="royalblue")
LabQ.after(2000, lambda color=bg: LabQ.configure(background=color))
This example doesn't gracefully handle the case where you type the same key twice in under two seconds, but that's unrelated to the core issue of how to change the value after a period of time has elapsed.
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...