Tkinter copy to clipboard not working in PyCharm - python-3.x

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))

Related

Change WM_CLASS of Dialog (tkinter)

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

Why is Python Skipping Over Tkinter Code?

I'm in Python 3.x using Tkinter to make a button that changes a boolean variable's value from true to false then a if statement to check if that value is false. Here is my code for that:
import tkinter
import time
x = True
top = tkinter.Tk()
def helloCallBack():
x = False
print (x)
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
if x == False:
print ('Hello')
top.mainloop()
This unfortunately didn't work, so I replaced the if statement with a time.sleep(10) and then print (x) so I would have enough time to press the button like so:
import tkinter
import time
x = True
top = tkinter.Tk()
def helloCallBack():
x = False
print (x)
B = tkinter.Button(top, text ="Hello", command = helloCallBack)
B.pack()
time.sleep(10)
print (x)
top.mainloop()
The issue with this is it skips over all of the Tkinter code and goes to the time.sleep(10), prints the value of x then brings everything from Tkinter up after. Why is it doing this?
The reason you see that the code does sleep() and then prints True before the tkinter windows opens is due to how the mainloop() works in tkinter.
sleep() is useful in python however due to tkinter's single threaded nature all sleep() can due is block the mainloop until it has completed sleeping. Tkinter has its own method to work around this issue called after() and you don't need it here but it is very useful for timing things in tkinter.
Tkinter does its updates in the mainloop and you have a sleep() command that occurs before your mainloop as well as a print command before the mainloop. Both of those things must finish execution before the mainloop is reached for the first time thus not allowing tkinter to start until they are complete.
What you want to do is to place this print statement into your function. As well as a few other quality of life clean ups.
See this example:
import tkinter as tk
top = tk.Tk()
x = True
def hello_call_back():
global x
x = False
if not x:
print('Hello')
tk.Button(top, text="Hello", command=hello_call_back).pack()
top.mainloop()
However in this case I think the simplest form of the code should look like this:
import tkinter as tk
top = tk.Tk()
def hello_call_back():
print('Hello')
tk.Button(top, text="Hello", command=hello_call_back).pack()
top.mainloop()
As your current use of x is redundant to the goal.

How to create a progressbar capturing the progress of a subprocess in tkinter with threading

I am currently programming a GUI with tkinter in Python 3.6 for another Program, which is called through subprocess. I would like to represent the progress of the Program called, with a Progressbar, popping up in another window as soon as I hit the 'Run Program' Button. The value of the progressbar is set by arguments passed from the subprocess script, where I calculate the percentage of progress in a function. What I need:
1. How do I properly use the Threading-Module for spawning the subprocess?
2. How do I import a function (and therefore its arguments) of a script, that runs with command line parameters?
I was looking for a answer in other posts the whole day, but couldn't really connect them to my problem.
I tried to spawn the subprocess by simply calling its function with threading.Thread(target=self.runProgram()).start()
Apparently the GUI-program still waits until the subprocess/Thread is finished and then continues with creating the progressbar, but I expected it to happen at the same time. I then tried to import the percentage value of the subprocess-Program, which didn't work due to missing parameters. Also I don't think that this would really work the way I want it to, because I want the arguments passed during the runtime, when the subprocess is called. It's very hard to properly explain what I am looking for, so here is some code:
GUI-Program:
import tkinter as tk
from tkinter import ttk
from tkinter import *
from tkinter.filedialog import askdirectory,askopenfilename
import os,subprocess
import threading
class eodApplication(tk.Tk):
def __init__(self):
#different entryboxes are created, not necessary for understanding
self.runButton=''
self.progress=''
self.window = tk.Tk()
self.window.geometry("500x350")
self.window.configure(background="white")
self.window.title("End of Day Application")
self.create_widgets()
def create_widgets(self):
#creating entryboxes initiated in __init__-method, containing paths one can browse for and a submit/'Run Program'-Button
self.submit()
def submit(self):
#Button calls function when pressed
self.runButton=ttk.Button(self.window,text='Run Program',command=lambda:self.bar_init())
self.runButton.grid(row=7, columnspan=2)
def bar_init(self):
barWindow = tk.Tk()
barWindow.geometry("250x150")
barWindow.configure(background="white")
barWindow.title("Progress")
self.progress=ttk.Progressbar(barWindow, orient="horizontal", length=200, mode="determinate",value=0)
self.progress.grid(row=0, column=0, sticky=tk.W)
threading.Thread(target=self.runProgram()).start()
from PythonProgrammeDWH import get_percentage
self.progress["value"] = percentage
def runProgram(self):
devnull = open(os.devnull, 'w')
subprocess.call(f"{self.entryboxPyExe.get()} {self.entryboxPyCode.get()} {self.entrybox64.get()} {self.entrybox66.get()} {self.entryboxXsd.get()} {self.entryboxOutput.get()} {self.entryboxXmlExe.get()}",cwd=os.getcwd(),stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,shell=True)
eodApplication=eodApplication()
eodApplication.window.mainloop()
PythonProgrammeDWH (subprocess):
def main(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4]):
~ a lot of stuff going on ~
sumFiles=len(os.listdir(path))
checkedFiles=0
for file in os.listdir(path):
checkedFiles+=1
get_percentage(sumFiles, checkedFiles)
def get_percentage(sumFiles, checkedFiles):
percentage= round((checkedFiles/sumFiles)*100)
return percentage
#percentage is the argument I want to pass to the other script for updating the progressbar

