how can i ensure my ttk.entry's invalid state isn't cleared when it loses focus?

  • Last Update :
  • Techknowledgy :

Here's a complete working example. If the entry widget contains the word "invalid", the state will be changed to "invalid". You can then click out of the widget to see that the state remains invalid:

try:
import Tkinter as tk
import ttk
except ImportError:
import tkinter as tk
from tkinter import ttk

class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)

# give invalid entries a red background
style = ttk.Style()
style.map("TEntry", background=[('invalid', "red")])

self.entryVar = tk.StringVar()
self.entry = ttk.Entry(self, textvariable=self.entryVar)

# this label will show the current state, updated
# every second.
self.label = tk.Label(self, anchor="w")
self.after_idle(self.updateLabel)

# layout the widgets
self.entry.pack(side="top", fill="x")
self.label.pack(side="bottom", fill="x")

# add trace on the variable to do custom validation
self.entryVar.trace("w", self.validate)

# set up bindings to also do the validation when we gain
# or lose focus
self.entry.bind("<FocusIn>", self.validate)
   self.entry.bind("<FocusOut>", self.validate)

      def updateLabel(self):
      '''Display the current entry widget state'''
      state = str(self.entry.state())
      self.label.configure(text=state)
      self.after(1000, self.updateLabel)

      def validate(self, *args):
      '''Validate the widget contents'''
      value = self.entryVar.get()
      if "invalid" in value:
      self.entry.state(["invalid"])
      else:
      self.entry.state(["!invalid"])

      if __name__ == "__main__":
      root = tk.Tk()
      Example(root).pack(fill="both", expand=True)
      root.mainloop()

If you can't beat them, join them. Use validate='focus' with a validatecommand that returns whether or not the text is valid, as if you had wanted to check this on focus events all along. And continue to set the invalid flag when something really changes. For example:

class MyEntry(ttk.Entry):

   def __init__(self, master):
   self.var = tk.StringVar(master)
super().__init__(master,
   textvariable = self.var,
   validate = 'focus',
   validatecommand = self.is_valid)
self.var.trace('w', self.revalidate)
self.revalidate()

def revalidate(self, * args):
   self.state(['!invalid'
      if self.is_valid()
      else 'invalid'
   ])

def is_valid(self, * args):
   #...
   return
      if current text is valid...

You could instead simply persist the state flag across focus events:

class ValidationDisabledEntry(ttk.Entry):
   def __init__(self, * args, ** kwargs):
   super().__init__( *
      args,
      validate = 'focus',
      validatecommand = lambda * a: 'invalid'
      not in self.state(), **
      kwargs)

Here is a solution to your problem. You may probably want to adapt it to your need but it may give you an idea :

import tkinter as tk
from tkinter import ttk

class ValidatingEntry(ttk.Entry):

COLOR_VALID = "#99ff99"
COLOR_INVALID="#ff9999"

def __init__(self, master, *args, **kwargs):
self.stringVar = tk.StringVar()

tk.Entry.__init__(self, master, *args, textvariable = self.stringVar, **kwargs)

self.validatingFunction = None

self.bind("<FocusOut>",self.validation)

   def validation(self, event):

   if self.validatingFunction != None:
   if self.validatingFunction():
   self['bg']=ValidatingEntry.COLOR_VALID
   else:
   self['bg']=ValidatingEntry.COLOR_INVALID
   else:
   print("no evaluation possible for the content of this entry")


   if __name__ == "__main__":
   app = tk.Tk()

   entry = ValidatingEntry(app)
   entry.validatingFunction = lambda : 'foo' in entry.stringVar.get()
   entry.pack()

   entry2 = ValidatingEntry(app)
   entry2.validatingFunction = lambda : 'bar' in entry2.stringVar.get()
   entry2.pack()


   app.mainloop()

Suggestion : 2

A checkbutton doesn't automatically set (or create) the linked variable. Therefore, your program needs to initialize it to the appropriate starting value.,Your program can specify what makes an entry valid or invalid, as well as when to check its validity. As we'll see soon, the two are related. We'll start with a simple example, an entry that can only hold an integer up to five digits long. ,If you're unsure what configuration options a widget supports, you can ask the widget to describe them. This gives you a long list of all its options. ,If you simply need a multi-line text field for a form, there are only a few things to worry about: create and size the widget (check), provide an initial value, and retrieve the text after a user has submitted the form.

