tkinter GUI设计:管理来自多个小部件/工具栏的变量

时间:2016-05-02 00:41:36

标签: python-3.x model-view-controller tkinter

{编辑:Bryan Oakley在建议的重复问题enter link description here中的答案a)触发对数组变量更改的响应(arrayvar.trace mode =“w”),我需要它在我最初的问题中描述的FocusOut上触发; b)适用于Python 2,但我无法将其转换为Python 3.5。我目前正在使用他和pyfunc的答案作为线索,并尝试使用FocusOut事件找出类似的解决方案。}

我正在开发一个tkinter GUI,它允许用户使用一对单选按钮列表选择特定类型的计算。根据选择,工具栏中填充了多个模块化条目小部件,每个小部件用于计算所需的每个变量。目标是将数值输入值传递给模型,该模型将返回要在画布或matplotlib小部件上绘制的数据。

我的问题是:为了更新显示并将其传递给模型,使用哪种典型策略来收集和不断刷新多个小部件的值?这里的诀窍是会有大量可能的计算类型,每种类型都有自己的工具栏。我希望活动工具栏“了解”其内容,并在每次更改小部件条目时对模型执行ping操作。

我认为小部件和工具栏必须是类,工具栏可以在检测到更改时查询每个小部件以获取其条目值的新副本,并将它们存储为传递给模型的某个集合。我不完全确定如何跟踪小部件的更改。在条目小部件上使用“validate ='focusout'”验证(例如,如 this validation reference) 建议自己,但我已经使用“validate ='key'”将所有条目限制为数字。我不想使用“validate = all”并捎带它,因为我不想让模型对每个按键进行冗长的计算。

然而,我是GUI编程的新手,所以我可能会咆哮错误的树。我确信必须有一个标准的设计模式来解决这个问题,但我还没有找到它。

下面是一个模型的屏幕截图,用于说明我希望GUI执行的操作。 Task radiobutton控制下面显示的辅助按钮菜单。第二个菜单中的选择使用必要的条目小部件填充顶部工具栏。

enter image description here

1 个答案:

答案 0 :(得分:0)

以下代码(大部分)是我想要的。 ToolBar框架对象将存储其包含的小部件中的值,并根据需要调用适当的模型。 VarBox对象是具有额外功能的Entry小部件。点击Tab或Return刷新存储在ToolBar字典中的数据,告诉ToolBar将数据发送到模型,并将焦点转移到下一个VarBox小部件。

from tkinter import *


# Actual model would be imported. "Dummy" model for testing below.
def dummy_model(dic):
    """
    A "dummy" model for testing the ability for a toolbar to ping the model.
    Argument:
    -dic: a dictionary whose values are numbers.
    Result:
    -prints the sum of dic's values.
    """
    total = 0
    for value in dic.values():
        total += value
    print('The total of the entries is: ', total)


class ToolBar(Frame):
    """
    A frame object that contains entry widgets, a dictionary of
    their current contents, and a function to call the appropriate model.
    """
    def __init__(self, parent=None, **options):
        Frame.__init__(self, parent, **options)
        self.vars = {}

    def call_model(self):
        print('Sending to dummy_model: ', self.vars)
        dummy_model(self.vars)


class VarBox(Frame):
    """
    A customized Frame containing a numerical entry box
    Arguments:
    -name: Name of the variable; appears above the entry box
    -default: default value in entry
    """
    def __init__(self, parent=None, name='', default=0.00, **options):
        Frame.__init__(self, parent, relief=RIDGE, borderwidth=1, **options)
        Label(self, text=name).pack(side=TOP)
        self.widgetName = name  # will be key in dictionary

        # Entries will be limited to numerical
        ent = Entry(self, validate='key')  # check for number on keypress
        ent.pack(side=TOP, fill=X)
        self.value = StringVar()
        ent.config(textvariable=self.value)
        self.value.set(str(default))
        ent.bind('<Return>',   lambda event: self.to_dict(event))
        ent.bind('<FocusOut>', lambda event: self.to_dict(event))

        # check on each keypress if new result will be a number
        ent['validatecommand'] = (self.register(self.is_number), '%P')
        # sound 'bell' if bad keypress
        ent['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

    def to_dict(self, event):
        """
        On event: Records widget's status to the container's dictionary of
        values, fills the entry with 0.00 if it was empty, tells the container
        to send data to the model, and shifts focus to the next entry box (after
        Return or Tab).
        """
        if not self.value.get():    # if entry left blank,
            self.value.set(0.00)    # fill it with zero
        # Add the widget's status to the container's dictionary
        self.master.vars[self.widgetName] = float(self.value.get())
        self.master.call_model()
        event.widget.tk_focusNext().focus()


root = Tk()  # create app window
BarParentFrame = ToolBar(root)  # holds individual toolbar frames
BarParentFrame.pack(side=TOP)
BarParentFrame.widgetName = 'BarParentFrame'

# Pad out rest of window for visual effect
SpaceFiller = Canvas(root, width=800, height=600, bg='beige')
SpaceFiller.pack(expand=YES, fill=BOTH)

Label(BarParentFrame, text='placeholder').pack(expand=NO, fill=X)
A = VarBox(BarParentFrame, name='A', default=5.00)
A.pack(side=LEFT)
B = VarBox(BarParentFrame, name='B', default=3.00)
B.pack(side=LEFT)

root.mainloop()