I'd like to create an Entry with Tkinter where the user can type its telephone number and the text dynamically changes in order that once finished it becomes like +34 1234567890.
In my code the function .icursor(n), used to set the cursor position, at first does not work properly, but then, surpassed the prefix, it does.
This is my code snippet (It belongs to a much larger one).
from Tkinter import *
def TelephoneCheck(self,Vari):
Plain = Vari.get()
Plain = list(Plain)
Plain_flat = []
for element in Plain:
try:
check = int(element)
Plain_flat.append(element)
except: pass
if len(Plain_flat) > 2:
Plain_flat.insert(2,' ')
Plain = ''.join(Plain_flat)
Plain = '+'+Plain
self.istn.set(Plain)
self.InsertTelephoneNumber.icursor(len(Plain))
def CreateInsertTelephoneNumber(self,X,Y,color='white'):
self.istn = StringVar()
self.istn.trace('w', lambda name, index, mode, istn=self.istn: self.TelephoneCheck(istn))
self.InsertTelephoneNumber = Entry(Body,textvariable=self.istn)
self.InsertTelephoneNumber.config(bg=color)
self.InsertTelephoneNumber.place(height=20,width=230,y=Y+27,x=X+245)
def LabelBody(self,X,Y):
TelephoneText = Label(Body,text='Telephone Number *')
TelephoneText.place(y=Y+4,x=X+243)
self.CreateInsertTelephoneNumber(X,Y)
As you see, theoretically, the position should be setted at the end of the string everytime the user adds a number.
I can not understand why it works like a charm only after the prefix and not when the first number is typed (It results as +(Cursor here)3 instead of +3(Cursor here)).
If more code is needed I will update the post.
Thanks for your time and help!
The problem is that you're setting the cursor, but then the underlying widget sets the cursor the way it normally does. Because you're inserting characters into the widget in the middle of Tkinter processing a key press and release, it gets confused. For example, on the very first keystroke it thinks the cursor should be at position 1, but you've inserted a character after that position so the cursor ends up between characters.
The simplest solution is to schedule your change to happen after the default behavior by using after_idle:
Body.after_idle(self.InsertTelephoneNumber.icursor, len(Plain))
Related
I tried asking this question earlier, but I think this is a better way to ask.
To start:
I am using Windows, and Python 3, with Tkinter.
I am trying to, on button press, update the first row of my grid, namely page_label to add a single character over time, and I'm currently attempting this using time.sleep() inside a for loop.
This works extremely well when using sys.stdout.write() without a gui, but I couldn't manage to get anything to write to a label directly, rather than my terminal.
So, I figured I should be able to use the .config from tkinter, and just update the label for each character, with a small time delay between each update.
However, when I run the following code, the time delay stacks and the update to the config doesn't run until the end.
So, when I first run the app, I get my initialized text in page_label.
Then after what I assume is the completion of the for loop, the final new statement displays as the label updates.
I would be happy with either my current workaround to work, or if there is a nice, clean way to get stdout to write to the label directly, I'd be extremely pleased.
import time
from tkinter import *
from tkinter.ttk import *
from PIL import Image, ImageFont, ImageTk, ImageDraw
import json
root = Tk()
root.title('actiread test')
root.iconbitmap("C:\\Users\\nicho\\Desktop\\Code Puzzles\\actiread\\actireadico.ico")
root.geometry("800x800")
#button triggering time delay text
def get_input():
words = ""
statement = "I need this to print in time delay"
for char in statement:
words = words + char
page_label.config(text = words)
time.sleep(0.2)
#creating updating label
page_label = Label(root, text = "I want to start with an introduction")
#creating button
entry_button = Button(root, text = "Start", command = get_input)
page_label.grid(row = 0, column = 0, columnspan = 2)
entry_button.grid(row = 1, column = 0, pady = 10, sticky = EW)
root.mainloop()
Please ignore the useless imports, I have more to add to the program that I believe will require them.
If it helps, What I'm trying to accomplish will eventually require user inputs that will be saved for later use, and I'll be moving through several versions of this to move through a storyline.
TIA!
The problem is that, when you use time.sleep(), it blocks the tkinter event loop from processing events and your GUI is stuck since it cannot process events. The right way to do this, is by using root.after(ms, func, *args) method which will repeat a function after a given amount of time.
def get_input(words="",count=0):
statement = "I need this to print in time delay"
if count < len(statement):
words += statement[count] # Index and get the current letter and add to the current string
page_label.config(text=words)
count += 1
root.after(200,get_input,words,count) # Call the function passing arguments words and count
And another hacky way around this is to use root.update inside the for loop to force tkinter to process the events, but this is not a good practice.
And a relatively easier solution might be to use global variables instead to understand better:
count = 0
words = ""
def get_input():
global count, words
statement = "I need this to print in time delay"
if count < len(statement):
words += statement[count]
page_label.config(text = words)
count += 1
root.after(200,get_input)
Do note that 200 passed on, is the time for delay in milliseconds, which is same as 0.2 seconds that was passed onto time.sleep
This question already has an answer here:
Python tkinter listbox bind on <Button-1> only works on second click
(1 answer)
Closed 1 year ago.
I was creating simple listbox containing numbers from 0 to 9. I wanted to print number when it get clicked so i bind list box with Button-1. Im facing problem that is when ever i select any number and try to get its location using list_box.curselection() it does not print any thing(return empty tuple), if i click on on any other number then it print previous selected number. I want to get current selected number.
from tkinter import *
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0,10):
list_box.insert("end",i)
def def_fun(event):
print(list_box.curselection())
list_box.bind("<Button-1>",def_fun)
root.mainloop()
You don't have to bind to <Button-1> or anything, there is a virtual event with Listbox that you can use here:
def def_fun(event):
print(event.widget.curselection()) # The widget that triggers the event is event.widget
list_box.bind("<<ListboxSelect>>",def_fun) # Gets triggered each time something is selected
Just in case you are wondering why the Button-1 did not work, it is because there is a delay, the delay might be due to binding order, you can read more about it here but here is a gist:
In the default case, your binding on <Key> happens before the class binding, and it is the class binding where the text is actually inserted into the widget. That is why your binding always seems to be one character behind.
Change the binding to releasing mouse button, this will also be more user friendly (for example if they accidentally clicked on a selection they didn't want to select, they can move their mouse to a one they want and only releasing will call the function):
from tkinter import Tk, Listbox
def def_fun(event=None):
print(list_box.curselection())
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0, 10):
list_box.insert("end", i)
list_box.bind("<ButtonRelease-1>", def_fun)
root.mainloop()
Another option if you want to call the function on select is either use #CoolCloud answer or you can also set a delay like this (although it will most certainly work in 99.9% of cases, there might be a case where it doesn't):
list_box.bind("<Button-1>", lambda e: root.after(10, def_fun))
The reason is that .curselection() gets the current selection but Button-1 is triggered before anything gets selected so it will print the previous selection because that is what was selected before and where the current selection is now, and then immediately after this, it will move the current selection to the item you clicked.
Important (because it may cause hard-to-debug issues):
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
Also:
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.
first of all let me start off by saying I started coding around 6 weeks ago in self-study, so it is very likely, that the approach may still be a total mess.
I have a program running (fine for me) in cli, but I want to make it usable for people that faint, when they see plain white text on a black background which they have to operate without a mouse.
Besides my main window, which is running in the background I wanted to have a message window, which displays the information if all necessary files where selected. which is shown below.
files_to_open = {'File_1':'', 'File_2':'', 'File_3':''}
def selectfiles_window():
global message_window
message_window = Tk()
...
content = Label(message_window, text=get_open_file_status_txt(), **txt_status_general)
content.pack(side='top')
button_select_file1 = Button(message_window,text = 'File 1',font = txt_general['font'],command = lambda:(
select_file_action(name='File 1', filetypename='Excel workbook', extension='*.xlsx'),
content.configure(text=get_open_file_status_txt())))
button_select_file1(side='bottom')
message_window.mainloop()
def select_file_action(name, filetypename, extension):
global files_to_open
files_to_open[name] = filedialog.askopenfilename(title = f'Select {name}', filetypes=[(filetypename, extension)])
def get_open_file_status_txt():
global files_to_open
message =f'''
[File_1] is {"NOT SET" if (files_to_open["File_1"] == "") else "SET"}'''
return message
I expected, that the text is updated after the filedialog was closed (which is partly working as expected).
Now what I don't understand: If I click the button to select File_1 and cancel it for the first time, the value for key File_1 is set to (). Any time after that, if I click the button to select File_1 and cancel it, the value for key File_1 is set to ''. If I select a file the path is saved correctly as value (also on the first attempt). If I cancel it is set again to ''.
Anybody an idea about why the value is set to () on the first cancel but afterwards runs as expected?
I would also be grateful for a different solution to update the text, if my approach is totally off.
Thank you and best regards,
Thomas
Turns out, that the function call was not the issue, rather that it is a (strange to me but maybe intended) behavior of filedialog.askopenfilename, which returns an empty tuple if cancel is selected on first call but an empty string on every further canceled calls.
I am writing a program with PyGTK for navigating large (>20 MB) text files. I am using a TextViewer widget for this, which handles them quite well except it takes several seconds to finish filling the corresponding TextBuffer. Meanwhile, I also have several dialogs that require filtering on their input (only hexadecimal digits or something). I pass the characters I want to allow and the name of a signal to this function:
def FilterText(self, chars, signal):
def Filt(entry, text, length, position):
position = entry.get_position()
chrs = set(chars)
realtext = ''.join([c for c in text if c in chrs])
if len(realtext) > 0:
entry.handler_block_by_func(Filt)
entry.insert_text(realtext, position)
entry.handler_unblock_by_func(Filt)
newPos = position + len(realtext)
gobject.idle_add(entry.set_position, newPos)
entry.stop_emission(signal)
return Filt
And then connect the result to the Entry widget's handler for that signal. This works, except that while the TextBuffer is being filled, none of the entry.set_position calls that were queued up get run until it finished. The result is that the cursor is stuck at the beginning of the Entry and everything typed is backwards which is, needless to say, quite annoying. This is presumably because there is no idle time until the TextBuffer is filled. Is there any way to work around this and allow the correct behavior when typing into a filtered Entry widget? (It should be possible, as no such problem is encountered with an unfiltered one) Calling entry.set_position directly doesn't work for some reason.
Finally figured it out--change the call
gobject.idle_add(entry.set_position, newPos)
To
gobject.timeout_add(0, entry.set_position, newPos)
Since entry.set_position returns None, it will call it once immediately, then never again, doing exactly what I wanted.
I would like to be able to double click on test,
in a Tkinter Text widget, and have it select test (and exclude the comma).
Here is what I've tried:
import Tkinter as tk
def selection_mod(event=None):
result = aText.selection_get().find(',')
if result > 0:
try:
aText.tag_add("sel", "sel.first", "sel.last-1c")
except tk.TclError:
pass
lord = tk.Tk()
aText = tk.Text(lord, font=("Georgia", "12"))
aText.grid()
aText.bind("<Double-Button-1>", selection_mod)
lord.mainloop()
The first issue is that <Double-Button-1> seems to trigger the handler before the selection is made, producing:
TclError: PRIMARY selection doesn't exist or form "STRING" not defined
The second issue is that even when using a binding that works,
my selection tag doesn't seem to do anything.
It doesn't even raise an error, and I've tried without the except tk.TclError:.
Your binding is happening before the default bindings occur. Thus, the selection doesn't yet exist when your binding fires. Because your binding tries to get the selection, it fails with the error that you see.
You will need to arrange for your binding to happen after the class bindings. A cheap hack is to use after to execute your code once the default bindings have a chance to work. Or, you can use the bindtag feature to make sure your binding fires after the default bindings.
The second problem is that you don't clear the old selection before setting the new. You'll want to do tag_remove to first remove the existing selection. Otherwise, the comma (if it was somehow selected) will remain selected since all you're doing is re-applying the tag to text that already has the tag.
However, double-click doesn't normally capture the comma so I don't quite understand then point of your code. At least, when I test it on OSX it doesn't include the comma.
Here is what I came up with thanks to Bryan's answer:
import Tkinter as tki # tkinter in Python 3
def selection_mod(event=None):
result = txt.selection_get().find(',')
if result > 0:
fir, sec = txt.tag_ranges("sel")
txt.tag_remove("sel", "sel.first", "sel.last")
txt.tag_add("sel", fir, str(sec)+"-1c")
root = tki.Tk()
txt = tki.Text(root, font=("Georgia", "12"))
txt.grid()
txt.bind("<Double-Button-1>", lambda x: root.after(20, selection_mod))
root.mainloop()
It's worth noting that I'm using Windows 7, and according to Bryan,
OSX doesn't include the comma when you double click a word.