python - 如何在循环中制作 tkinter 框架并更新对象值?

标签 python python-3.x dictionary tkinter

我有一个名为 Bones 的类(class)我有5个Bones在我的skeleton字典。然而,在我的实际实现中,有 300 多个骨骼,这就是我今天在 stackoverflow 上问这个问题的原因。

每个Bone有:

  • ID : 一个用于识别骨骼的 int
  • w :w 位置(在 -11 之间 float )
  • x : x 位置(在 -11 之间 float )
  • y : y 位置(在 -11 之间 float )
  • z : z 位置(在 -11 之间 float )

Bone.py

INCREMENT = 0.01

class Bone:
    def __init__(self, boneId, w, x, y, z):
        self.id = boneId
        self.w = w
        self.x = x
        self.y = y
        self.z = z

    def shouldChangePos(self, num):
        if (num >= 1 or num <= -1):
            return False
        return True

    def incrW(self):
        if(self.shouldChangePos(self.w)):
            self.w = self.w + INCREMENT

    def decrW(self):
        if(self.shouldChangePos(self.w)):
            self.w = self.w - INCREMENT

    def incrX(self):
        if(self.shouldChangePos(self.x)):
            self.x = self.x + INCREMENT

    def decrX(self):
        if(self.shouldChangePos(self.x)):
            self.x = self.x - INCREMENT

    def incrY(self):
        if(self.shouldChangePos(self.y)):
            self.y = self.y + INCREMENT

    def decrY(self):
        if(self.shouldChangePos(self.y)):
            self.y = self.y - INCREMENT

    def incrZ(self):
        if(self.shouldChangePos(self.z)):
            self.z = self.z + INCREMENT

    def decrZ(self):
        if(self.shouldChangePos(self.z)):
            self.z = self.z - INCREMENT
<小时/>

问题说明

我正在尝试制作一个 tkinter GUI 看起来像这样:

mock-up drawing of gui

图例:

  • 绿色 - 代表 Frame (只是我的注释来解释)
  • 红色 - 是对象的属性(只是我的注释来解释)
  • 黑色 - 是对象的方法(只是我的注释来解释)
  • 蓝色 - 向我显示文本和按钮

如您所见,它显示 ID , w , x , y , z 。在它的下面,有一个+按钮和一个-按钮。每次单击这些按钮时,我想减少对象中的相应值并更新 tkinter显示号码。我知道如何手动执行此操作,但根据我的要求,我有 300+ Bones 。我无法手动制作这些框架。

如何在循环中创建这些框架,并在单击 +- 按钮时更新 GUI 和对象上显示的值?

<小时/>

main.py

from tkinter import *
from tkinter import ttk
from Bone import *

skeleton = {
    1: Bone(-0.42, 0.1, 0.02, 0.002, 0.234),
    4: Bone(4, 0.042, 0.32, 0.23, -0.32),
    11: Bone(11, 1, -0.23, -0.42, 0.42),
    95: Bone(95, -0.93, 0.32, 0.346, 0.31),
}


root = Tk()
root.geometry('400x600')

boneID = Label(root, text="ID: 1")
boneID.grid(row=1, column=1, sticky=W, padx=(0, 15))

w = Label(root, text="-0.42")
w.grid(row=1, column=2, sticky=W)

x = Label(root, text="0.02")
x.grid(row=1, column=4, sticky=W)

y = Label(root, text="0.002")
y.grid(row=1, column=6, sticky=W)

z = Label(root, text="0.234")
z.grid(row=1, column=8, sticky=W)

wPlusBtn = Button(root, text="+")
wPlusBtn.grid(row=2, column=2)
wMinusBtn = Button(root, text="-")
wMinusBtn.grid(row=2, column=3, padx=(0, 15))

xPlusBtn = Button(root, text="+")
xPlusBtn.grid(row=2, column=4)
xMinusBtn = Button(root, text="-")
xMinusBtn.grid(row=2, column=5, padx=(0, 15))

yPlusBtn = Button(root, text="+")
yPlusBtn.grid(row=2, column=6)
yMinusBtn = Button(root, text="-")
yMinusBtn.grid(row=2, column=7, padx=(0, 15))

zPlusBtn = Button(root, text="+")
zPlusBtn.grid(row=2, column=8)
zMinusBtn = Button(root, text="-")
zMinusBtn.grid(row=2, column=9, padx=(0, 15))

root.mainloop()

最佳答案

TL;DR - 将一个大问题分解为几个较小的问题,然后分别解决每个问题。

<小时/>

主窗口

首先查看 UI 的整体设计。您有两个部分:一个包含骨骼的面板和一个包含随机文本的面板。所以我要做的第一件事就是将这些面板创建为框架:

