I have a tkinter application, with a text widget in it.
The I tried to add a paste function:
def paste(self):
self.text.insert('insert', self.text.clipboard_get())
But when the clipboard has no content, it pastes None.
I tried using a if condition to fix this:
if self.text.clipboard_get() == 'None':
...
It fixes some errors, but when the clipboard has 'None', it won't paste.
So how can I fix this?
Thanks for any help!
Here clipboard_get() with an empty clipboard raise an error.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
text = tk.Text()
text.clipboard_clear()
try:
clip_text = text.clipboard_get()
print(clip_text)
except tk.TclError:
print('clipboard is empty')
Related
Is there a way to change WM_CLASS of showinfo (and other dialogs)? className, class_ parameters do the job for Tk, Toplevel.
import tkinter as tk
from tkinter.messagebox import showinfo
if __name__ == '__main__':
root = tk.Tk(className='ymail')
mail_client = tk.Toplevel(root, class_='ymail')
new_message = tk.Toplevel(root, class_='ymail')
showinfo(title="Cancel sending", parent=new_message, message="""
Send is cancelled due to empty message""")
root.mainloop()
For showinfo dialog
$ xprop WM_CLASS
gives
WM_CLASS(STRING) = "__tk__messagebox", "Dialog"
I think it is convenient to cycle tkinter windows with Alt-~ (Tilde), for which their WM_CLASS shall be the same.
I did the search ("tkinter change WM_CLASS showinfo"). Some of the hits are not applicable, some don't work (xdotool), and some I'd rather use as a last resort (converting C program to python).
Using
Debian 10
python 3.7.3
GNOME 3.30.1
EDIT
Added workaround (using xdotool)
import threading
import subprocess
import time
import tkinter as tk
from tkinter.messagebox import showinfo
def change_dialog_class(from_="Dialog", to_="ymail"):
cmd = f"xdotool search --class {from_} set_window --class {to_}"
time.sleep(1)
subprocess.run(cmd.split())
if __name__ == '__main__':
root = tk.Tk(className='ymail')
mail_client = tk.Toplevel(root, class_='ymail')
new_message = tk.Toplevel(root, class_='ymail')
tk.Frame.class_ = 'ymail'
threading.Thread(target=change_dialog_class, args=("Dialog", "ymail"),
daemon=True).start()
showinfo(title="Cancel sending", parent=new_message,
message="""Send is cancelled due to empty message""")
root.mainloop()
along with ymail.desktop it works
$ cat ~/.local/share/applications/ymail.desktop
[Desktop Entry]
Type=Application
Terminal=false
Name=ymail
Icon=python
StartupWMClass=ymail
yet, the python solution would be better
Since I'm not a XSystem user it took me some time to follow up. It
seems like that you are looking for wm_group and unfortunately it isnt
possible without subclassing it, which results in pretty much the same
as writing your own class with tk.Toplevel. Anyway I hope
toplevel.wm_group(root) ease things out and works for you.
After I noticed that the SimpleDialog may has some functionality that you want to keep and can be hard to code for yourself, I decided to write an answer that you may want to use. It also provides the class_ option in case wm_group dosent work for you.
Here is the code:
import tkinter as tk
import tkinter.simpledialog as simpledialog
class MessageBox(simpledialog.SimpleDialog):
def __init__(self, master,**kwargs):
simpledialog.SimpleDialog.__init__(self,master,**kwargs)
#root.tk.call('wm', 'group', self.root._w, master)
def done(self,num):
print(num)
self.root.destroy()
root = tk.Tk()
MessageBox(root,title='Cancel',text='Im telling you!',class_='ymail',
buttons=['Got it!','Nah'], default=None, cancel=None)
root.mainloop()
and here is the source:
https://github.com/python/cpython/blob/main/Lib/tkinter/simpledialog.py#L31
In a script, that I am writing I need a button with a little trash bin as an icon on it. I use the code shown below:
# Python 3.7.1
import tkinter as tk
master = tk.Tk()
photo = tk.PhotoImage(file="bin.png")
icon_button = tk.Button(master, image=photo)
icon_button.pack()
The following error occurs:
_tkinter.TclError: image "pyimage1. doesn't exist
Since I specified bin.png as the image file, I cannot really understand how pyimage1 is specified in the error.
After some debugging, I realized, that the PhotoImage returns the string pyimage1, and therefore gives "pyimage1" as a parameter to the Button, but I still don't know how to fix my issue.
The problem is that Relative path won't be accepted, i.e. if you have your bin.png in C:\ then you should do as-
tk.PhotoImage(file='C:\\bin.png')
Now, if you still want to use relative paths then the following will do-
import tkinter as tk
import os
Win = tk.Tk()
Img = tk.PhotoImage(file=os.path.abspath('bin.png')
tk.Button(Win, image=Img).pack()
Win.mainloop()
Or use this-
import sys, os
def get_path(file):
if not hasattr(sys, ''):
file = os.path.join(os.path.dirname(__file__), file)
return file
else:
file = os.path.join(sys.prefix, file)
return file
It simply just gets the full path of a file.
Now, use the function-
...file=get_path('bin.png'))
I want to make a simple GUI that will allow user to pick an excel file to load the data from that will later be used for math calculations. Both modules work correctly when separate. But when I try to use my GUI as an import into my main file I am unable to access the needed var without calling the class method which in turn iterates it. It is troublesome because it is supposed to use this function only after the button press.
Is there something fundamentally wrong that I am doing?
GUI script
import tkinter as tk
import tkinter.filedialog as tkf
class TestClass():
def __init__(self, master):
frame = tk.Frame(master)
frame.pack()
self.dialogButton = tk.Button(frame, text="choose", command=self.chooseFile)
self.dialogButton.pack(side=tk.BOTTOM)
def chooseFile(self):
global filename
filename = tkf.askopenfilename()
print(filename)
Import script
import tkinterTest as tt
import tkinter as tk
root = tk.Tk()
classObject = tt.TestClass(root)
var = classObject.chooseFile()
print(var)
root.mainloop()
I want to access the path string only with the GUI so that it gives me the string only after I press the button to select the file not right after the program starts.
The following code produces an app with a single Entry widget. When run on MacOS using Python 3.7.3 from Homebrew, pressing the up or down arrow while inside the entry box causes a character 0xF701 to be inserted:
import tkinter as tk
root = tk.Tk()
app = tk.Frame(master=root)
app.pack()
entry = tk.Entry(app)
entry.pack()
app.mainloop()
This doesn't happen with Anaconda Python and I haven't been able to find anyone else having this issue.
By binding print to the up and down events I've been able to see that the character associated with these events is indeed 0xF700 and 0xF701.
entry.bind('<Down>', print)
entry.bind('<Up>', print)
Output after pressing up and down:
<KeyPress event state=Mod3|Mod4 keysym=Up keycode=8320768 char='\uf700' delta=8320768 x=-5 y=-50>
<KeyPress event state=Mod3|Mod4 keysym=Down keycode=8255233 char='\uf701' delta=8255233 x=-5 y=-50>
With the Anaconda Python version the output is slightly different:
<KeyPress event state=Mod3|Mod4 keysym=Up keycode=8320768 char='\uf700' x=-5 y=-50>
<KeyPress event state=Mod3|Mod4 keysym=Down keycode=8255233 char='\uf701' x=-5 y=-50>
Does anyone know of a simple solution to this problem?
Can validating the Entry help? The code below validates that the resulting string in Entry only contains characters in valid_chars. A more complex validation rule could be written if required.
import tkinter as tk
import re
valid_chars = re.compile(r'^[0-9A-Za-z ]*$') # Accept Alphanumeric and Space
class ValidateEntry(tk.Entry):
def __init__(self, parent, regex):
self.valid = regex
validate_cmd = (parent.register(self.validate),'%P') # %P pass the new string to validate
super().__init__( parent, validate = 'key', validatecommand = validate_cmd)
# validate = 'key' runs the validation at each keystroke.
def validate(self, new_str):
if self.valid.match(new_str): return True
return False
def do_key(ev):
print(ev.widget, ev, entry.get())
root= tk.Tk()
root.title("Validation")
fram = tk.Frame(root)
fram.grid()
entry = ValidateEntry(fram, valid_chars)
entry.grid()
entry.bind('<Down>', do_key)
entry.bind('<Up>', do_key)
root.mainloop()
This may be overkill but should work across all the platforms.
The release why you are getting those unknown characters in an Entry widget is because for some reason char codes of "Up" (\uf700) and "Down" (\uf701) arrows prints when run from homebrew python but not with anaconda python not sure why is that.
You can try and see yourself by running this code with either of them.
root = Tk()
E = Entry(root)
E.bind('<Key>', lambda e: print(e.char))
E.pack()
root.mainloop()
The solution I come up with is to overwrite the main <Key> bind of Entry widget to ignore "Up" and "Down" arrows.
import tkinter as tk
class Entry(tk.Entry):
def __init__(self, master=None, cnf={}, **kw):
super(Entry, self).__init__(master=master, cnf=cnf, **kw)
self.bind_class('Entry', '<Key>', self.add_char)
def add_char(self, evt):
if evt.char != '\uf701' and evt.char != '\uf700':
self.insert('insert', evt.char)
self.xview_moveto(1)
if __name__ == "__main__":
root = tk.Tk()
E = Entry(root)
E.pack()
root.mainloop()
I just installed PyCharm and opened up a script I had been using in IDLE that did some string manipulation then copied it to the clipboard, but it doesn't work when I run it in PyCharm.
from tkinter import Tk
r = Tk()
r.withdraw()
r.clipboard_clear()
r.clipboard_append("test")
r.destroy()
When I run this in IDLE I am able to paste "test" after, but in PyCharm it just says "Process finished with exit code 0" but there is nothing in the clipboard (even if there was before running). I have Python 3.5 as the selected interpreter.
There seems to be problem if the clipboard is manipulated and the program closes too quickly soon after. The following program worked for me but was unreliable when the call to root.after only used one millisecond for the delay. Other possibilities were tried, but code down below should work:
import random
import string
import tkinter
def main():
root = tkinter.Tk()
root.after_idle(run_code, root)
root.after(100, root.destroy)
root.mainloop()
def run_code(root):
root.withdraw()
root.clipboard_clear()
root.clipboard_append(''.join(random.sample(string.ascii_letters, 10)))
print('Clipboard is ready.')
if __name__ == '__main__':
main()
The following is a mildly more useful version of the program and demonstrates that you can make many calls to root.after_idle to run your code in a sequential manner. Its design is primarily for use to process command-line arguments and send them to your clipboard for you:
import sys
import tkinter
def main(argv):
root = tkinter.Tk()
root.after_idle(root.withdraw)
root.after_idle(root.clipboard_clear)
root.after_idle(root.clipboard_append, ' '.join(argv[1:]))
root.after_idle(print, 'The clipboard is ready.')
root.after(100, root.destroy)
root.mainloop()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))