การตรวจสอบเนื้อหาวิดเจ็ตรายการแบบโต้ตอบใน tkinter


85

เทคนิคที่แนะนำสำหรับการตรวจสอบเนื้อหาแบบโต้ตอบในEntryวิดเจ็ตtkinter คืออะไร?

ฉันได้อ่านโพสต์เกี่ยวกับการใช้validate=Trueและvalidatecommand=commandและดูเหมือนว่าคุณสมบัติเหล่านี้จะถูก จำกัด ด้วยข้อเท็จจริงที่ว่าพวกเขาถูกล้างหากvalidatecommandคำสั่งอัพเดตEntryค่าของวิดเจ็ต

ได้รับพฤติกรรมนี้เราควรจะผูกบนKeyPress, CutและPasteเหตุการณ์ที่เกิดขึ้นและตรวจสอบ / อัปเดตของเราEntryคุ้มค่าวิดเจ็ตผ่านเหตุการณ์เหล่านี้หรือไม่ (และเหตุการณ์ที่เกี่ยวข้องอื่น ๆ ที่ฉันอาจพลาด?)

หรือเราควรลืมการตรวจสอบความถูกต้องเชิงโต้ตอบทั้งหมดและตรวจสอบความถูกต้องเท่านั้น FocusOutเหตุการณ์ ?

คำตอบ:


222

คำตอบที่ถูกต้องคือใช้ validatecommandคุณลักษณะของวิดเจ็ต น่าเสียดายที่คุณลักษณะนี้ไม่ได้รับการบันทึกไว้อย่างรุนแรงในโลกของ Tkinter แม้ว่าจะมีการบันทึกไว้ค่อนข้างเพียงพอในโลก Tk แม้ว่าจะไม่ได้รับการจัดทำเป็นเอกสารอย่างดี แต่ก็มีทุกสิ่งที่คุณต้องทำการตรวจสอบความถูกต้องโดยไม่ต้องใช้การผูกหรือการติดตามตัวแปรหรือแก้ไขวิดเจ็ตจากภายในขั้นตอนการตรวจสอบความถูกต้อง

เคล็ดลับคือการรู้ว่าคุณสามารถมี Tkinter pass ในค่าพิเศษสำหรับคำสั่ง validate ของคุณได้ ค่าเหล่านี้ให้ข้อมูลทั้งหมดที่คุณจำเป็นต้องทราบเพื่อตัดสินใจว่าข้อมูลนั้นถูกต้องหรือไม่: ค่าก่อนการแก้ไขค่าหลังการแก้ไขหากการแก้ไขถูกต้องและข้อมูลอื่น ๆ อีกหลายบิต ในการใช้สิ่งเหล่านี้คุณต้องทำวูดูเล็กน้อยเพื่อให้ข้อมูลนี้ส่งผ่านไปยังคำสั่ง validate ของคุณ

หมายเหตุ: สิ่งสำคัญคือคำสั่ง validation จะส่งคืน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


16
นี่คือวิธีที่เหมาะสมที่จะทำ มันกล่าวถึงปัญหาที่ฉันพบเมื่อฉันพยายามให้คำตอบของ jmeyer10 ใช้งานได้ ตัวอย่างหนึ่งนี้ให้เอกสารที่ดีกว่าในการตรวจสอบเทียบกับสิ่งที่ฉันหาได้จากที่อื่น ฉันหวังว่าฉันจะให้ 5 โหวตนี้
Steven Rumbalski

3
ว้าว! ฉันเห็นด้วยกับสตีเวน - นี่คือประเภทของการตอบกลับที่สมควรได้รับมากกว่าหนึ่งคะแนน คุณควรเขียนหนังสือเกี่ยวกับ Tkinter (และคุณได้โพสต์วิธีแก้ปัญหาไว้เพียงพอแล้วที่จะทำให้เป็นซีรีส์หลายเล่ม) ขอขอบคุณ!!!
Malcolm

2
ขอบคุณสำหรับตัวอย่าง เป็นที่น่าสังเกตว่าคำสั่งที่ถูกต้องต้องส่งคืนบูลีน (เฉพาะจริงและเท็จ) หากไม่เป็นเช่นนั้นการตรวจสอบความถูกต้องจะถูกลบออก
Dave Bacher

3
ฉันคิดว่าควรนำหน้านี้มาไว้ข้างหน้า
ขาขวา

