Validatecommand is called when I edit the entry programmatically - python

I am trying to create an entry with a very special validation. For that, I'm playing dirty with validatecommand. However, I got a problem:
When something in the entry is deleted, I can't tell if it was done with the delete or backspace key (and tutorial pages like this: https://www.pythontutorial.net/tkinter/tkinter-validation/ no substitution is indicated provide that information).
So, I decided to add a bind. The function that I link returns "break" and must take care of removing a character and inserting a space in its place.
The problem, as the title says, is that validatecommand even validates entry edits made with the insert and delete methods.
To avoid this I considered disabling validation (which always returns True) while I make the corresponding edits. But this could cause other entries not to be validated.
Is there a way to skip that validation when programmatically editing an entry?
I leave you this code so that you have some basis to help me:
from functools import partial
class ChrFormat:
def __init__(self):
self.charvalidators = []
def register_in(self, widget):
widget.config(validate="key", validatecommand=(widget.register(partial(self, widget)), "%d", "%i", "%P", "%s", "%S"))
def add(self, obj):
self.charvalidators.append(obj)
def __call__(self, widget, accion, index, new_text, old_text, char):
accion = int(accion)
index = int(index)
if(len(char) == 1):
if(accion == 1):
if(index < self.width):
for validator in self.charvalidators[index:]:
if(not isinstance(validator, str)):
break
index += 1
else:
return False
if(validator(char)):
widget.delete(index)
widget.insert(index, char)
widget.icursor(index + 1)
return (accion != 1)
def apply(self):
self.width = len(self.charvalidators)
self.null = "".join((part if(isinstance(part, str)) else " ") for part in self.charvalidators)
fecha = ChrFormat()
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.add("-")
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.add("-")
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.add(str.isdecimal)
fecha.apply()
from tkinter import ttk
import tkinter as tk
root = tk.Tk()
sv = tk.StringVar()
entrada = ttk.Entry(textvariable=sv)
entrada.pack()
fecha.register_in(entrada)
sv.set(fecha.null)
I think I didn't explain myself well, sorry. What I'm looking for is that when the user presses backspace, it deletes a number and puts a space in its place. And something similar with delete. But I need to know which side of the cursor to put that space on.
Obviously, natural validation is the right thing for this, maybe do the validation through binds.
For those who know a bit about Clipper programming languaje, I want to mimic what happens when a picture is placed, such as '#r 999.999'. I would post a video, but I'm not in a good time to record and I didn't find any videos to prove it.

Is there a way to skip that validation when programmatically editing an entry?
The simplest solution is to set the validate option to "none" before making the edits. You then can turn the validation back on via after_idle as documented in the official tk documentation
widget.configure(validate="none")
widget.delete(index)
widget.insert(index, char)
widget.icursor(index + 1)
widget.after_idle(lambda: widget.configure(validate="key"))

Related

page GUI - Combobox pass variable

