tkinterのエントリーウィジェットコンテンツをインタラクティブに検証する


85

tkinterEntryウィジェットのコンテンツをインタラクティブに検証するための推奨テクニックは何ですか?

私は、使用についての記事を読んだvalidate=Truevalidatecommand=command、これらの機能があればそれらがクリアされているという事実によって制限されているように思われるvalidatecommandコマンドが更新Entryウィジェットの値を。

この動作を考えると、我々は上に結合すべきであるKeyPressCutと、Pasteイベントやモニター/更新たちのEntryこれらのイベントを通じて、ウィジェットの値は?(そして私が見逃したかもしれない他の関連イベント?)

または、インタラクティブな検証を完全に忘れて、FocusOutイベントでのみ検証する必要がありますか?

回答:


221

正解は、validatecommandウィジェットの属性を使用することです。残念ながら、この機能はTkinterの世界では十分に文書化されていますが、Tkinterの世界では十分に文書化されていません。十分に文書化されていませんが、バインディングや変数のトレースに頼ったり、検証手順内からウィジェットを変更したりすることなく、検証を行うために必要なすべてが揃っています。

秘訣は、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を参照してください。


16
これはそれを行う正しい方法です。これは、jmeyer10の回答を機能させようとしたときに見つけた問題に対処します。この1つの例は、他の場所で見つけることができるものと比較して、検証するための優れたドキュメントを提供します。この5票をあげられたらいいのにと思います。
Steven Rumbalski 2010年

3
うわー!私はスティーブンに同意します-これは複数の投票に値するタイプの返信です。Tkinterで本を書く必要があります(そして、それをマルチボリュームシリーズにするのに十分なソリューションをすでに投稿しています)。ありがとうございました!!!
マルコム

2
例をありがとう。validateコマンドはブール値(TrueとFalseのみ)を返さなければならないことに注意してください。そうでない場合、検証は削除されます。
Dave Bacher 2012年

3
このページを前面に出すべきだと思います。
右脚

4
「Tkinterの世界では文書化が大幅に不足しています」。笑—他のほとんどすべてのTkiinter世界のように。
マーティ

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 2018

10

使う Tkinter.StringVarて、エントリウィジェットの値を追跡します。あなたは、の値を検証することができますStringVar設定することにより、traceそれに。

これは、エントリウィジェットで有効なフロートのみを受け入れる短い動作プログラムです。

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()メソッドが使用されているのを見て楽しんだ。
マルコム

なぜこのエラーが発生する可能性があるのか​​考えてみてください。「NameError:name'validate 'is not defined」
ArmenSanoyan20年

4

ブライアン・オークリーの答えを研究しているときに、はるかに一般的な解決策を開発できるとのことでした。次の例では、検証のために、モード列挙、タイプディクショナリ、およびセットアップ関数を紹介します。使用例とその単純さのデモンストレーションについては、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ウィジェットの「invalidcommand」属性については誰も言及していません。

良い説明はここにあります:http//infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html

リンクが壊れた場合のテキストのコピー/貼り付け

エントリウィジェットは、validatecommandがFalseを返すたびに呼び出されるコールバック関数を指定するinvalidcommandオプションもサポートします。このコマンドは、ウィジェットに関連付けられたテキスト変数で.set()メソッドを使用して、ウィジェット内のテキストを変更できます。このオプションの設定は、validateコマンドの設定と同じように機能します。Python関数をラップするには、.register()メソッドを使用する必要があります。このメソッドは、ラップされた関数の名前を文字列として返します。次に、invalidcommandオプションの値として、その文字列、または置換コードを含むタプルの最初の要素として渡します。

注:方法がわからないことが1つだけあります。エントリに検証を追加し、ユーザーがテキストの一部を選択して新しい値を入力した場合、元の値をキャプチャしてリセットする方法はありません。エントリ。これが例です

  1. エントリは、「validatecommand」を実装することによって整数のみを受け入れるように設計されています
  2. ユーザーが1234567と入力します
  3. ユーザーは「345」を選択し、「j」を押します。これは、「345」の削除と「j」の挿入の2つのアクションとして登録されます。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
こんにちは、StackOverflowへようこそ。「コードのみ」の回答は、特にすでに多くの回答がある質問に回答する場合は、嫌われます。提供している回答が何らかの形で実質的であり、元の投稿者がすでに精査したものを単に反映しているのではない理由について、いくつかの洞察を追加してください。
CHB

1
@Demian Wolf元の回答の改良版が気に入りましたが、ロールバックする必要がありました。あなた自身の答えとしてそれを投稿することを検討してください(あなたはそれを改訂履歴で見つけることができます)。
Marc.23 7719年

1

個別の削除または挿入ではなく、選択によるテキストの置換時に単純な検証を処理するというorionrobertの問題に対応する:

選択したテキストの置換は、削除とそれに続く挿入として処理されます。これにより、問題が発生する可能性があります。たとえば、削除によってカーソルが左に移動し、置換によってカーソルが右に移動する場合などです。幸い、これら2つのプロセスはすぐに実行されます互いの後。したがって、削除自体と、置換による挿入の直後の削除とを区別できます。後者は、削除と挿入の間のアイドルフラグを変更しないためです。

これは、substitutionFlagとを使用して悪用され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.