I am a beginner in programming. I do this as hobby and to improve my productivity at work.
I am writing a program to automatically paste the clipboard to a Tkinter entry whenever a user copy a line of text.
I use a while loop to detect if there is a change in the current clipboard, then paste the newly copied clipboard text to the Tkinter entry.
The GUI update perfectly when I copy a new line of text.
However the GUI is not responding and I can't click the TK entry to type something that I want.
FYI I am using Python 3.5 software.
Thanks in advance.
My code:
from tkinter import *
import pyperclip
#initial placeholder
#----------------------
old_clipboard = ' '
new_clipboard = ' '
#The GUI
#--------
root = Tk()
textvar = StringVar()
label1 = Label(root, text='Clipboard')
entry1 = Entry(root, textvariable=textvar)
label1.grid(row=0, sticky=E)
entry1.grid(row=0, column=1)
#while loop
#-----------
while(True): #first while loop: keep monitoring for new clipboard
while(old_clipboard == new_clipboard): #second while loop: check if old_clipboard is equal to the new_clipboard
new_clipboard = pyperclip.paste() #get the current clipboard
print('\nold clipboard pre copy: ' + old_clipboard)
old_clipboard = new_clipboard #assign new_clipboard to old_clipboard
print('current clipboard post copy: ' + old_clipboard)
print('\ncontinuing the loop...')
textvar.set(old_clipboard) #set the current clipboard to GUI entry
root.update() #update the GUI
root.mainloop()
You need to put your while loop in a def, then start it in a new thread, that way your gui won´t freeze.
eg:
import threading
def clipboardcheck():
#Your while loop stuff
class clipboardthread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
clipboardcheck()
clipboardthread.daemon=True #Otherwise you will have issues closing your program
clipboardthread().start()
Related
I'm a beginner in programming and I have a problem making a label on my GUI to change its text value to inform the user of word's meaning (working on English dictionary). I would like the label to update itself with every loop, and so to show all the the definitions of a given word. However, the program only updates itself once the entire for loop is completed, and displays only the last definition of a word.
I've learned that I should use a QTimer, and tried it as it's shown in documentation, but with no success.
I would appreciate any help of how to make the label to update itself. Thank you.
Here is the code I am using:
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox
from PyQt6.QtWidgets import QLabel, QPushButton, QLineEdit, QComboBox, QScrollArea
from PyQt6.QtCore import Qt, QTimer
import json
from difflib import get_close_matches
data = json.load(open("data.json"))
def translate(word):
word = word.lower()
if word in data:
return data[word]
elif word.title() in data:
return data[word.title()]
elif word.upper() in data:
return data[word.upper()]
elif len(get_close_matches(word, data.keys())) > 0:
msg = QMessageBox()
msg.setWindowTitle('Get close matches')
msg.setText("Did you mean %s instead? Press Yes if yes, or No if no: " % get_close_matches(word, data.keys())[0])
msg.setIcon(QMessageBox.Icon.Question)
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
msg.setDefaultButton(QMessageBox.StandardButton.Yes)
ret = msg.exec()
if ret == QMessageBox.StandardButton.Yes:
return data[get_close_matches(word, data.keys())[0]]
else:
return "We didn't understand your entry. Please double check it."
else:
return "The word doesn't exist. Please double check it."
def translate2():
output = translate(text.text())
if type(output) == list:
i = 1
for item in output:
output_label.setText("%s. %s" % (i,item))
i+=1
else:
output_label.setText(output)
app = QApplication([])
window = QWidget()
window.setWindowTitle('Word Definition')
layout = QVBoxLayout()
layout1 = QHBoxLayout()
layout.addLayout(layout1)
layout2 = QVBoxLayout()
layout.addLayout(layout2)
text = QLineEdit()
layout1.addWidget(text)
btn = QPushButton('Convert')
layout1.addWidget(btn, alignment=Qt.AlignmentFlag.AlignBottom)
btn.clicked.connect(translate2)
output = QWidget()
output_label = QLabel('')
output_label.setFixedSize(600, 50)
layout2.addWidget(output_label)
window.setLayout(layout)
window.show()
app.exec()
I have a question, its more an OS-based one.
I'm playing a video game and I want to be able to put a textual timer ontop of the game's screen as if it was a part of the game itself.
Now, I can write a program in any language that displays a TextBox with a timer on the screen, but if I run it, the game's process (lets call it game.exe) "loses" its focus and I get my TextBox focused and interactive by the OS.
Is there any option to display that text "ontop" of the game.exe that comes from an entire different process? as if there were "layers" to the screen. Also, this text shouldn't be intractable, clickable or make the game.exe process lose its focus.
Here's a very simple example I drew:
Thanks a lot!
Solved this using a window trick with python and tkinter with some windows api stuff.
The trick is to create a transparent non-clickable window and keep it always on top.
I've basically combined this answer with a bunch of simpler stuff like removing window's border and set to auto fullscreen.
from tkinter import *
import time
import win32gui
import win32api
from win32api import GetSystemMetrics
# WIDTH = 500
# HEIGHT = 500
WIDTH = GetSystemMetrics(0)
HEIGHT = GetSystemMetrics(1)
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0
tk = Tk()
# tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
tk.attributes('-fullscreen', True)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, highlightthickness=0)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
canvas.create_text(WIDTH/2,HEIGHT/2,fill="white",font="Arial 20", text="TEXT GOES HERE")
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
global old
if old !=():
canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
old = (data[0], data[1])
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
tk.bind('<Visibility>', putOnTop)
tk.focus()
running = 1
while running == 1:
try:
tk.update()
time.sleep(0.01)
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
except Exception as e:
running = 0
print("error %r" % (e))
My computer doesn't have any way of letting me know if my NumLk is on or off, so I am trying to add an icon in my systray that will changed depending on the state of my NumLk. This .py will always be running when my computer is on.
So far I was able to mix 3 codes and I am able to display the icon in the systray but it doesn't get updated when the state of NumLk change. Actually if I press NumLk twice, I still get the same icon (the on one) and I get this error:
QCoreApplication::exec: The event loop is already running
File "\systray_icon_NumLk_on_off.py", line 21, in on_key_press
main(on)
File "\systray_icon_NumLk_on_off.py", line 46, in main
sys.exit(app.exec_())
SystemExit: -1
My code may not be the best way to do it, so any alternative is welcome! Here is what I came up so far:
#####get the state of NumLk key
from win32api import GetKeyState
from win32con import VK_NUMLOCK
#how to use: print(GetKeyState(VK_NUMLOCK))
#source: http://stackoverflow.com/questions/21160100/python-3-x-getting-the-state-of-caps-lock-num-lock-scroll-lock-on-windows
#####Detect if NumLk is pressed
import pyglet
from pyglet.window import key
window = pyglet.window.Window()
#source: http://stackoverflow.com/questions/28324372/detecting-a-numlock-capslock-scrlock-keypress-keyup-in-python
on=r'on.png'
off=r'off.png'
#window.event
def on_key_press(symbol, modifiers):
if symbol == key.NUMLOCK:
if GetKeyState(VK_NUMLOCK):
#print(GetKeyState(VK_NUMLOCK))#should be 0 and 1 but
main(on)
else:
main(off)
#window.event
def on_draw():
window.clear()
### display icon in systray
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
#source: http://stackoverflow.com/questions/893984/pyqt-show-menu-in-a-system-tray-application - add answer PyQt5
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
def main(image):
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon(image), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pyglet.app.run()
The reason for QCoreApplication::exec: The event loop is already running is actually because you're trying to start app.run() twice. Qt will notice there's already an instance running and throw this exception. When instead, what you want to do is just swap the icon in the already running instance.
Your main problem here is actually the mix of libraries to solve one task if you ask me.
Rather two tasks, but using Qt5 for the graphical part is fine tho.
The way you use Pyglet is wrong from the get go.
Pyglet is intended to be a highly powerful and effective graphics library where you build a graphics engine ontop of it. For instance if you're making a game or a video-player or something.
The way you use win32api is also wrong because you're using it in a graphical window that only checks the value when a key is pressed inside that window.
Now, if you move your win32api code into a Thread (a QtThread to be precise) you can check the state no matter if you pressed your key inside your graphical window or not.
import sys
import win32api
import win32con
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread, enumerate
from time import sleep
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(QtWidgets.qApp.quit)
self.setContextMenu(menu)
class KeyCheck(QtCore.QThread):
def __init__(self, mainWindow):
QtCore.QThread.__init__(self)
self.mainWindow = mainWindow
def run(self):
main = None
for t in enumerate():
if t.name == 'MainThread':
main = t
break
while main and main.isAlive():
x = win32api.GetAsyncKeyState(win32con.VK_NUMLOCK)
## Now, GetAsyncKeyState returns three values,
## 0 == No change since last time
## -3000 / 1 == State changed
##
## Either you use the positive and negative values to figure out which state you're at.
## Or you just swap it, but if you just swap it you need to get the startup-state correct.
if x == 1:
self.mainWindow.swap()
elif x < 0:
self.mainWindow.swap()
sleep(0.25)
class GUI():
def __init__(self):
self.app = QtWidgets.QApplication(sys.argv)
self.state = True
w = QtWidgets.QWidget()
self.modes = {
True : SystemTrayIcon(QtGui.QIcon('on.png'), w),
False : SystemTrayIcon(QtGui.QIcon('off.png'), w)
}
self.refresh()
keyChecker = KeyCheck(self)
keyChecker.start()
sys.exit(self.app.exec_())
def swap(self, state=None):
if state is not None:
self.state = state
else:
if self.state:
self.state = False
else:
self.state = True
self.refresh()
def refresh(self):
for mode in self.modes:
if self.state == mode:
self.modes[mode].show()
else:
self.modes[mode].hide()
GUI()
Note that I don't do Qt programming often (every 4 years or so).
So this code is buggy at it's best. You have to press Ctrl+C + Press "Exit" in your menu for this to stop.
I honestly don't want to put more time and effort in learning how to manage threads in Qt or how to exit the application properly, it's not my area of expertis. But this will give you a crude working example of how you can swap the icon in the lower corner instead of trying to re-instanciate the main() loop that you did.
I'm trying to make a gui (Qt Designer) to import an excel file and display the data in the gui.
The script works fine when I run it from within my IDE (Spyder), but if I run it from the command window or by opening the python file from windows explorer, the import function does not work. (The gui starts up fine but when the import button is pressed and the file is selected, nothing happens and no error is produced. When running from Spyder, the data is imported and displayed in the gui as expected).
If I pre-select the file location (commented out in the code below), then the script works fine from the command line or by clicking from explorer.
Thanks for any help!
Python 2.7 (Anaconda), Windows 10, PyQt4
import sys
from PyQt4 import QtGui
from excel_import_gui import Ui_MainWindow
import xlrd
class Main(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.setupSignals()
def set_import_data(self,data_to_import,table_to_change):
for row in range(len(data_to_import)):
for col in range(len(data_to_import[0])):
table_to_change.setRowCount(len(data_to_import))
table_to_change.setColumnCount(len(data_to_import[0]))
item = data_to_import[row][col]
table_to_change.setItem(row,col,QtGui.QTableWidgetItem(str(item)))
def setupSignals(self):
self.ui.importData_btn.clicked.connect(self.select_file)
def select_file(self):
excel_file = QtGui.QFileDialog.getOpenFileName(self,
"Select Excel file to import","","Excel (*.xls *.xlsx)")
# excel_file = "C:/Users/Ben/Work/Python tests/Qt GUIs/Excel_import_GUI/fish_test.xlsx"
if excel_file:
open_excel_file = xlrd.open_workbook(excel_file)
self.start_import_data(open_excel_file)
def start_import_data(self, workbook):
#import data from excel file
workbook_data = []
for sheetNum in range (workbook.nsheets):
worksheet = workbook.sheet_by_index(sheetNum)
workbook_data.append([[worksheet.cell_value(row,col) for col in range (worksheet.ncols)] for row in range(worksheet.nrows)])
# Set each worksheet of workbook_data to each tab in GUI widget
self.set_import_data(workbook_data[0],self.ui.fish_table)
self.set_import_data(workbook_data[1],self.ui.boats_table)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Main()
window.show()
sys.exit(app.exec_())
Well I found a solution myself by converting the excel_file variable to a string.
excel_file = str(QtGui.QFileDialog.getOpenFileName(self, "Select Excel file to import","","Excel (*.xls *.xlsx)"))
I created a simple GUI in python 3.4 using tkinter 8.5. I used cx_freeze to build an exe from this GUI. Now when I run this exe, sometimes I notice that the program still shows under 'Background Processes' in Task Manager even after I terminate it using a Quit button or using the close button in the window.
The GUI works like this: You select a file type from a drop down list, read the file using a command button and save it as a separate file. Now this problem happens only if I close the GUI after using it. If I simply open the GUI and close it using the Quit button or close button, it does not stay as a background process.
Is it normal for it to behave like this? If not what can I do to terminate it properly?
The simplified code for the GUI is given below. The function 'fileselect' calls functions from the module 'dataselect'. If needed, I will provide the code for the 'dataselect' module also.
from dataselect import *
from openpyxl import Workbook
from tkinter import *
from tkinter import ttk, filedialog
root = Tk()
root.title("Select Data File")
# Actual File Selection based on Combobox Selection
def fileselect():
file_type = filetype.get()
if file_type == ".txt":
text = selecttxt()
textfile = filedialog.asksaveasfile(mode='w', defaultextension=".txt")
for line in text:
for number in line:
textfile.write(str(number)+" ")
textfile.write('\n')
elif file_type == ".xlsx":
excel = selectxlsx()
excelfile = filedialog.asksaveasfile(mode='w', defaultextension=".xlsx")
excelfilename = excelfile.name
excelbook = Workbook()
excelsheet = excelbook.active
rows = 0
for excel_row in excel:
cols = 0
for excel_cell in excel_row:
excelsheet.cell(row=rows, column=cols).value = excel[rows][cols]
cols += 1
rows += 1
excelbook.save(excelfilename)
def quit():
global root
root.destroy()
# Select the File Type to be opened (.txt or .xlsx for now)
ttk.Label(root, text="Please select the file type").grid(column=2, row=1)
filetype = StringVar()
sel_type = ttk.Combobox(root,values=('.txt','.xlsx'),textvariable=filetype)
sel_type.grid(column=2,row=2,sticky=E)
# Command Button for Opening File
cb_open = ttk.Button(root, text="Select File", command=fileselect)
cb_open.grid(column=2, row=3)
# Command Button for Quitting GUI
cb_quit = ttk.Button(root, text="Quit", command=quit)
cb_quit.grid(column=1, row=3)
root.mainloop()
There are two things you need to change:
1) Add sys.exit() to your quit method
def quit():
root.quit
root.destroy
sys.exit()
2) Add protocol to your root
root.protocol("WM_DELETE_WINDOW", quit)
Finally, don't forget to import sys.