Tkinter'da Giriş pencere öğesi içeriğini etkileşimli olarak doğrulama


85

Bir tkinter Entrywidget'ındaki içeriği etkileşimli olarak doğrulamak için önerilen teknik nedir ?

Ben kullanmayla ilgili mesajları okudum validate=Trueve validatecommand=commandve bu özellikler eğer temizlenmezse gerçeği ile sınırlıdır görünür validatecommandkomut günceller Entrywidget'inin değerini.

Bu davranış göz önüne alındığında, biz bağlamak gerekir KeyPress, Cutve Pasteolaylar ve monitör / güncellemek bizim Entrybu tür etkinlikler aracılığı widget'inin değeri? (Ve kaçırmış olabileceğim diğer ilgili olaylar?)

Yoksa etkileşimli doğrulamayı tamamen unutmalı ve yalnızca FocusOutolaylar üzerinde doğrulamalı mıyız ?

Yanıtlar:


221

Doğru cevap, validatecommandwidget'ın özelliğini kullanmaktır . Ne yazık ki, bu özellik Tkinter dünyasında yeterince belgelenmemiştir, ancak Tk dünyasında yeterince belgelenmiştir. İyi bir şekilde belgelenmemiş olsa da, bağlamalara başvurmadan veya değişkenleri takip etmeden veya doğrulama prosedürünün içinden widget'ı değiştirmeden doğrulama yapmanız gereken her şeye sahiptir.

İşin püf noktası, Tkinter'in validate komutunuza özel değerler vermesini sağlayabileceğinizi bilmektir. Bu değerler size verilerin geçerli olup olmadığına karar vermeniz için bilmeniz gereken tüm bilgileri verir: düzenlemeden önceki değer, düzenleme geçerliyse düzenlemeden sonraki değer ve diğer birkaç bilgi biti. Bunları kullanmak için, bu bilgiyi validate komutunuza geçirmek için biraz voodoo yapmanız gerekir.

Not: doğrulama komutunun ya Trueda döndürmesi önemlidir False. Başka herhangi bir şey, widget için doğrulamanın kapatılmasına neden olur.

Aşağıda, yalnızca küçük harfe izin veren (ve tüm bu ilginç değerleri yazdıran) bir örnek verilmiştir:

import tkinter as tk  # python 3.x
# import Tkinter as tk # python 2.x

