Tkinter can't insert into Text Widgets with pack_forget() - python

My application can receive multiple jobs which it processes using threads. I've created Tabs which contain a Text Widget for each job, but I'm having trouble inserting text into the text widgets.
No error the application just hangs.
Tabs are generated using the script found here: http://code.activestate.com/recipes/577261-python-tkinter-tabs/
The tabs are a subclass of Frames that use pack_forget() to hide when they're not selected.
A simplified version of my App
server.py
class supply_thread(threading.Thread):
def __init__(self, _sock, app):
threading.Thread.__init__(self)
self.app = app
def run(self):
def close_tab():
print 'Terminating supply.'
new_supply.kill()
# Create new tab
self.tab = Frame(self.app)
self.tab.pack()
#self.tab.pack_forget() # <-- inserting this causes the app to hang
# Scrollbar
self.scrollbar = Scrollbar(self.tab)
self.scrollbar.pack(side=RIGHT, fill=Y)
# Text
self.text = Text(self.tab, yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.text.yview)
self.text.pack(expand=YES, fill=BOTH)
# Close
self.button = Button(self.tab, text="CLOSE", command=close_tab)
self.button.pack(side=BOTTOM, fill=BOTH, expand=YES)
print 'Starting thread' , data[0]['job'] , data[0]['supply']['dir_name'] , self.getName()
logging.info(data[0])
new_supply = supply.supply(data, self.app, self.text)
new_supply.run()
print 'Closing Thread' , data[0]['job'] , data[0]['supply']['dir_name'] , self.getName()
main.py
class App(Tk):
def __init__(self, master=None):
Tk.__init__(self, master)
tab1 = Frame(self)
tab1.pack()
self.scrollbar = Scrollbar(tab1)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.text1 = Text(tab1, yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.text1.yview)
self.text1.pack(expand=YES, fill=BOTH)
if __name__ == "__main__":
app = App()
server = server(app)
server.daemon = True
server.start()
app.mainloop()
I think this is what is causing the issue... If so is there an alternative?
Thank you in advance for any help.

pack_forget has no effect on whether you can insert into a text widget or not. There is not enough information in your question to give you an answer.
If you're using threads, is it possible that you are trying to insert text from within a thread? You can only call widget methods from the main thread. Though, typically the result of this is that the program crashes rather than hangs.
When a tkinter app hangs, that is sometimes a symptom of trying to use both pack and grid in the same container widget. Are you doing that?

Related

Unresponsive tkinter SimpleDialog box

Below is an outline of a tkinter GUI in which I want the same dialog box to be opened in various ways. The response selected by the user from choices in the dialog then needs to be returned to the mainloop.
The SimpleDialog class looks to be ideal for this and here I have just used the example provided in the dialog code. It is accessed by both the button and popup menu in the View class, along with their bindings in the Controller class.
It works just fine when called from the button, but when called from the popup menu (from a right click) the dialog appears and then freezes the entire app.
from tkinter import simpledialog as s
import tkinter as tk
class View(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self)
self.grid(row=0, column=0, sticky='nsew')
self.configure(bg = 'blue')
self.popup = tk.Menu(self, tearoff=0)
self.bind("<Button-2>", self.make_popup) #right-click to show popup
self.button = tk.Button(self, text='Test')
self.button.grid()
def make_popup(self, event):
try:
self.popup.tk_popup(event.x_root + 15, event.y_root, 0)
finally:
self.popup.grab_release()
class Controller():
def __init__(self, view):
view.popup.add_command(label ='do test', command = lambda : self.do_test(None, view))
view.popup.add_command(label ='dummy test', command = print('This one works OK'))
view.button.bind("<Button-1>", lambda e, : self.do_test(e, view))
def do_test(self, event, view):
d = s.SimpleDialog(view,
text="This is a test dialog. "
"Would this have been an actual dialog, "
"the buttons below would have been glowing "
"in soft pink light.\n"
"Do you believe this?",
buttons=["Yes", "No", "Cancel"],
default=0,
cancel=2,
title="Test Dialog")
print(d.go())
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('200x100')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
view = View(self)
controller = Controller(view)
if __name__ == "__main__":
app = App()
app.mainloop()
It seems to me that the dialog should either work or not work, and not care how it is called. So I would be very grateful for an explanation as to why it responds in one case but not the other, and of course equally grateful for a fix.
The problem appears to lie in the print statement in the do_test callback, as splitting this into two lines fixes it
#print(d.go())
answer = d.go()
print(answer)
As reported in a comment this may be only an issue for MacOS (I am using MacOS 11.1 and Python 3.10.8 ).