4
"ได้รับการบันทึกอย่างรุนแรงในโลกของ Tkinter" ฮ่า ๆ - เหมือนกับโลกอื่น ๆ เกือบทั้งหมดของ Tkiinter
martineau

21

หลังจากศึกษาและทดลองใช้โค้ดของไบรอันแล้วฉันได้สร้างการตรวจสอบอินพุตเวอร์ชันขั้นต่ำ รหัสต่อไปนี้จะใส่ช่องรายการและยอมรับเฉพาะตัวเลขเท่านั้น

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

บางทีฉันควรเพิ่มว่าฉันยังเรียน Python อยู่และฉันยินดีที่จะยอมรับความคิดเห็น / ข้อเสนอแนะใด ๆ ทั้งหมด


1
คนทั่วไปมักใช้entry.configure(validatecommand=...)และเขียนtest_valแทนtestValแต่นี่เป็นตัวอย่างง่ายๆที่ดี
wizzwizz4

10

ใช้Tkinter.StringVarเพื่อติดตามค่าของวิดเจ็ตรายการ คุณสามารถตรวจสอบค่าของStringVarโดยการตั้งค่า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

มีความคิดว่าทำไมฉันถึงได้รับข้อผิดพลาดนี้ "NameError: name 'validate' is not
specified

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

คำตอบของไบรอันถูกต้อง แต่ไม่มีใครพูดถึงแอตทริบิวต์ "คำสั่งไม่ถูกต้อง" ของวิดเจ็ต tkinter

คำอธิบายที่ดีอยู่ที่นี่: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html

คัดลอกข้อความ / วางในกรณีที่ลิงค์เสีย

วิดเจ็ต Entry ยังสนับสนุนอ็อพชันคำสั่งที่ไม่ถูกต้องซึ่งระบุฟังก์ชันการเรียกกลับที่ถูกเรียกเมื่อใดก็ตามที่ validatecommand ส่งคืน False คำสั่งนี้อาจแก้ไขข้อความในวิดเจ็ตโดยใช้เมธอด. set () บน textvariable ที่เกี่ยวข้องของวิดเจ็ต การตั้งค่าตัวเลือกนี้จะทำงานเหมือนกับการตั้งค่า validatecommand คุณต้องใช้เมธอด .register () เพื่อรวมฟังก์ชัน Python ของคุณ วิธีนี้จะส่งคืนชื่อของฟังก์ชันที่ถูกรวมเป็นสตริง จากนั้นคุณจะส่งผ่านเป็นค่าของอ็อพชันคำสั่งที่ไม่ถูกต้องไม่ว่าจะเป็นสตริงนั้นหรือเป็นองค์ประกอบแรกของทูเพิลที่มีโค้ดการแทนที่

หมายเหตุ: มีเพียงสิ่งเดียวที่ฉันคิดไม่ออกว่าต้องทำอย่างไร: หากคุณเพิ่มการตรวจสอบความถูกต้องให้กับรายการและผู้ใช้เลือกส่วนของข้อความและพิมพ์ค่าใหม่จะไม่มีวิธีจับค่าเดิมและรีเซ็ต รายการ นี่คือตัวอย่าง

  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

PS: ตัวอย่างนี้มีประโยชน์มากสำหรับการสร้างแอปเช่น 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ในการจัดการกับการตรวจสอบความถูกต้องอย่างง่ายเมื่อมีการแทนที่ข้อความผ่านการเลือกแทนที่จะลบหรือแทรกแยกต่างหาก:

การแทนที่ข้อความที่เลือกจะถูกประมวลผลเป็นการลบตามด้วยการแทรก สิ่งนี้อาจนำไปสู่ปัญหาเช่นเมื่อการลบควรเลื่อนเคอร์เซอร์ไปทางซ้ายในขณะที่การแทนที่ควรเลื่อนเคอร์เซอร์ไปทางขวา โชคดีที่กระบวนการทั้งสองนี้ดำเนินการทันทีหลังจากกัน ดังนั้นเราสามารถแยกความแตกต่างระหว่างการลบด้วยตัวมันเองและการลบโดยตรงตามด้วยการแทรกเนื่องจากการแทนที่เนื่องจากส่วนหลังไม่ได้เปลี่ยนแฟล็กที่ไม่ได้ใช้งานระหว่างการลบและการแทรก

สิ่งนี้ถูกใช้โดยใช้การแทนที่ธงและ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.