Incorrect behaviour of print() when executed from within a QTDialog window in Spyder

I am working on a very simple interface to explore/graph csv files. My aim is ultimately to explore, not to build software as I am not a developer, more of a "desperate user" :-)
I am leveraging the code found in this example
These are my first steps both in Python and in GUI, so I tend to put print messages in my calls so that I can more or less track what is happening. And this is where I found a strange behavior if I run the code from within Spyder.
import sys
import os
from PyQt4 import QtGui
import pandas as pd
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
# QtGui.QDialog
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = plt.figure()
# this is the Canvas Widget that displays the `figure`
# it takes the `figure` instance as a parameter to __init__
self.canvas = FigureCanvas(self.figure)
# this is the Navigation widget
# it takes the Canvas widget and a parent
self.toolbar = NavigationToolbar(self.canvas, self)
# Just some extra button to mess around
self.button= QtGui.QPushButton('Push Me')
self.button.clicked.connect(self.do_print)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def do_print(self):
print('Hello World!!')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
The strange behavior is that if I push the button once, nothing happens on the Ipython console. By the second time I push, then two "Hello World!" printouts appear.
If, on the other hand, I just launch my script from within a Windows Shell:
python my_simple_test.py
Then everything works as expected.
What am I then doing wrong from within Spyder?
Thanks,
Michele
IPython buffers stdout a bit differently from a terminal. When something is printed, it looks at how long it has been since it last flushed the buffer, and if it's longer than some threshold, it flushes it again. So the second time you click the button, it flushes stdout, and you see both outputs.
You can force it to flush immediately like this:
print('Hello World!!')
sys.stdout.flush()

QFileDialog: no signals emitted, wrong starting directory

I'm trying to show a QFileDialog using the following piece of code:
import os, sys
from PyQt4.QtGui import *
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self._button = QPushButton('Test button')
self._button.clicked.connect(self._onButtonClicked)
self._layout = QHBoxLayout()
self._layout.addWidget(self._button)
self.setLayout(self._layout)
def _onButtonClicked(self):
self._dialog = QFileDialog(self, 'Select directory')
self._dialog.setDirectory(os.getenv('HOME'))
self._dialog.setFileMode(QFileDialog.Directory)
self._dialog.directoryEntered.connect(self._onDirEntered)
self._dialog.exec_()
def _onDirEntered(self, directory):
print("Entered directory: %s" % (directory))
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
app.exec_()
Two problems here:
The directoryEntered signal is never emitted, at least I don't get any output from the script (except for some KDE warnings about Samba support, etc.); actually no signal from the QFileDialog class I tried to connect to gets emitted in the example. What am I doing wrong?
In this example I set the starting directory to $HOME, but the dialog will start in /home instead and have my home directory selected in the listing instead of starting directly in my home directory. Can I change this behaviour somehow?
I'm using Python 3.4.0 with PyQt 4.10.4-2.
Both problems do not occur when using a non-native dialog as suggested by #ekhumoro.

Resources