root = tk.Tk()
bonePanel = tk.Frame(root, background="forestgreen", bd=2, relief="groove")
textPanel = tk.Frame(root, background="forestgreen", bd=2, relief="groove")

当然,您还需要使用packgrid将它们放置在窗口上。我推荐 pack 因为只有两个框架,而且它们是并排的。

显示骨骼

对于骨骼面板,每个骨骼似乎都有一行。因此,我建议创建一个类来表示每一行。它可以继承自 Frame,并负责该行内发生的所有事情。通过继承 Frame,您可以将其视为自定义小部件,并将其布局在屏幕上。

您的 UI 代码的目标是如下所示:

bones = (
    Bone(boneId=1,  w=-0.42, x=0.02,  y=0.002, z=0.234),
    Bone(boneId=4,  w=0.042, x=0.32,  y=0.23,  z=-0.32),
    Bone(boneId=11, w=1,     x=-0.23, y=-0.42, z=0.42),
    ...
)

bonePanel = tk.Frame(root)
for bone in bones:
    bf = BoneFrame(bonePanel, bone)
    bf.pack(side="top", fill="x", expand=True)

同样,如果需要,您可以使用 grid,但 pack 似乎是自然的选择,因为行是从上到下堆叠的。

显示单个骨骼

现在,我们需要解决每个 BoneFrame 的作用。它似乎由五个部分组成:一个显示 id 的部分,然后是四个几乎相同的属性部分。由于这些部分之间的唯一区别是它们表示的属性,因此将每个部分表示为类的实例是有意义的。同样,如果该类继承自 Frame,我们可以将其视为自定义小部件。

这一次,我们应该传入骨骼,也许还有一个字符串告诉它要更新哪个 id。

所以,它可能开始看起来像这样:

class BoneFrame(tk.Frame):
    def __init__(self, master, bone):
        tk.Frame.__init__(self, master)

        self.bone = bone

        idlabel = tk.Label(self, text="ID: {}".format(bone.id))
        attr_w = BoneAttribute(self, self.bone, "w")
        attr_x = BoneAttribute(self, self.bone, "x")
        attr_y = BoneAttribute(self, self.bone, "y")
        attr_z = BoneAttribute(self, self.bone, "z")

pack 在这里是一个不错的选择,因为这些部分都是从左到右排列的,但如果您愿意,也可以使用 grid。唯一真正的区别是使用grid需要多行代码来配置行权重和列权重。

属性按钮和标签的小部件

最后,我们必须处理 BoneAttribute 类。这是我们最终添加按钮的地方。

它非常简单并且遵循相同的模式:创建小部件,然后将它们布局。不过,还有一点。我们需要连接按钮来更新骨骼,并且还需要在骨骼发生变化时更新标签。

我不会详细介绍所有细节。您所需要做的就是创建一个标签、几个按钮以及供按钮调用的函数。另外,我们需要一个函数来在值更改时更新标签。

让我们从更新标签的函数开始。由于我们知道属性的名称,因此我们可以进行简单的查找来获取当前值并更改标签:

class BoneAttribute(tk.Frame):
    ...
    def refresh(self):
        value = "{0:.4f}".format(getattr(self.bone, self.attr))
        self.value.configure(text=value)

这样,我们就可以随时更新标签。

现在只需定义按钮的功能即可。有更好的方法可以做到这一点,但一个简单、直接的方法是只使用一些 if 语句。增量函数可能如下所示:

...
plus_button = tk.Button(self, text="+", command=self.do_incr)
...

def do_incr(self):
    if self.attr == "w":
        self.bone.incrW()
    elif self.attr == "x":
        self.bone.incrX()
    elif self.attr == "y":
        self.bone.incrY()
    elif self.attr == "z":
        self.bone.incrZ()

    self.refresh()

do_decr 函数是相同的,只是它调用了一次递减函数。

就是这样。这里的关键点是将较大的问题分解为较小的问题,然后一次解决一个较小的问题。无论您有 3 个骨骼还是 300 个骨骼,您唯一需要编写的额外代码是最初创建骨骼对象的位置。 UI 代码保持完全相同。

关于python - 如何在循环中制作 tkinter 框架并更新对象值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58331886/

相关文章:

python - PyPI 拒绝从 Windows 上传的 token 身份验证

python - 在 pandas 中的非唯一(重复)单元格上传播值

python - 将 QTableWidget 写入 .csv 或 .xls

python - numpy.pad 添加的填充量是声明的两倍

c# - 动态编译 LINQ 查询以验证字典值

python - 使用 OAuth 服务器端的 Facebook 注销

python - 为什么这个元胞自动机代码在 Python 3.x 中工作得很好,但在 Python 2.x 中却不行?

list - TypeError : list indices must be integers or slices,未列出

python - 字典中的交叉列表(超过两个)

python - 一种使用字典对列表中的整数进行排序的方法