New to GUI. Not quite getting there. I used page and get can get buttons to do something (click on a button and get a response). With Combobox, I can't pass a value. Searched here, tried many things, watched a few hours of youtube tutorials.
What am I doing wrong below? This is the code page generates (basically) then I added what I think I need to do to use the Combobox.
I am just trying to have 1,2,3 in a combo box and print out the value that is chosen. Once I figure that out I think I can actually make a simple GUI that passes variables I can then program what I want to do with these variables being selected.
class New_Toplevel_1:
def __init__(self, top):
self.box_value = StringVar()
self.TCombobox1 = ttk.Combobox(textvariable=self.box_value)
self.TCombobox1.place(relx=0.52, rely=0.38, relheight=0.05, relwidth=0.24)
self.TCombobox1['values']=['1','2','3']
self.TCombobox1.configure(background="#ffff80")
self.TCombobox1.configure(takefocus="")
self.TCombobox1.bind('<<ComboboxSelected>>',func=select_combobox)
def select_combobox(self,top=None):
print 'test combo ' # this prints so the bind works
self.value_of_combo = self.ttk.Combobox.get() # this plus many other attempts does not work
It's hard to know what you're actually asking about, since there is more than one thing wrong with your code. Since you say the print statement is working, I'm assuming the only problem you have with your actual code is with the last line.
To get the value of the combobox, get the value of the associated variable:
self.value_of_combo = self.box_value.get()
Here's a working version where I fixed the other things that were wrong with the program:
from tkinter import *
from tkinter import ttk
class New_Toplevel_1:
def __init__(self, top):
self.box_value = StringVar()
self.TCombobox1 = ttk.Combobox(textvariable=self.box_value)
self.TCombobox1.place(relx=0.52, rely=0.38, relheight=0.05, relwidth=0.24)
self.TCombobox1['values']=['1','2','3']
self.TCombobox1.configure(background="#ffff80")
self.TCombobox1.configure(takefocus="")
self.TCombobox1.bind('<<ComboboxSelected>>',func=self.select_combobox)
def select_combobox(self,top=None):
print('test combo ') # this prints so the bind works
self.value_of_combo = self.box_value.get()
print(self.value_of_combo)
root = Tk()
top = New_Toplevel_1(root)
root.mainloop()
Note: I strongly advise you not to start with place. You should try to learn pack and place first. I know place seems easier, but to get the most responsive and flexible GUI you should leverage the power of pack and grid.

Tkinter- Open and Save the UI with all the widgets. Trouble while incrementing the rows

Here, is the piece of code that I have written. I have to make Open and Save Button functional. So, my both functions are working fine. I am able to Save and Load UI but the basic problem is that after loading, when I click on add rows, the rows doesn't added in below the already existed row. It has been a week working on it. I am in trouble and doesnt know the wayout
from tkinter import *
import dill
from collections import OrderedDict
class Program:
def __init__(self):
self.row=0
self.entries=[]
self.current_widget=0
self.Ordered_dictionary_for_entry_widget=OrderedDict()
self.values_for_entry_dictionary=[]
self.AddButton = Button(text="Add Row", command=self.add_button_command)
self.AddButton.grid(column=0,row=0)
self.save_button=Button(text="save",command=self.save_button_command)
self.save_button.grid(column=0,row=1)
self.load_button=Button(text="Open",command=self.Open_button_command)
self.load_button.grid(column=0,row=2)
self.total_entries_length=len(self.entries)
def add_button_command(self):
self.entry=Entry()
self.entry.grid(column=1,row=self.row)
self.entries.append(self.entry)
self.row=self.row+1
def save_button_command(self):
self.total_entries_length=len(self.entries)
print(self.total_entries_length)
for widget in self.entries:
self.Ordered_dictionary_for_entry_widget["Name"+str(self.current_widget+1)]=widget.get()
self.current_widget=self.current_widget+1
with open("example_fully_functional.txt","wb") as f:
dill.dump(self.Ordered_dictionary_for_entry_widget,f)
def Open_button_command(self):
print("Total entries length",self.total_entries_length)
with open("example_fully_functional.txt","rb") as f:
self.Ordered_dictionary_for_entry_widget=dill.load(f)
for key,values in self.Ordered_dictionary_for_entry_widget.items():
self.values_for_entry_dictionary.append((values))
print(self.values_for_entry_dictionary)
for i in (self.values_for_entry_dictionary):
self.entry=Entry()
self.entry.grid(column=1,row=i)
self.entries.append(self.entry)
print("Entry loaded",self.entries_loaded)
#Insert the entries back into the UI
[self.entries.insert(0,self.values_for_entry_dictionary) for
self.entries,self.values_for_entry_dictionary in
zip(self.entries,self.values_for_entry_dictionary)]
program = Program()
mainloop()
Ok to answer the direct question: self.row is not incremented in Open_button_command so it is inaccurate when add_button_command tries to add a new Entry,
for i in (self.values_for_entry_dictionary):
self.entry=Entry()
self.entry.grid(column=1,row=i)
self.entries.append(self.entry)
## THIS ONE HERE ##
self.row+=1
#####
I want to suggest a better solution then keeping track of the next column in a variable but before I can we need to fix up a few things, first in Open_button_command:
for i in (self.values_for_entry_dictionary):
self.entry=Entry()
self.entry.grid(column=1,row=i)
...
You are iterating over the values that need to be inserted into the entries not the indices, to get the indices to use in .grid you can use range(len(X)) instead:
for i in range(len(self.values_for_entry_dictionary)):
or better yet use enumerate to make the Entries and fill them at the same time:
for i,value in enumerate(self.values_for_entry_dictionary):
self.entry=Entry()
self.entry.grid(column=1,row=i)
self.entry.insert(0,value)
...
this way you don't need this:
[self.entries.insert(0,self.values_for_entry_dictionary) for
self.entries,self.values_for_entry_dictionary in
zip(self.entries,self.values_for_entry_dictionary)]
which overrides self.entries and self. self.values_for_entry_dictionary during the loop so a lot of information gets messed up during that, use enumerate instead.
Once that is cleaned up and self.entries will consistently be a list of all the Entry widgets in window the self.row should always be equal to len(self.entries) so it would be much preferable to use a property to calculate it every time:
class Program:
#property
def row(self):
return len(self.entries)
...
Then comment out any statement trying to set self.row=X since you don't have or need a setter for it. Every time you use self.row it will calculate it with the property and add_button_command will always add a new entry to the bottom.
However in Open_button_command you are still creating new widgets even if there is already an Entry in the window, it would make more sense to check if there is already one that can be reused:
def Open_button_command(self):
...
for i,value in enumerate(self.values_for_entry_dictionary):
if i<len(self.entries):
self.entry = self.entries[i]
self.entry.delete(0,"end") #remove any previous content
else:
self.entry=Entry()
self.entry.grid(column=1,row=i)
self.entries.append(self.entry)
# either way:
self.entry.insert(0,value)
Although this still breaks if you hit the open button twice since you do not reset self.values_for_entry_dictionary when opening a file, so the values in the file are added to any already open Entries so it would be a good idea to also reset it when opening a file:
def Open_button_command(self):
self.values_for_entry_dictionary=[]
...
If you wanted help with a working but buggy code you might consider submitting it for review and I'd be happy to provide other tips but I think this is sufficient to at least get the example code working as expected.