Tkinter newly created button does not execute command

A script should open an application with two buttons visible. When Hello button is pressed a new button is gridded into the row number 1 and Hello button to be deactivated. When this new button is pressed it should delete itself off the grid and reactivate hello button but it does not do it.
Please check the video to see it in action.
Code edited to comment suggestion
from tkinter import *
class Application(Frame):
def __init__(self, master=None):
self.master = master
self.master.geometry('300x100+10+10')
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def new_button(self):
print("enable_b")
self.hi_there.config(state=ACTIVE)
self.new_button.grid_remove()
def say_hi(self):
print("hi there, everyone!")
self.new_button = Button(self)
self.new_button.config(text = "New BTN", command=self.new_button)
self.new_button.grid(row=1,column=0)
self.hi_there.config(state=DISABLED)
def createWidgets(self):
self.QUIT = Button(self)
self.QUIT.config(text="QUIT",fg="red",command=self.quit)
self.QUIT.grid(row=0,column=1)
self.hi_there = Button(self)
self.hi_there["text"] = "Hello",
self.hi_there["command"] = self.say_hi
self.hi_there.grid(row=0,column=0)
def quit(self):
self.master.destroy()
def testit():
root = Tk()
app = Application(master=root)
app.mainloop()
if __name__ == '__main__':
testit()
Initially, self.new_button refers to a method. Then, you do this:
self.new_button = Button(self)
That effecting removes the method and replaces it with the button widget itself.
Also, you never assign a command to the new button, so clicking it doesn't cause anything to be called.
Where your program will technically work just fine with the 2 correction mentioned in Bryan's answer I am not sure why you are taking all the extra effort configuring the widgets for each individual field. All your configs can be done when you create the widget.
That said you can also change a few things for a cleaner code and 1 change I think that really needs to be made is how you are removing the new_button from the grid. When you do grid_remove() this only takes the widget off the screen but does not get rid of the widget. Then next time you press the say_hi button you will end up creating a new button and the old button will still exist. Instead I think I would use destroy() on the button and then let say_hi recreate it.
See this revised version of your code. You will see what I mean about configuring everything when creating the widget and also you do not need to write your own function for quit you can simply do self.master.destroy in the quit button command.
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.master = master
self.master.geometry('300x100+10+10')
self.create_widgets()
def new_btn(self):
print("enable_b")
self.hi_there.config(state="active")
self.new_button.destroy()
def say_hi(self):
print("hi there, everyone!")
self.new_button = tk.Button(self, text="New BTN", command=self.new_btn)
self.new_button.grid(row=1, column=0)
self.hi_there.config(state="disabled")
def create_widgets(self):
tk.Button(self, text="QUIT", fg="red", command=self.master.destroy).grid(row=0,column=1)
self.hi_there = tk.Button(self, text="Hello", command=self.say_hi)
self.hi_there.grid(row=0, column=0)
if __name__ == '__main__':
root = tk.Tk()
app = Application(master=root).pack()
root.mainloop()

Access to Tkinter's Text widget by background thread cause crashes

