tkinter에서 대화식으로 항목 위젯 컨텐츠 유효성 검증


85

tkinter Entry위젯 에서 대화식으로 콘텐츠를 검증하는 데 권장되는 기술은 무엇입니까 ?

validate=True및 사용에 대한 게시물을 읽었으며 validatecommand=command이러한 기능은 validatecommand명령이 Entry위젯의 값을 업데이트하면 지워진다는 사실로 인해 제한되는 것으로 보입니다 .

이 동작을 감안할 때, 우리는에 결합한다 KeyPress, CutPaste이벤트 모니터 / 업데이트 우리의 Entry이러한 이벤트를 통해 위젯의 가치인가? (그리고 내가 놓친 기타 관련 이벤트?)

아니면 대화 형 유효성 검사를 완전히 잊어 버리고 FocusOut이벤트에 대해서만 유효성을 검사해야 합니까?

답변:


217

정답은 validatecommand위젯 의 속성을 사용하는 것입니다. 안타깝게도이 기능은 Tkinter 세계에서는 충분히 문서화되어 있지 않지만 Tk 세계에서는 충분히 문서화되어 있습니다. 잘 문서화되어 있지는 않지만 바인딩이나 추적 변수에 의존하지 않고 유효성 검사 절차 내에서 위젯을 수정하지 않고도 유효성 검사를 수행하는 데 필요한 모든 것이 있습니다.

트릭은 Tkinter가 특정 값을 validate 명령에 전달할 수 있다는 것을 아는 것입니다. 이러한 값은 데이터가 유효한지 여부를 결정하는 데 필요한 모든 정보를 제공합니다. 편집 전 값, 편집이 유효한 경우 편집 후 값 및 기타 여러 정보를 제공합니다. 그러나이를 사용하려면이 정보를 validate 명령에 전달하기 위해 약간의 부두를해야합니다.

참고 : 유효성 검사 명령이 True또는을 반환하는 것이 중요합니다 False. 그 밖의 사항은 위젯에 대한 유효성 검사를 해제합니다.

다음은 소문자 만 허용하고 모든 펑키 값을 인쇄하는 예입니다.

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()

register메서드 를 호출 할 때 내부에서 발생하는 작업에 대한 자세한 내용 은 입력 유효성 검사 tkinter를 참조하세요.


15
이것이 올바른 방법입니다. jmeyer10의 답변이 작동하도록 시도했을 때 발견 한 문제를 해결합니다. 이 한 가지 예는 다른 곳에서 찾을 수있는 것과 비교할 때 검증 할 수있는 우수한 문서를 제공합니다. 이 5 표를 줄 수 있으면 좋겠다.
Steven Rumbalski 2010

3
와! 나는 Steven의 의견에 동의합니다. 이것은 한 표 이상을받을 자격이있는 유형의 답변입니다. Tkinter에 대한 책을 작성해야합니다 (여러 권의 시리즈를 만들 수있는 충분한 솔루션을 이미 게시했습니다). 감사합니다!!!
Malcolm

2
예를 들어 주셔서 감사합니다. validate 명령이 반드시 부울 (True 및 False 만)을 반환해야한다는 점은 주목할 가치가 있습니다. 그렇지 않은 경우 유효성 검사가 제거됩니다.
Dave Bacher 2012-06-19

3
나는 이 페이지 를 전면에 가져와야 한다고 생각 한다.
오른쪽 다리

4
"Tkinter 세계에서 심각하게 문서화되지 않음". LOL — 거의 모든 Tkiinter 세계와 같습니다.
martineau

21

Bryan의 코드를 연구하고 실험 한 후 최소한의 입력 유효성 검사 버전을 생성했습니다. 다음 코드는 입력 상자를 표시하고 숫자 만 허용합니다.

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()

아마도 나는 아직 파이썬을 배우고 있으며 모든 의견 / 제안을 기꺼이 받아 들일 것이라고 덧붙여 야 할 것입니다.


1
일반적으로 사람들은 대신을 사용 entry.configure(validatecommand=...)하고 작성 하지만 이것은 좋은 간단한 예입니다. test_valtestVal
wizzwizz4

10

Tkinter.StringVar항목 위젯의 값을 추적 하려면을 사용하십시오 . 에 StringVara trace를 설정하여 의 값을 확인할 수 있습니다 .

다음은 Entry 위젯에서 유효한 수레 만 허용하는 짧은 작업 프로그램입니다.

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
게시물 주셔서 감사합니다. Tkinter StringVar .trace () 메서드를 사용하는 것을 즐겼습니다.
Malcolm

4