Tkinter Focusout Entry Validation

I have seen several questions on tkinter entry validation here but each one seems to stick to validate="key" option.
While this is great for interactive validation, what i want is a "focusout" validation.
More particularly I am looking to validate an email field. Here's the code I have tried so far but it doesn't work.
import Tkinter as tk
import re
master = tk.Tk()
def validateEmail(P):
x = re.match(r"[^#]+#[^#]+\.[^#]+", P)
return (x != None)
vcmd = (master.register(validateEmail), '%P')
emailentry = tk.Entry(master, validate="focusout", validatecommand=vcmd)
emailentry.pack()
b = tk.Button(master, text="Login")
b.pack()
tk.mainloop()
Any ideas on how to validate email entry please ?
%S represents the string being inserted, if any. This is only meaningful for validation on text insertion. When the widget loses focus, no character is being inserted so this parameter will always be an empty string. Since it is an empty string, it will always fail your validation.
You should use %P instead, which represents the whole string.
Also, strictly speaking, the validation function should return a boolean rather than an object. You should save the result of the match in a variable, then return something like return (match is not None)

What is the significance of the number 4 when one feeds a callback to the show argument of a Tkinter Entry Box?

Question
Why is my random ascii character selector function outputting fours, and what is the significance of the number four in this context? Why am I not recieving an error message?
Remember, the question is not about how to solve the issue, it is about why that particular number was output.
Background and Code
I am trying to creating a basic email client. I thought that it would be cool for my password box to show random characters instead of the obvious *. So, I created a function which chose a random ascii letter.
import random
import string
def random_char():
char_select = random.randrange(52)
char_choice = string.ascii_letters[char_select]
return char_choice
When I run this in an interactive terminal, it spits out a random letter. But, when I run it through my widget
self.Password = Entry (self, show = lambda: random_char())
I am met with a bunch of fours.
Extra Credit
If you have the time, please visit my related question, How to have a Tkinter Entry box repeat a function each time a character is inputted?
The show parameter accepts a value not a callback. Tkinter is taking your callback object and trying to convert it to a string and that is what you get when you type in the Entry box.
Instead you can re-configure your Entry after you type by using binding:
def key(event):
entry.configure(show = random_char())
entry = tk.Entry (self)
entry.pack()
entry.bind("<Key>", key)
EDIT
Bryan Oakley is correct in that this will change all the characters to the same single random character as you type. Showing different random characters as you type is not the way you are supposed to use the Entry widget. You can try something like:
def key(event):
global real_password
global garbage
current_len = len(v.get())
if event.char and event.char in string.ascii_letters:
real_password += event.char
garbage += random_char()
garbage = garbage[:current_len]
v.set(garbage)
v = tk.StringVar()
real_password = ""
garbage = ""
entry = tk.Entry (self, textvariable = v)
entry.pack()
entry.bind("<KeyRelease>", key)
Of course there are lots of limitations, the last character typed is changed when the key is released not when is pressed, so you have to type fast :) , there is not control over the cursor movement keys etc. But anyway it was fun trying.