If you're using Python 3.9 or newer, the build system will look in /Library/Frameworks, where ActiveState and other custom builds are typically installed.

% . / configure
   %
   make

When compiling Python versions prior to 3.9, you will need to add two new command-line options to the initial ./configure in the Python build process. The first provides the locations of the Tcl and Tk include files, and the second provides the locations of the Tcl and Tk libraries. These are usually found in two different locations (i.e., Tcl.framework and Tk.framework). You need to provide both locations for the include files and both for the libraries. Note the location of the quotes in the command below and the spaces separating the Tcl and Tk paths.

% . / configure--with - tcltk - includes = "-I/Library/Frameworks/Tcl.framework/Headers -I/Library/Frameworks/Tk.framework/Headers"--with - tcltk - libs = "/Library/Frameworks/Tcl.framework/Tcl /Library/Frameworks/Tk.framework/Tk" %
   make

When everything is built, be sure to test it out. Start Python from your terminal, e.g.

% . / python.exe

You can also get the exact version of Tcl/Tk that is being used with:

>>> tkinter.Tcl().eval('info patchlevel')

To verify the exact version of Tcl/Tk that you are running, from the Wish console type the following:

% info patchlevel

Next, you'll want to install Ruby. There are multiple ways to do this, as explained at www.ruby-lang.org. One option is to use a package manager like Homebrew. Once it's been installed (at /usr/local/bin/brew) you can install Ruby from a command prompt (e.g. Terminal) via:

% brew install ruby

Next, you'll need to download and install Ruby's Tk module, which is packaged as a Ruby gem. To do so, from the command prompt, run:

% /usr/local / opt / ruby / bin / gem install tk

To verify that everything worked, start up /usr/local/opt/ruby/bin/irb and type:

% require 'tk' %
   Tk::TK_PATCHLEVEL

Next, you'll want to install Perl. There are multiple ways to do this, as explained at www.perl.org. One option is to use a package manager like Homebrew. Once it's been installed (at /usr/local/bin/brew) you can install Ruby from a command prompt (e.g. Terminal) via:

% brew install perl

Next, you'll need to download and install Perl's Tkx module. We can grab it from CPAN. Unfortunately, at present it will not install correctly due to errors in its tests. We can bypass the tests and install it anyway. To do so, from the command prompt, run:

% /usr/local / opt / perl / bin / perl - MCPAN - e "CPAN::Shell->notest('install','Tkx')"

To check that this worked, run this from the Unix command line:

% /usr/local / opt / perl / bin / perl - MTkx - e 'print Tkx::info("patchlevel");'

Once you've installed or compiled Python, test it out to make sure Tkinter works. From the Python prompt, enter these two commands:

>>>
import tkinter
   >>>
   tkinter._test()

You can also get the exact version of Tcl/Tk that is being used with:

>>> tkinter.Tcl().eval('info patchlevel')

Run the installer, and follow along. You'll end up with a fresh install of ActiveTcl, usually located in C:\ActiveTcl. From a command prompt, you should then be able to run a Tcl/Tk 8.6 shell via:

% C: \ActiveTcl\ bin\ wish

This should pop up a small window titled "wish", which will contain your application. A second, larger window titled "Console" is where you can type in Tcl/Tk commands. To verify the exact version of Tcl/Tk that you are running, type the following:

% info patchlevel

Suggestion : 3

The insertion cursor shows where new text will be inserted. It is displayed only when the user clicks the mouse somewhere in the widget. It usually appears as a blinking vertical line inside the widget. You can customize its appearance in several ways. , Set the insertion cursor just before the character at the given index. , Sets the selection under program control. Selects the text starting at the start index, up to but not including the character at the end index. The start position must be before the end position. , You may need to figure out which character position in the widget corresponds to a given mouse position. To simplify that process, you can use as an index a string of the form '@n', where n is the horizontal distance in pixels between the left edge of the Entry widget and the mouse. Such an index will specify the character at that horizontal mouse position.

To create a new Entry widget in a root window or frame named parent:

    w = tk.Entry(parent, option, ...)