Bryan Oakley의 답변을 연구하는 동안 훨씬 더 일반적인 솔루션을 개발할 수 있다는 내용이 나에게 알려졌습니다. 다음 예제에서는 유효성 검사를위한 모드 열거, 유형 사전 및 설정 함수를 소개합니다. 사용 예와 단순성에 대한 데모는 48 행을 참조하십시오.

#! /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의 대답은 맞지만 아무도 tkinter 위젯의 'invalidcommand'속성을 언급하지 않았습니다.

좋은 설명은 다음과 같습니다. http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html

링크가 끊어진 경우 텍스트 복사 / 붙여 넣기

Entry 위젯은 validatecommand가 False를 반환 할 때마다 호출되는 콜백 함수를 지정하는 invalidcommand 옵션도 지원합니다. 이 명령은 위젯의 관련 텍스트 변수에서 .set () 메서드를 사용하여 위젯의 텍스트를 수정할 수 있습니다. 이 옵션을 설정하는 것은 validate 명령을 설정하는 것과 동일하게 작동합니다. Python 함수를 래핑하려면 .register () 메서드를 사용해야합니다. 이 메서드는 래핑 된 함수의 이름을 문자열로 반환합니다. 그런 다음 해당 문자열 또는 대체 코드를 포함하는 튜플의 첫 번째 요소로 invalidcommand 옵션의 값으로 전달합니다.

참고 : 방법을 알아낼 수없는 한 가지만 있습니다. 항목에 유효성 검사를 추가하고 사용자가 텍스트의 일부를 선택하고 새 값을 입력하면 원래 값을 캡처하고 재설정 할 방법이 없습니다. 항목. 여기에 예가 있습니다.

  1. 항목은 'validatecommand'를 구현하여 정수만 허용하도록 설계되었습니다.
  2. 사용자가 1234567을 입력합니다.
  3. 사용자는 '345'를 선택하고 'j'를 누릅니다. 이것은 '345'삭제와 'j'삽입의 두 가지 동작으로 등록됩니다. Tkinter는 삭제를 무시하고 'j'가 삽입 된 경우에만 작동합니다. 'validatecommand'는 False를 반환하고 'invalidcommand'함수에 전달 된 값은 다음과 같습니다. % d = 1, % i = 2, % P = 12j67, % s = 1267, % S = j
  4. 코드가 'invalidcommand'함수를 구현하지 않으면 'validatecommand'함수는 'j'를 거부하고 결과는 1267이됩니다. 코드가 'invalidcommand'함수를 구현하면 원래 1234567을 복구 할 방법이 없습니다. .

3

다음은 사용자가 숫자 만 입력 할 수있는 입력 값을 확인하는 간단한 방법입니다.

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

추신 :이 예제는 calc와 같은 앱을 만드는 데 매우 유용 할 수 있습니다.


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
안녕하세요, Stack Overflow에 오신 것을 환영합니다. "코드 전용"답변은 특히 이미 많은 답변이있는 질문에 답변 할 때 눈살을 찌푸립니다. 귀하가 제공하는 응답이 원본 포스터에서 이미 심사 한 내용을 단순히 반영하지 않고 어떻게 든 실질적인 이유에 대한 추가 통찰력을 추가하십시오.
chb

1
@Demian Wolf 원래 답변의 개선 된 버전이 마음에 들었지만 롤백해야했습니다. 자신의 답변으로 게시하는 것을 고려하십시오 ( 수정 내역 에서 찾을 수 있음 ).
Marc.2377

1

별도의 삭제 또는 삽입 대신 선택을 통해 텍스트를 대체 할 때 간단한 유효성 검사를 처리 하는 orionrobert의 문제 에 대한 응답 :

선택한 텍스트의 대체는 삭제 후 삽입으로 처리됩니다. 예를 들어 삭제하면 커서가 왼쪽으로 이동하고 대체는 커서를 오른쪽으로 이동해야하는 경우와 같은 문제가 발생할 수 있습니다. 다행히도이 두 프로세스는 즉시 실행 됩니다. 따라서 후자는 삭제와 삽입 사이의 유휴 플래그를 변경하지 않기 때문에 자체 삭제와 대체로 인해 삽입이 직접 뒤 따르는 삭제를 구별 할 수 있습니다.

이는 replacementFlag 및 Widget.after_idle(). after_idle()이벤트 큐 끝에서 람다 함수를 실행합니다.

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

물론 대체 후 삭제 부분을 확인하는 동안 삽입이 뒤따를 지 여부는 여전히 알 수 없습니다. 다행히 그러나,로 : .set(), .icursor(), .index(SEL_FIRST), .index(SEL_LAST), .index(INSERT), 우리가 대상으로 가장 원하는 동작을 얻을 수 있습니다 (삽입과 우리의 새로운 substitutionFlag의 조합은 새로운 독특하고 최종 이벤트이기 때문이다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.