Making Entry widget "disabled" like in windows calc

I've got a problem with Entry widget while making a copy of Windows Calc.
I have made buttons like in windows calc and I also bind the keyboard 1234567890 + - / * % buttons, to make the same things as the calc buttons.
The mainly problem was that I wanted the Entry to store only numbers and let user input only numbers... but after searching many topics about validatecommand and also looking at windows calc I decided that validatecommand isn't the thing I need - I don't know how to make it validate every character the user inputs to the entry box and after making the keyboard binds, when I am in entrybox and press "1" to write the number it does it two times, because the keyboard event binding inserts the "1" to the entry box too.
So, the thing I want to make is to make entry widget work like the Windows Calc.exe entry box.
The windows calc entry box doesn't let you insert any other character then numbers and also doesn't let you to put your cursor into the entry box...,
it looks like this:
-entrybox is disabled BUT it looks like ENABLED
-numbers and operations can be made by calc buttons or by keyboard buttons
I tried getting this effect by disabling the entry widget at start, and making all buttons functions like that:
-enable the entry widget
-insert the number (the widget must be in enabled? or normal? (don't remember the name) state to let you insert something to it)
-disable the entry widget
It works like I want... but it doesn't look like I want it to look. Is there any possibility to change Entry widget disabled bg color to normal?
Or maybe is there another way to make such entry box? :S
The way to do it is with the validatecommand and validate options of the entry widget. This scenario is precisely what those features are for.
You say you "don't know how to make it validate every character the user inputs to the entry box". If you set the validate attribute to "key", that will cause your validate command to be called on every keypress.
Unfortunately, this is a somewhat under-documented feature of Tkinter, though it's documented quite well for Tk. Here's a working example which performs some very rudimentary checks:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# define a command to be called by the validation code. %P
# represents the value of the entry widget if the edit is
# allowed. We want that passed in to our validation comman so
# we can validate it. For more information see
# http://tcl.tk/man/tcl8.5/TkCmd/entry.htm#M7
vcmd = (self.register(self._validate), '%P')
e = tk.Entry(self, validate="key", validatecommand=vcmd)
e.pack()
def _validate(self, P):
# accept the empty string, "." or "-." (to make it possible to
# enter something like "-.1"), or any string that can be
# converted to a floating point number.
try:
if P in (".", "-", "-.", ""):
return True
n = float(P)
return True
except:
self.bell()
return False
app=SampleApp()
app.mainloop()
If you search this site for [tkinter] validatecommand you'll find many other examples.

Categories

Resources