Is it possible to emulate Tk receive in tkinter? - python-3.x

I have an application that supports communicating to other Tk applications using the send method. In Tcl/Tk it's easy to call a user subroutine in one Tk app by calling send() from another Tk app and specifying the receiving app and subroutine name and args. In Perl it's possible to do the same with a little more work (overriding the definition of Tk::Receive to act on an incoming message from another app's send method).
Does anyone know to use tkinter to receive external messages coming from Tk send? I'd like to have a subroutine that is called to process incoming messages while in mainloop().

Thanks #jasonharper, the following worked in python 3.6:
from tkinter import *
root = Tk()
def some_func(*args):
print("Got args: " + ",".join(args))
# Create a command called my_func that calls some_func
root.tk.createcommand("my_func", some_func)
root.mainloop()
E.g. if I did send tk my_func hey from another Tk app, assuming the default tkinter appname tk, I would see Got args: hey from the above code

Related

How to set Window focus on an easygui message box

I am building a very simple python project that will allow the user to read a message in a box and then click OK. This will part of a larger program.
The issue is that when I run it in Spyder, the message box ends up behind all other windows. How do I make it be the focus and open in front of all other windows? I am running a Windows 11 computer.
I call the below function from another python script.
# import libraries
# sys / os to change working directory
import sys
import os
# easygui for message boxes
from easygui import *
# Create function to set working directory
def setworkdir():
uni_code = fileopenbox()
print(uni_code)
# Create Welcome Box
def openMsgBox():
msgbox("Welcome to DocPhoto. Click on Ok to get started")
setworkdir()
The script to call it:
from docFunctions import openMsgBox
openMsgBox()
I have seen answers where the programmer is trying to do this in tkinter, but I'm not using tkinter. Is there a way to do it without tkinter?

Can you have two instances of tkinter in Python?

Is it possible to have two instances of tkinter ?
import tkinter as tk
import tkinter as sk
root = tk.Tk()
root2 = sk.Tk()
....some window with tk
....some window with sk
root.mainloop()
root2.mainloop()
then have a Toplevel() in both instances.
You can, but the way it works likely won't be like what you expect. Importing it twice isn't the problem (but neither is it the solution). No matter how you import it or how often you import it, creating more than one instance of Tk is the problem. Each instance is backed by a separate internal interpreter. Images and variables and widgets created in one won't exist in the other.
If you need more than one window, it's usually best if second and subsequent windows are instances of Toplevel.
By creating 2 instances or tkinter I can close one instance and all top Levels of that instance. Leaving the second Instance open and running with it's Toplevel instances. This is useful in the sense where I use it for a User preference file. Where it checks for a userfile database for keeping track of variables. I use a csv file to save user variable using pandas. This in turn keeps all the form information in my app safe from being erased after closing the application or accidentally closing the window. Adding AtExit saves the info closing the addinfo window and continues to run the main application. That was my reasoning for having asked the question. I have since found that using multiple Toplevel(s) is a better choice as it will also produce the same result. So my menu items all have a separate instance definition and can be closed in the same manner ,making error checks with each closed window.
from tkinter import Tk,Label,Entry,Button,Toplevel
root=Tk()
def about():
ab=Toplevel()
# About stuff for this window
ab.mainloop()
def info():
inf=Toplevel()
# Information Stuff for this window.
inf.mainloop()
def getUserInfo():
# User info using pandas
getUserInfo.mainloop()
root.mainloop()

Tkinter textvariable does not work in a secondary window?

Because when I use textvariable from a secondary window called from a command in another window the variable.set () is not reflected in that secondary window.
example:
import tkinter as tk
def test():
ven=tk.Tk()
v1=tk.StringVar()
v1.set('TEST')
print(v1.get())
tk.Label(ven, textvariable=v1).pack()
ven.mainloop()
win=tk.Tk()
tk.Button(text='BOTON',command=test).pack()
win.mainloop()
In this case the message 'TEST' set through 'set' is not registered in the Label textvariable..
Why does this happen?
Your problem comes from the fact that you have several Tk instances running simultaneously. Tkinter is based on the the Tk gui framework which is a tcl library. Therefore each Tk instance is not just a window, it's also a tcl interpreter, therefore, when you have several Tk instances, they cannot share StrinVar because the value of the StrinVar is defined in one interpreter (here win) which does not communicate with the other one (ven).
To avoid this kind of issue, just don't use several Tk instances, use Toplevel windows instead:
import tkinter as tk
def test():
ven = tk.Toplevel(win)
v1 = tk.StringVar(win)
v1.set('TEST')
print(v1.get())
tk.Label(ven, textvariable=v1).pack()
win = tk.Tk()
tk.Button(text='BOTON', command=test).pack()
win.mainloop()

How to run PyQt5 GUIs in non-blocking threads?

I have a PyQt5 GUI class that I want to be able to create multiple instances of either from an interactive console or normal run. I need these GUIs to be non-blocking so that they can be used while subsequent code runs.
I've tried calling app.exec__() in separate threads for each GUI like this answer, but the program sometimes crashes as the comment on the answer warned it would:
Run pyQT GUI main app in seperate Thread
And now I'm trying to get the code below working which I made based on this answer:
Run Pyqt GUI main app as a separate, non-blocking process
But when I run it the windows pop and and immediately disappear
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import time
class MainWindow(QtWidgets.QWidget):
def __init__(self):
# call super class constructor
super(MainWindow, self).__init__()
# build the objects one by one
layout = QtWidgets.QVBoxLayout(self)
self.pb_load = QtWidgets.QPushButton('Load')
self.pb_clear= QtWidgets.QPushButton('Clear')
self.edit = QtWidgets.QTextEdit()
layout.addWidget(self.edit)
layout.addWidget(self.pb_load)
layout.addWidget(self.pb_clear)
# connect the callbacks to the push-buttons
self.pb_load.clicked.connect(self.callback_pb_load)
self.pb_clear.clicked.connect(self.callback_pb_clear)
def callback_pb_load(self):
self.edit.append('hello world')
def callback_pb_clear(self):
self.edit.clear()
def show():
app = QtWidgets.QApplication.instance()
if not app:
app = QtWidgets.QApplication(sys.argv)
win = MainWindow()
win.show()
if __name__ == '__main__':
show()
show()
EDIT - I don't see how this question is a duplicate. The 'duplicate' questions are only slightly related and don't provide solutions to my problem at all.
I want to be able to create multiple instances of a GUI (MainWindow in my example) by calling the show() function from either an interactive session or script, and I want those windows to stay on my screen while subsequent code is running.
EDIT2 - When I run the code as a script I can do what I want by using multiprocessing, see this demo:
https://www.screencast.com/t/5WvJNVSLm9OR
However I still need help because I want it to also work in interactive Python console sessions, and multiprocessing does not work in that case.
It isn't necessary to use separate threads or processes for this. You just need a way to maintain a reference to each new window when importing the script in a python interactive session. A simple list can be used for this. It is only necessary to explictly start an event-loop when running the script from the command-line; in an interactive session, it will be handled automatically by PyQt.
Here is an implementation of this approach:
...
_cache = []
def show(title=''):
if QtWidgets.QApplication.instance() is None:
_cache.append(QtWidgets.QApplication(sys.argv))
win = MainWindow()
win.setWindowTitle(title)
win.setAttribute(QtCore.Qt.WA_DeleteOnClose)
win.destroyed.connect(lambda: _cache.remove(win))
_cache.append(win)
win.show()
if __name__ == '__main__':
show('Foo')
show('Bar')
sys.exit(QtWidgets.QApplication.instance().exec_())
This is a minor addendum to #ekhumoro's answer. I don't have enough reputation to only add a comment so I had to write this as an answer.
#ekhumoro's answer almost fully answers #Esostack's question, but doesn't work in the Ipython console. After many hours of searching for the answer to this question myself, I came across a comment from #titusjan in a three year old thread (here) also responding to a good answer from #ekhumoro.
The missing part to #ekhumoro's answer which results in the gui windows freezing for Ipython specifically is that Ipython should be set to use the qt gui at launch or once running.
To make this work with Ipython:
Launch Ipython with ipython --gui=qt5
In a running Ipython console run the magic command %gui qt5
To fix it from a Python script you can run this function
def fix_ipython():
from IPython import get_ipython
ipython = get_ipython()
if ipython is not None:
ipython.magic("gui qt5")

How to create a simple message box with a Python script a plattform-independend way?

I have simple Python3-script. It is not a application - just a helper script.
Normaly it would do its job without any human readable output. But when an error happens I want to have a message window.
I know there are some unixoid commandline tools doing that (zenity).
Maybe there is a Python3 package doing the same?
If all you need is the messagebox then you can use
import tkinter as tk
from tkinter.messagebox import showerror
tk.Tk().withdraw() #Hide window that appears with message
showerror('Title', 'Content') #display message

Resources