I have a method that is called from a button and it allows the user to select a file and pump out an analysis on the other side. There is a canvas outside the class and method that has a text widget that tells the user what is happening, with an original text='Ready'. This is the method that gets it done:
def process_solution(self):
canvas.itemconfig(infobox_text, text='Please Select a File.')
self.import_file_path = filedialog.askopenfilename()
canvas.itemconfig(infobox_text, text='Analysing Solution. Please Wait.')
if self.import_file_path:
# UnGzip
with gzip.open(self.import_file_path, 'rt') as decompressed_buffer:
decompressed_data = decompressed_buffer.read()
# Parse to XML
parsed_data = ElementTree.fromstring(decompressed_data)
# Now extract metrics
self.analysis_results = ExtractMetrics.start(parsed_data)
# Display Parsed Results in Window
canvas.itemconfig(infobox_text, text=self.analysis_results)
else:
canvas.itemconfig(infobox_text, text='Upload Cancelled.')
It changes from the original 'Ready' message to 'Please Select a File' with no problem, then it seems to skip 'Analysing Solution. Please Wait.' (even when I put the thread to sleep for a few seconds). And finally it displays self.analysis_results. Even if the file selection is cancelled it displays the right message, but not when a file is selected.
My question is why, if in all cases I'm using canvas.itemconfig(infobox_text, text=...) the first, second and fourth changes to the text work, but it skips the third. No errors anywhere either.
Related
I have a code snippet which runs perfectly. In some cases, I need user input, but there are also cases where user input is not necessary and code functions without it perfectly.
So, in that cases I create with conditional a flow where entry box widget is created and destroyed after value is get() by script. But I cannot make code to wait until to say stops(pauses) when the user has given input value then continues to run.
code is below;
varSheetname_GS = ''
if varsoundTitle_usernameHeroContainer == 'FloatingBlueRecords' or varsoundTitle_usernameHeroContainer == 'DayDoseOfHouse':
varSheetname_GS = varsoundTitle_usernameHeroContainer
else:
# look for sheetname as an input value entered by user
new_sheetname_entryBox=tk.Entry(canvas2,width=30).pack()
new_sheetname_entryBox.focus()
var_new_sheetName =new_sheetname_entryBox.get()
new_sheetname_entryBox.destroy()
varSheetname_GS = var_new_sheetName #input("Enter the sheetname in (GooSheets):")
I have looked for so_01 and so_02 which are related to topic but was not able to implement in my situation. So, anyone who would guide me towards that answers would be great from yourside.
Thanks ahead!
You can use the wait_window method to wait until the entry widget has been destroyed. You can then bind the return key to destroy the window. If the entry is associated with a StringVar, you can get the value after the widget has been destroyed.
The solution might look something like this:
entry_var = tk.StringVar()
new_sheetname_entryBox=tk.Entry(canvas2,width=30, textvariable=entry_var)
new_sheetname_entryBox.pack()
new_sheetname_entryBox.bind("<Return>", lambda event: new_sheetname_entryBox.destroy())
new_sheetname_entryBox.focus_set()
# wait for the entry widget to be deleted...
new_sheetname_entryBox.wait_window()
# save the value
varSheetname_GS = entry_var.get()
I'm trying to overwrite an existing keyboard function on the enter key with a custom hotkey. The problem is, I cannot stop the default action from occurring also. Worse yet, it occurs after the custom action, so I don't have the chance to retroactively correct it as well.
Here are the relevant parts of the code:
from tkinter import *
from tkinter import ttk
from keyboard import *
root = Tk()
root.geometry('400x300')
'''This is the problematic function called by the hotkey'''
def entr_key(text):
'''Enter key results in printing of the line to the right of cursor'''
curs_pos = text.index(INSERT)
right_hand = text.get(curs_pos,END)
right_hand = right_hand.split('\n')[:1] #lines -> strs in a list, selects the first
print (right_hand)
return
'''THIS IS THE MAIN WIDGET FOR THIS FRAME'''
text_view = ttk.Frame(root, padding=10)
text_view.grid()
text_box = Text(text_view, width=45, wrap=WORD)
text_box.grid(column=0, row=1)
text_box.insert('1.0', 'This is a Text widget demo,\nThis text is aimed at testing the enter key function')
add_hotkey("enter", lambda: entr_key(text_box))
root.mainloop()
(I've changed up some variable names to be more understandable, apologies if I missed any but it's not the source of the problem!)
I've also (unsuccesfully) tried other ways of doing this, eg:
while True:
if is_pressed('enter'):
entr_key(text_box)
'''AND ALSO'''
on_press_key("enter", lambda x=None: entr_key(text_box))
Just to be clear, I don't want the default action of enter key moving the text to a new line.
I need either a way to "break" the key event so that only the custom action takes place, or a way for the custom action to occur after default action, so I can retroactively edit it out
EDIT!!!
I've found a workaround: at the start of entr_key() I call time.sleep(0.01). During this, the default action of the enter key occurs first, and I can retroactively edit it out when the custom function resumes. The delay is slight enough to not be noticeable at all.
But still, if anyone knows how to prevent the default action from occurring completely, I would really appreciate it.
I'm writing a program using tkinter, which will display a Toplevel at startup containing license information. I want to provide a checkbox on the Toplevel so that the user won't have to see the license information again when running the program in the future. I haven't really found much on how to do this kind of thing, so if there is a much simpler way, I'm all ears.
The process of determining whether to launch the Toplevel is performed by a function, license_window which is executed at startup.
I've started by creating a simple text file, config.txt which contains the single character '0'. When the user starts the program for this first time, license_window will check to determine the contents of config.txt. If it is '0', which it will be at the first startup, then the Toplevel appears with the license info along with a close button and "do no show again" checkbox.
If the user selects the checkbox, then the '0' in config.txt is replaced by '1'. and the Toplevel should close. In this case, when the program is run again, the Toplevel won't appear since the entry in config.txt is no longer '0'.
Here's some of the relevant code, where Toplevel is named info_window. I haven't included the code for a scrolling textbox appearing directly above the close button and checkbox.
def license_window():
my_file = open("config.txt", "r+")
content = my_file. read()
if content=='0': #only create the Toplevel if the content is '0'
info_window=Toplevel()
info_window.wm_title('Software License')
def close_window(): #Simply close Toplevel if button chosen below.
info_window.destroy()
def update_config():
#Change text entry in config.txt to 1 if checkbox below is selected
my_file = open("config.txt", "r+")
content = my_file.read()
content=content.replace('0','1')
with open('config.txt', 'w') as file:
file.write(content)
close_window
close_button=Button(info_window,text="Close",command=close_window)
close_button.pack(side=BOTTOM,expand=True)
choice = IntVar()
choice_box = Checkbutton(info_window, text='Do not show this
again',variable=choice, onvalue=1, offvalue=0, command=update_config)
choice_box.pack(side=BOTTOM,expand=True)
When I run this, selecting choice_box does indeed change the content of my text file from a '0' to a '1' as desired. However, selecting choice_box does not close the Toplevel, which is what the last line of update_config() is intended to do. (I don't want the user to have to select the close button separately.)
Am I making a silly callback function error?
all. I'm working on a simple Notepad-like program that saves files and closes the program when the escape key is pressed. I mention this because it is in this method that the program runs into problems. textpad is a ScrolledText object.
This line:
`contents = self.textPad.get(self, 1.0, END)`
results in the following error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1535, in __call__
return self.func(*args)
File "todopad.py", line 24, in save_and_quit
contents = self.textPad.get(self, 1.0, END)
AttributeError: Event instance has no attribute 'textPad'
I know this is the problem, because the program executes and terminates without issue when this line is commented out. Although I don't understand the error at all.
This has been a very long-winded way of asking: How can I retrieve the contents of a ScrolledText text pad and save it to a variable or directly write it to a file? And also an explanation about the error message?
Thank you in advance.
EDIT: As requested, here is the code for the entire thing.
import sys
import Tkinter
from Tkinter import *
from ScrolledText import *
root = Tkinter.Tk(className = "TodoPad");
textPad = ScrolledText(root, width = 80, height = 20)
def start_and_open():
textFile = open('/home/colin/documents/prog/py/todopad/todo', 'r')
contents = textFile.read()
textPad.insert('1.0', contents)
textFile.close()
def save_and_quit(self):
textFile = open('/home/colin/documents/prog/py/todopad/todo', 'w')
#contents = self.textPad.get(self, 1.0, END) # The line in question
#textFile.write(contents)
textFile.close()
root.destroy()
textPad.pack()
root.bind('<Escape>', save_and_quit)
root.after(1, start_and_open)
root.mainloop()
Since I have posted the whole thing I may as well explain the rationale behind everything. It's supposed to be a fast little thing that opens a to-do list and displays what's already on the list in the text box. I make whatever edits I like, then it saves before closing when I hit escape, problem being is that it doesn't like closing because of the line that I mentioned previously in my post.
First of all, kudos on identifying the problem.
Placing the Widget
To answer what is going wrong: you need to actually place the widget into the window frame. You have a choice between .grid() and .pack(). The first allows you to pick exactly where you want it to go, the second puts in a (technically) default location.
Right now, the instance of your widget is not preset, so your program has no idea where to pull the value from. You have to set a location. i would recommend using .grid(), but for the example .pack() will work as well.
textPad = ScrolledText(root, width = 80, height = 20)
textPad.pack()
Try this, and see if it works. This should fix it, but I could be wrong.
Do NOT just do
textPad = ScrolledText(root, width = 80, height = 20).pack()
The pack() function returns a NULL and will nullify your widget.
Your Issue With Self
Finally, why are you using self at all? You are not using any classes--you need to globalize the variable. The error that is thrown is a result of your program not knowing what class you are pulling the self instance from. Remove the self variables from the program, and put this into the function:
global textPad
This will make it global and all functions will be able to use it.
This should solve all the problems you have right now. However, give it a try and report what happens.
Here are some resources on global variables, getting input from widgets, and saving to files;
http://www.python-course.eu/python3_global_vs_local_variables.php
http://effbot.org/tkinterbook/text.htm
http://www.afterhoursprogramming.com/tutorial/Python/Writing-to-Files/
Happy coding, and best of luck!!
I am creating test scripts using Python. I need to have a message displayed to the user while the script continues to run. This is to have some status update , for eg: "Saving test results" which should not wait for the user to click "Ok". Essentially , I need to create a message that pops up and closes without the user having to do it.
Currently,I am using easygui module for adding GUI.Easygui can be used for creating such message boxes but they cannot be closed in the code and need to wait for the user to close them for the script to continue running.
Thanks in advance for your time and help.
Kavitha
To forcibly remove on timeout a message box created with easygui you could use .after() method:
from Tkinter import Tk
from contextlib import contextmanager
#contextmanager
def tk(timeout=5):
root = Tk() # default root
root.withdraw() # remove from the screen
# destroy all widgets in `timeout` seconds
func_id = root.after(int(1000*timeout), root.quit)
try:
yield root
finally: # cleanup
root.after_cancel(func_id) # cancel callback
root.destroy()
Example
import easygui
with tk(timeout=1.5):
easygui.msgbox('message') # it blocks for at most `timeout` seconds
easygui is not very suitable for your use case. Consider
unittestgui.py or Jenkins.
If you have started to create a GUI, you should be able to use the textbox() function. A text box could be used as a place for your status messages, rather than making a separate dialog window appear.
I got the following description of textbox() here:
textbox(msg='', title=' ', text='', codebox=0)
Display some text in a
proportional font with line wrapping at word breaks. This function is
suitable for displaying general written text. The text parameter
should be a string, or a list or tuple of lines to be displayed in the
textbox.