I created a simple tkinter app, where have used two threads. Their task is to write numbers to widgets such as label and text. One thread is triggered by button (click event) and second is executed as a background thread.
import Tkinter as tk
from ttk import *
from Tkconstants import *
import threading, thread, time
def tl1(text,counter):
while True:
text.insert(END,counter)
counter += 1
time.sleep(2)
def tl2(label,counter):
while True:
label['text'] = counter
counter += 1
time.sleep(1)
class mainWindow():
def __init__(self, master):
self.master = master
self._initLayout()
def _initLayout(self):
#button
self.button = tk.Button(self.master, text="thread1_start", command = self._task1)
self.button.pack()
#label
self.label = tk.Label(self.master)
self.label.pack()
#text
self.text = tk.Text(self.master, width=30)
self.text.pack()
def _task1(self):
t1 = thread.start_new_thread(tl1,(self.text,1))
def _task2(self):
t2 = thread.start_new_thread(tl2,(self.label,1000))
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.mainWindow = mainWindow(self)
self.mainWindow._task2() #background_thread
app = App()
app.mainloop()
In this manner everything works fine, but if we change the background thread to display results on text widget, the whole app freezes.
Why background thread works fine communicating with label but causes problems with text widget? Is there any way to run it properly?
Tkinter isn't thread safe. You can only access widgets from the thread that created them. Your threads will need to put data on a thread-safe queue, and your GUI thread will need to poll the queue.
In your particular case you don't need threads at all. You can use the tkinter after method to run code periodically.

How do you validate a window in Tkinter?

def createWidgets(self):
self.INSTRUCTIONS = Button(self) #creating button linked to instructions_window
self.INSTRUCTIONS["text"] = "Instructions"
self.INSTRUCTIONS["fg"] = "green"
self.INSTRUCTIONS["command"] = self.instruction_window #command which opens instructions_window
self.INSTRUCTIONS.pack({"side": "left"})
Currently, if I press the button multiple times then the instructions window will open multiple times. How do I ensure that when the button is pressed, if the window is already open then it will flash to show that the same window can't be opened. Is there a command? Or do I need to use a validation of some sort?
Here's a great article dealing with more complicated examples of dialog boxes.
Essentially what you are looking for is almost like a modal dialog window except it seems with the additional ability to still interact with the parent window to a degree. At this point it may be worth considering making it totally modal, but I do not know your use case. If not, you can definitely adapt the scripts given on the tutorial website to fit your needs.
The way I do this is to create a function that will create the window if it doesn't exist, and then display the window. Personally I don't think there's a need to flash the window, but you could do that if you want. Tkinter doesn't know how to flash a window, but you can do something simple like changing the colors briefly.
Here's an example:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.instruction_window = None
self.instructions = tk.Button(self, text="Instructions", foreground="green",
command=self.show_instructions)
self.instructions.pack(side="left")
def show_instructions(self):
'''show the instruction window; create it if it doesn't exist'''
if self.instruction_window is None or not self.instruction_window.winfo_exists():
self.instruction_window = InstructionWindow(self)
else:
self.instruction_window.flash()
class InstructionWindow(tk.Toplevel):
'''A simple instruction window'''
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.text = tk.Text(self, width=40, height=8)
self.text.pack(side="top", fill="both", expand=True)
self.text.insert("end", "these are the instructions")
def flash(self):
'''make the window visible, and make it flash temporarily'''
# make sure the window is visible, in case it got hidden
self.lift()
self.deiconify()
# blink the colors
self.after(100, lambda: self.text.configure(bg="black", fg="white"))
self.after(500, lambda: self.text.configure(bg="white", fg="black"))
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Tkinter: Calling a multithreaded instance