class Example(tk.Frame):

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

        # valid percent substitutions (from the Tk entry man page)
        # note: you only have to register the ones you need; this
        # example registers them all for illustrative purposes
        #
        # %d = Type of action (1=insert, 0=delete, -1 for others)
        # %i = index of char string to be inserted/deleted, or -1
        # %P = value of the entry if the edit is allowed
        # %s = value of entry prior to editing
        # %S = the text string being inserted or deleted, if any
        # %v = the type of validation that is currently set
        # %V = the type of validation that triggered the callback
        #      (key, focusin, focusout, forced)
        # %W = the tk name of the widget

        vcmd = (self.register(self.onValidate),
                '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        self.entry = tk.Entry(self, validate="key", validatecommand=vcmd)
        self.text = tk.Text(self, height=10, width=40)
        self.entry.pack(side="top", fill="x")
        self.text.pack(side="bottom", fill="both", expand=True)

    def onValidate(self, d, i, P, s, S, v, V, W):
        self.text.delete("1.0", "end")
        self.text.insert("end","OnValidate:\n")
        self.text.insert("end","d='%s'\n" % d)
        self.text.insert("end","i='%s'\n" % i)
        self.text.insert("end","P='%s'\n" % P)
        self.text.insert("end","s='%s'\n" % s)
        self.text.insert("end","S='%s'\n" % S)
        self.text.insert("end","v='%s'\n" % v)
        self.text.insert("end","V='%s'\n" % V)
        self.text.insert("end","W='%s'\n" % W)

        # Disallow anything but lowercase letters
        if S == S.lower():
            return True
        else:
            self.bell()
            return False

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

registerYöntemi çağırdığınızda başlık altında neler olduğu hakkında daha fazla bilgi için bkz. Giriş doğrulama tkinter


16
Bunu yapmanın doğru yolu bu. Jmeyer10'un cevabını çalıştırmaya çalıştığımda bulduğum sorunları ele alıyor. Bu bir örnek, başka yerde bulabileceklerime kıyasla doğrulamak için daha üstün belgeler sağlar. Keşke bu 5 oyu verebilseydim.
Steven Rumbalski

3
VAOV! Steven'a katılıyorum - bu, birden fazla oyu hak eden yanıt türüdür. Tkinter üzerine bir kitap yazmalısınız (ve bunu çok ciltli bir dizi yapmak için zaten yeterli sayıda çözüm gönderdiniz). Teşekkür ederim!!!
Malcolm

2
Örnek için teşekkürler. Validatekomutunun bir boole (yalnızca Doğru ve Yanlış) döndürmesi ZORUNLUDUR. Aksi takdirde, doğrulama kaldırılacaktır.
Dave Bacher

3
Bu sayfanın öne çıkarılması gerektiğini düşünüyorum .
Sağ bacak

4
"Tkinter dünyasında yeterince belgelenmemiş". LOL - neredeyse Tkiinter dünyasının geri kalanı gibi.
martineau

21

Bryan'ın kodunu inceledikten ve denedikten sonra, minimum bir girdi doğrulama sürümü ürettim. Aşağıdaki kod bir Giriş kutusu koyacak ve yalnızca sayısal rakamları kabul edecektir.

from tkinter import *

root = Tk()

def testVal(inStr,acttyp):
    if acttyp == '1': #insert
        if not inStr.isdigit():
            return False
    return True

entry = Entry(root, validate="key")
entry['validatecommand'] = (entry.register(testVal),'%P','%d')
entry.pack()

root.mainloop()

Belki de eklemeliyim ki hala Python öğreniyorum ve tüm yorumları / önerileri memnuniyetle kabul edeceğim.


1
Genellikle insanlar bunun yerine kullanır entry.configure(validatecommand=...)ve yazar , ancak bu iyi, basit bir örnek. test_valtestVal
wizzwizz4

10

Tkinter.StringVarGiriş widget'ının değerini izlemek için a kullanın . Sen değerini doğrulamak StringVarbir ayarlayarak traceüzerinde.

Giriş widget'ında yalnızca geçerli kayan sayıları kabul eden kısa bir çalışma programı.

from Tkinter import *
root = Tk()
sv = StringVar()

def validate_float(var):
    new_value = var.get()
    try:
        new_value == '' or float(new_value)
        validate.old_value = new_value
    except:
        var.set(validate.old_value)    
validate.old_value = ''

# trace wants a callback with nearly useless parameters, fixing with lambda.
sv.trace('w', lambda nm, idx, mode, var=sv: validate_float(var))
ent = Entry(root, textvariable=sv)
ent.pack()

root.mainloop()

1
Gönderiniz için teşekkürler. Tkinter StringVar .trace () yönteminin kullanımda olduğunu görmekten keyif aldım.
Malcolm

Bu hatayı neden alabileceğime dair bir fikriniz var mı? "NameError: 'validate' adı tanımlanmadı"
Armen Sanoyan

4

Bryan Oakley'in cevabını incelerken , bir şey bana çok daha genel bir çözüm geliştirilebileceğini söyledi. Aşağıdaki örnek, doğrulama amacıyla bir mod numaralandırması, bir tür sözlüğü ve bir kurulum işlevi sunar. Örnek kullanım ve basitliğinin bir gösterimi için 48. satıra bakın.

#! /usr/bin/env python3
# /programming/4140437
import enum
import inspect
import tkinter
from tkinter.constants import *


Mode = enum.Enum('Mode', 'none key focus focusin focusout all')
CAST = dict(d=int, i=int, P=str, s=str, S=str,
            v=Mode.__getitem__, V=Mode.__getitem__, W=str)


def on_validate(widget, mode, validator):
    # http://www.tcl.tk/man/tcl/TkCmd/ttk_entry.htm#M39
    if mode not in Mode:
        raise ValueError('mode not recognized')
    parameters = inspect.signature(validator).parameters
    if not set(parameters).issubset(CAST):
        raise ValueError('validator arguments not recognized')
    casts = tuple(map(CAST.__getitem__, parameters))
    widget.configure(validate=mode.name, validatecommand=[widget.register(
        lambda *args: bool(validator(*(cast(arg) for cast, arg in zip(
            casts, args)))))]+['%' + parameter for parameter in parameters])


class Example(tkinter.Frame):

    @classmethod
    def main(cls):
        tkinter.NoDefaultRoot()
        root = tkinter.Tk()
        root.title('Validation Example')
        cls(root).grid(sticky=NSEW)
        root.grid_rowconfigure(0, weight=1)
        root.grid_columnconfigure(0, weight=1)
        root.mainloop()

    def __init__(self, master, **kw):
        super().__init__(master, **kw)
        self.entry = tkinter.Entry(self)
        self.text = tkinter.Text(self, height=15, width=50,
                                 wrap=WORD, state=DISABLED)
        self.entry.grid(row=0, column=0, sticky=NSEW)
        self.text.grid(row=1, column=0, sticky=NSEW)
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        on_validate(self.entry, Mode.key, self.validator)

    def validator(self, d, i, P, s, S, v, V, W):
        self.text['state'] = NORMAL
        self.text.delete(1.0, END)
        self.text.insert(END, 'd = {!r}\ni = {!r}\nP = {!r}\ns = {!r}\n'
                              'S = {!r}\nv = {!r}\nV = {!r}\nW = {!r}'
                         .format(d, i, P, s, S, v, V, W))
        self.text['state'] = DISABLED
        return not S.isupper()


if __name__ == '__main__':
    Example.main()

4

Bryan'ın cevabı doğru, ancak hiç kimse tkinter widget'ının "geçersiz komut" özelliğinden bahsetmedi.

İyi bir açıklama burada: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html

Kopuk bağlantı durumunda metin kopyalayın / yapıştırın

Giriş pencere öğesi ayrıca validatecommand False döndürdüğünde çağrılan bir geri çağrı işlevini belirten geçersiz komut seçeneğini de destekler. Bu komut, pencere aracının ilişkili metin değişkeninde .set () yöntemini kullanarak widget'taki metni değiştirebilir. Bu seçeneğin ayarlanması, doğrulama komutunun ayarlanmasıyla aynı şekilde çalışır. Python işlevinizi sarmak için .register () yöntemini kullanmanız gerekir; bu yöntem, sarılmış işlevin adını bir dizge olarak döndürür. Ardından, geçersiz komut seçeneğinin değeri olarak bu dizge veya ikame kodlarını içeren bir demetin ilk öğesi olarak geçeceksiniz.

Not: Nasıl yapılacağını anlayamadığım tek bir şey var: Bir girişe doğrulama eklerseniz ve kullanıcı metnin bir bölümünü seçip yeni bir değer yazarsa, orijinal değeri yakalayıp sıfırlamanın bir yolu yoktur. giriş. İşte bir örnek

  1. Giriş, 'validatecommand' uygulayarak yalnızca tam sayıları kabul edecek şekilde tasarlanmıştır
  2. Kullanıcı 1234567'yi girer
  3. Kullanıcı "345" i seçer ve "j" ye basar. Bu, iki eylem olarak kaydedilir: '345'in silinmesi ve' j'nin eklenmesi. Tkinter, silme işlemini görmezden gelir ve yalnızca 'j'nin eklenmesiyle çalışır. "validatecommand" False değerini döndürür ve "geçersiz komut" işlevine iletilen değerler aşağıdaki gibidir:% d = 1,% i = 2,% P = 12j67,% s = 1267,% S = j
  4. Kod bir 'geçersiz komut' işlevi uygulamazsa, 'validatecommand' işlevi 'j'yi reddeder ve sonuç 1267 olur. Kod bir' geçersiz komut 'işlevi gerçekleştirirse, orijinal 1234567'yi kurtarmanın bir yolu yoktur. .

3

Aşağıda, kullanıcının yalnızca rakamları girmesine izin veren giriş değerini doğrulamanın basit bir yolu verilmiştir:

import tkinter  # imports Tkinter module


root = tkinter.Tk()  # creates a root window to place an entry with validation there


def only_numeric_input(P):
    # checks if entry's value is an integer or empty and returns an appropriate boolean
    if P.isdigit() or P == "":  # if a digit was entered or nothing was entered
        return True
    return False


my_entry = tkinter.Entry(root)  # creates an entry
my_entry.grid(row=0, column=0)  # shows it in the root window using grid geometry manager
callback = root.register(only_numeric_input)  # registers a Tcl to Python callback
my_entry.configure(validate="key", validatecommand=(callback, "%P"))  # enables validation
root.mainloop()  # enters to Tkinter main event loop

Not: Bu örnek, calc gibi bir uygulama oluşturmak için çok yararlı olabilir.


2
import tkinter
tk=tkinter.Tk()
def only_numeric_input(e):
    #this is allowing all numeric input
    if e.isdigit():
        return True
    #this will allow backspace to work
    elif e=="":
        return True
    else:
        return False
#this will make the entry widget on root window
e1=tkinter.Entry(tk)
#arranging entry widget on screen
e1.grid(row=0,column=0)
c=tk.register(only_numeric_input)
e1.configure(validate="key",validatecommand=(c,'%P'))
tk.mainloop()
#very usefull for making app like calci

2
Merhaba, Stack Overflow'a hoş geldiniz. "Yalnızca kod" yanıtları, özellikle zaten birçok yanıtı olan bir soruyu yanıtlarken hoş karşılanmaz. Lütfen, verdiğiniz yanıtın neden bir şekilde önemli olduğuna ve orijinal gönderen tarafından zaten incelenmiş olanı yankılamadığına dair bazı ek bilgiler eklediğinizden emin olun.
2019

1
@Demian Wolf Orijinal cevabın geliştirilmiş halini beğendim, ancak geri almak zorunda kaldım. Lütfen kendi cevabınız olarak göndermeyi düşünün (bunu revizyon geçmişinde bulabilirsiniz ).
Marc. 2377

1

Orionrobert'in , ayrı silmeler veya eklemeler yerine, seçim yoluyla metin ikameleri üzerine basit doğrulama ile uğraşma sorununa yanıt vermek :

Seçilen metnin değiştirilmesi bir silme olarak işlenir ve bunu bir ekleme izler. Bu, örneğin silme işleminin imleci sola hareket ettirmesi gerektiğinde, bir ikame imleci sağa hareket ettirmesi gerektiğinde sorunlara yol açabilir. Neyse ki, bu iki işlem birbiri ardına hemen yürütülüyor . Bu nedenle, kendi başına bir silme ile doğrudan bir değişiklikten kaynaklanan bir ekleme işlemi arasında ayrım yapabiliriz çünkü ikincisi, silme ve ekleme arasında boş bayrağını değiştirmez.

Bu, bir substitutionFlag ve bir Widget.after_idle(). after_idle()olay kuyruğunun sonunda lambda işlevini çalıştırır:

class ValidatedEntry(Entry):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.tclValidate = (self.register(self.validate), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
        # attach the registered validation function to this spinbox
        self.config(validate = "all", validatecommand = self.tclValidate)

    def validate(self, type, index, result, prior, indelText, currentValidationMode, reason, widgetName):

        if typeOfAction == "0":
            # set a flag that can be checked by the insertion validation for being part of the substitution
            self.substitutionFlag = True
            # store desired data
            self.priorBeforeDeletion = prior
            self.indexBeforeDeletion = index
            # reset the flag after idle
            self.after_idle(lambda: setattr(self, "substitutionFlag", False))

            # normal deletion validation
            pass

        elif typeOfAction == "1":

            # if this is a substitution, everything is shifted left by a deletion, so undo this by using the previous prior
            if self.substitutionFlag:
                # restore desired data to what it was during validation of the deletion
                prior = self.priorBeforeDeletion
                index = self.indexBeforeDeletion

                # optional (often not required) additional behavior upon substitution
                pass

            else:
                # normal insertion validation
                pass

        return True

Tabii ki, bir değişiklikten sonra, silme kısmını onaylarken, kişi hala bir ekin gelip gelmeyeceğini bilmeyecektir. Neyse ki, ancak şununla: .set(), .icursor(), .index(SEL_FIRST), .index(SEL_LAST), .index(INSERT), biz geriye dönük en istenilen davranışı elde edebilirsiniz (bir ekleme ile yeni substitutionFlag kombinasyonu yeni bir benzersiz ve son olaydır çünkü.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.