【问题标题】:tkinter SpinBox: delay between updating displayed value and associated StringVartkinter SpinBox:更新显示值和关联的 StringVar 之间的延迟
【发布时间】:2017-09-30 16:50:00
【问题描述】:

总结:单击 tkinter SpinBox 箭头会增加 GUI 中的数字,但检查与关联的 StringVar() 条目的单击会滞后。

我创建了复合小部件,其中包含数字条目,在提交条目更改时调用模型计算。我不想每次更改条目时都简单地调用模型,因为计算可能很慢。因此,实际代码使用绑定<Tab><Return> 等(从下面的示例代码中省略)并且仅在这样的“提交”上调用模型。

我查看的 SpinBox 文档没有显示绑定到 SpinBox 本身的向上/向下箭头按钮的方法。所以,我有用于单击和释放鼠标按钮的绑定。在单击和释放时,将最后存储的值与当前的 StringVar 值进行比较,如果它们不同,则更新存储的值并调用模型。涉及的关键方法有bind_entryon_presson_releaserefreshentry_is_changedsave_entry。 (on_presson_release 中有注释掉的代码,允许在按下 SpinBox 箭头时刷新模型;保留它以使预期的最终行为清晰,但不需要复制错误) .

from tkinter import *  # technically bad practice, but common


class SpinBoxFrame(Frame):
    """
    A tkinter Frame that holds a labeled entry widget with added behavior.
    EntryFrame will call the function (provided as 'model' in the arguments)
    when a change in the entry's value is committed.

    Arguments (in addition to standard Frame options):
        name-- for widget label and introspection
        array-- a 2D array ( list of lists, [[],[]]
        coord-- the coordinate of the array to be read from/written to
        from_, to, increment: SpinBox arguments (minimum and maximum values,
        and incremental change on each arrow click)
    """

    def __init__(self, parent=None, name='', model=None,
                 array=None, coord=(0, 0),
                 from_=0.00, to=100.00, increment=1,
                 **options):

        Frame.__init__(self, parent, **options)
        self.name = name
        self.model = model
        self.array = array
        self.row, self.col = coord
        self.spinbox_kwargs = {'from_': from_,
                               'to': to,
                               'increment': increment}
        self.initialize()
        self.add_label()
        self.add_entry()
        self.bind_entry()
        self.validate_entry()

    def initialize(self):
        self.value_var = StringVar()
        self.value = self.array[self.row][self.col]
        self.value_var.set(self.value)

    def add_label(self):
        Label(self, text=self.name, bg='white', bd=0).pack(side=TOP)

    def add_entry(self):
        self.entry = Spinbox(self, width=7,
                             validate='key',  # check for number on keypress
                             **self.spinbox_kwargs)
        self.entry.pack(side=TOP, fill=X)
        self.entry.config(textvariable=self.value_var)

    def bind_entry(self):
        self.entry.bind('<FocusOut>', lambda event: self.refresh())
        self.entry.bind('<ButtonPress-1>', lambda event: self.on_press())
        self.entry.bind('<ButtonRelease-1>', lambda event: self.on_release())

    def refresh(self):
        if self.entry_is_changed():
            print('VALUE CHANGE')
            self.save_entry()
            print('Saved new ', self.name, ' value')
            self.model()

    def entry_is_changed(self):
        print('Old value of ', self.name, ' was ', self.value)
        print('Current value of ', self.name, ' is ',
              float(self.value_var.get()))
        return self.value != float(self.value_var.get())

    def save_entry(self):
        if not self.value_var.get():  # if entry left blank,
            self.value_var.set(0.00)  # fill it with zero
        self.value = float(self.value_var.get())
        self.array[self.row][self.col] = self.value

    def on_press(self):
        print('Button pressed')
        # self.loop_refresh()  # uncomment to enable real-time refreshing

    def loop_refresh(self):
        self.refresh()
        self.button_held_job = self._root().after(50, self.loop_refresh)

    def on_release(self):
        print('Button released')
        # uncomment if loop enabled in on_press()
        # self._root().after_cancel(self.button_held_job)
        self.refresh()

    def validate_entry(self):
        """
        The base EntryFrame class assumes the entry contents should be numerical
        """
        # check on each keypress if new result will be a number
        self.entry['validatecommand'] = (self.register(self.is_number), '%P')
        # sound 'bell' if bad keypress
        self.entry['invalidcommand'] = 'bell'

    @staticmethod
    def is_number(entry):
        """
        tests to see if entry is acceptable (either empty, or able to be
        converted to a float.)
        """
        if not entry:
            return True  # Empty string: OK if entire entry deleted
        try:
            float(entry)
            return True
        except ValueError:
            return False

if __name__ == '__main__':
    dummy_array = [[1, 42], [0, 0]]
    root = Tk()


    class TestFrame(Frame):
        """Mimics a toolbar object that holds entry widgets and passes their
        entries on to a model"""
        def __init__(self, parent, **options):
            Frame.__init__(self, parent, **options)

        def call_model(self):
            print('requesting calculation from the model, using:')
            print(dummy_array)

    mainwindow = TestFrame(root)
    mainwindow.pack()

    box1 = SpinBoxFrame(mainwindow, array=dummy_array, coord=(0, 0),
                                 name='Box 1', model=mainwindow.call_model)
    box1.pack(side=LEFT)
    box2 = SpinBoxFrame(mainwindow, array=dummy_array, coord=(0, 1),
                        name='Box 2', model=mainwindow.call_model)
    box2.pack(side=LEFT)

    # workaround fix for Tk problems and mac mouse/trackpad:
    while True:
        try:
            root.mainloop()
            break
        except UnicodeDecodeError:
            pass

单击并释放 SpinBox 箭头会增加/减少 GUI 中的值,但检查与 StringVar 的对比表明没有变化。在相同方向上再次单击相同箭头会导致更改,但会更改为先前的值而不是当前的 GUI 值。因此,StringVar 检查总是落后于显示的值一个。我想知道我的代码运行速度与 tkinter 主循环更新与 SpinBox 条目关联的 StringVar 对象的速度之间是否存在一些“竞争条件”。似乎调用 entry_is_changed 的速度比更新 StringVar 的速度快。

屏幕截图显示了错误的性质。首先,左侧小部件通过单击从 1 增加到 2,但值检查仍然表明当前 StringVar 仍然保持“1”。然后,右边的小部件增加了两次。在第一次从 42 增加到 43 之后,值没有变化。在从 43 到 44 的第二次增量之后,可以看到值 43 的变化。

【问题讨论】:

    标签: python python-3.x tkinter


    【解决方案1】:

    我认为&lt;ButtonRelease-1&gt;事件在变量值更新之前触发refresh。为了避免这种情况,我在on_release 中调用refresh 之前引入了一个小延迟:

    def on_release(self):
        print('Button released')
        # uncomment if loop enabled in on_press()
        # self._root().after_cancel(self.button_held_job)
        self.after(1, self.refresh)
    

    至少在我的电脑上,这 1 毫秒的延迟足以获得更新后的值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-10
      • 2020-04-18
      • 2014-10-02
      • 1970-01-01
      • 2017-09-11
      • 1970-01-01
      • 1970-01-01
      • 2021-07-22
      相关资源
      最近更新 更多