I am a beginner at python, and it is my first language. I have been tasked with something that is quickly becoming too large for me to grasp that I need to finish. I am almost done. At this moment, I have created a dialog box that serves as a Main Menu, a dialog box that is a selectable option from the main menu that runs a test, and a multithreaded instance which runs the test, opens up a "please wait" box and up on finishing the test, another dialog box pops up which declares the test complete.
My issue: Within the "Run Test" dialog, I am trying to create a button that will call the multithreaded instance into action. From the code I have parsed together with the help of others, I can not see which class to instantiate within the "Run Test" dialog box.
I am beginning to believe my implementation of threading is incorrect. However there must be a way.
This is the module I am trying to call on.
from slice_setup import SLICE_SETUP
import Tkinter as tk
import threading
import Queue
class GuiPart:
def __init__(self, master, queue):
self.queue = queue
self.master = master
self.master.geometry("300x100+400+250")
self.master.title("RSAM BCT")
tk.Label(master, text="REDCOM SLICE", fg="red").pack()
tk.Label(master, text="BCT - Basic Configuration Test", fg= "red").pack()
tk.Label(master, text="Please wait...", fg= "black").pack()
tk.Label(master, text="Estimated time: 3 min 6 sec", fg= "black").pack()
def processIncoming(self):
while self.queue.qsize():
try:
text = self.queue.get(0)
Complete(self.master, text)
except Queue.Empty:
pass
class ThreadedClient:
def __init__(self, master):
self.master = master
self.queue = Queue.Queue()
self.gui = GuiPart(master, self.queue)
self.running = True
self.thread = threading.Thread(target=self.workerThread1)
self.thread.start()
self.periodicCall()
def periodicCall(self):
self.gui.processIncoming()
if not self.running:
return
self.master.after(100, self.periodicCall)
def workerThread1(self):
obj_rcs = SLICE_SETUP()
obj_rcs.SLICE()
self.queue.put("Configuration Complete!")
self.running = False
class Complete(tk.Toplevel):
def __init__(self, master=None, completetext=""):
tk.Toplevel.__init__(self, master)
self.geometry("400x300+400+250")
self.title("RSAM BCT")
tk.Label(self, text="REDCOME SLICE", fg="red").pack()
tk.Label(self, text="BCT - Basic Configuration Test", fg="red").pack()
tk.Label(self, text=completetext, fg="dark green").pack()
tk.Label(self, text="Trunk 1: Port 1: Phone 1: 760-450-4500", fg="black").pack()
tk.Label(self, text="Trunk 1: Port 2: Phone 2: 760-450-4501", fg="black").pack()
tk.Button(self, text=" Exit ", command=self.destroy).pack()
if __name__ == "__main__":
root = tk.Tk()
client = ThreadedClient(root)
root.mainloop()
and this is where I am trying to call from:
import sys
import Tkinter as Tk()
from bct_pleasewait import ????
import threading
import Queue
import time
sGui = Tk()
class slice_menu:
def runtest(self):
obj_wait = ????
obj_wait.????
def slicemenu(self):
sGui.geometry("400x300+400+250")
sGui.title("RSAM BCT")
Label(sGui, text= "REDCOM SLICE", fg="red").pack()
Label(sGui, text= "BCT - Basic Configuration Test", fg= "red").pack()
Label(sGui, text= "-Ensure you are logged off of HyperTerminal", fg= "black").pack()
Label(sGui, text= "-Turn on your REDCOM SLICE unit",
fg= "black").pack()
Label(sGui, text= "-Please connect your laptop to SLICE CONSOLE", fg= "black").pack()
Label(sGui, text= "-This configuration will take 3 minutes", fg= "black").pack()
Button(sGui, text = " Run ", command = self.runtest).pack()
Button(sGui, text = " Exit test ", command = sGui.destroy).pack()
sGui.mainloop()
This class still has minor errors in it but I just want to get this issue solved first.
Not a specific answer, but your question is very broad.
Some points to keep in mind:
Tk is not thread-safe. That is, you should only invoke Tk calls from the main thread. It is OK to let other threads do non-gui work.
In CPython, only one thread at a time can be executing Python bytecode, due to the Global Interpreter Lock ("GIL"). So your non-gui thread could make the GUI unresponsive.
If you are only running one test at a time, and if you can divide that test into small pieces, you could use a timeout (also called alarm handler). This handler does a little bit of work, saves its state, updates the progress dialog and then exits, waiting to be invoked again.
If the test runs a long time and you want the GUI to remain responsive, I would suggest using multiprocessing instead of threading. Start your test in a different process, and use things like Queues and Semaphores et cetera to communicate between the GUI and non-qui processes.

Categories

Resources