TL; DR - разбейте одну большую проблему на несколько более мелких, а затем решите каждую проблему отдельно.
Главное окно
Начните сглядя на общий дизайн интерфейса. У вас есть два раздела: панель с костями и панель с произвольным текстом. Поэтому первое, что я хотел бы сделать, это создать эти панели в виде фреймов:
root = tk.Tk()
bonePanel = tk.Frame(root, background="forestgreen", bd=2, relief="groove")
textPanel = tk.Frame(root, background="forestgreen", bd=2, relief="groove")
Конечно, вам также нужно использовать pack
или grid
, чтобы расположить их на окне. Я рекомендую pack
, поскольку имеется только две рамки, и они расположены рядом.
Отображение костей
Для панели с костями у вас, кажется, есть один ряд для каждой кости. Итак, я рекомендую создать класс для представления каждой строки. Он может наследовать от Frame и отвечать за все, что происходит внутри этого ряда. Унаследовав от Frame
, вы можете обращаться с ним как с пользовательским виджетом в отношении размещения его на экране.
Цель состоит в том, чтобы код вашего пользовательского интерфейса выглядел примерно так:
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
. Похоже, он состоит из пяти разделов: раздел для отображения идентификатора, а затем четыре почти идентичные разделы для атрибутов. Поскольку единственное различие между этими разделами - это атрибут, который они представляют, имеет смысл представлять каждый раздел как экземпляр класса. Опять же, если класс наследует от Frame
, мы можем рассматривать его так, как будто это был пользовательский виджет.
На этот раз мы должны передать кость и, возможно, строку, сообщающую ей, какой идентификатор обновлять.
Итак, может показаться, что выглядело бы примерно так:
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
идентична, за исключением того, что она вызывает один раз из функций декремента.
И это все. Ключевой момент здесь состоит в том, чтобы разбить вашу более крупную проблему на более мелкие, а затем решать каждую более мелкую проблему по одной. Независимо от того, есть ли у вас три кости или 300, единственный дополнительный код, который вам нужно написать, - это то, где вы изначально создаете объекты кости. Код пользовательского интерфейса остается точно таким же.