Передача OptionMenu в обратный вызов (или получение ссылки на используемый виджет)
Я работаю над (toplevel in) графическим интерфейсом, который состоит из массива из 8 OptionMenus, каждый из которых содержит один и тот же список опций. В настоящее время я создаю эти виджеты, используя цикл for, и сохраняю ссылки в словаре. Все OptionMenus ссылаются на одну и ту же (лямбда) функцию обратного вызова.
Чтобы оставаться практичным: элементы в списке опций представляют последовательность шагов обработки, и пользователь может изменять порядок процессов.
Изменение в одном из списков приведет к тому, что один процесс будет выполнен дважды, а один процесс - вовсе нет. Однако я хочу, чтобы каждый элемент появлялся только один раз. Следовательно, каждый пользовательский ввод должен сопровождаться вторым изменением OptionMenu.
Например: начальный порядок 1-2-3 -> пользователь изменяет второй процесс: 1-3-3, который автоматически корректируется на: 1-3-2, где каждый процесс снова выполняется только один раз.
Насколько я понимаю, я могу заставить это работать, только если у меня есть ссылка на OptionMenu, который был только что изменен (из функции обратного вызова). Я пытался передать виджет в обратный вызов. Пример кода представляет собой попытку реализовать второй предложенный метод, но результат не тот, который я ожидал.
Дело в том, что виджет OptionMenu, похоже, ведет себя несколько иначе, чем другие виджеты. OptionMenu не позволяет переопределить командную функцию. Независимо от того, какой ввод я передаю вместе с командной функцией, кажется, что обратный вызов только возвращает выбор OptionMenu, что является недостаточной информацией для определения моего порядка обработки.
Предложения будут высоко оценены!
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.create_widgets()
def create_widgets(self):
self.active_procs = ['proc 1','proc 2','proc 3','proc 4',
'proc 5','proc 6','proc 7','proc 8']
itemnr, widgets = dict(), dict()
for index in range(8):
name_construct = 'nr' + str(index)
itemnr[name_construct] = tk.StringVar(root)
itemnr[name_construct].set(self.active_procs[index])
widgets[name_construct] = tk.OptionMenu(self, itemnr[name_construct], *self.active_procs,
command=lambda widget=name_construct:
self.order_change(widget))
widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
sticky="nwse", padx=10, pady=10)
def order_change(self,widget):
print(widget)
root = tk.Tk()
root.title("OptionMenu test")
app = Application(master=root)
root.mainloop()
2 ответа
OptionMenu
передаст новое значение в обратный вызов, поэтому вам не нужно ничего делать, чтобы получить новое значение. Вот почему ваш widget
значение не является значением name_construct
- переданное значение перезаписывает значение по умолчанию, которое вы предоставляете в лямбда-выражении.
Чтобы исправить это, вам просто нужно добавить еще один аргумент, чтобы вы могли передать значение name_construct
к обратному вызову, чтобы идти вместе со значением, которое автоматически отправляется.
Это будет выглядеть примерно так:
widgets[name_construct] = tk.OptionMenu(..., command=lambda value, widget=name_construct: self.order_change(value, widget))
...
def order_change(self, value, widget):
print(value, widget)
Обратите внимание OptionMenu
на самом деле не виджет tkinter. Это просто удобная функция, которая создает стандарт Menubutton
с ассоциированным Menu
, Затем он создает один пункт в меню для каждой опции и связывает все это вместе с StringVar
,
Вы можете получить то же самое поведение довольно легко. Это позволит изменить то, что делает каждый элемент в меню при выборе.
Для интересующихся ниже вы можете найти пример кода того, как я получил поведение виджета, которое я хотел. Я воспользовался советом Брайана, чтобы заменить OptionMenu для комбинации меню / меню. Я также воспользовался этим сообщением, чтобы найти повторяющиеся записи в моем списке технологических заказов.
Любые мысли или предложения о том, как сделать это более понятным или коротким способом, или как получить ту же функциональность с другим интерфейсом (например, перетаскивание), приветствуются!
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.create_widgets()
def create_widgets(self):
# Assisting text
l1 = tk.Label(self, text = "Data in", font=(None, 15))
l1.grid(row=0, column=2)
l2 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
l2.grid(row=1, column=2)
l3 = tk.Label(self, text = "Data out", font=(None, 15))
l3.grid(row=11, column=2)
l4 = tk.Label(self, text = u'\N{BLACK DOWN-POINTING TRIANGLE}', font=(None, 15))
l4.grid(row=10, column=2)
# Process list
self.active_procs = ['proc a','proc b','proc c','proc d',
'proc e','proc f','proc g','proc h']
self.the_value, self.widgets, self.topmenu = dict(), dict(), dict()
for index in range(8):
name_construct = 'nr' + str(index)
self.the_value[name_construct] = tk.StringVar(root)
self.the_value[name_construct].set(self.active_procs[index])
self.widgets[name_construct] = tk.Menubutton(self, textvariable=
self.the_value[name_construct],
indicatoron=True)
self.topmenu[name_construct] = tk.Menu(self.widgets[name_construct],
tearoff=False)
self.widgets[name_construct].configure(menu=self.topmenu[name_construct])
for proc in self.active_procs:
self.topmenu[name_construct].add_radiobutton(label=proc, variable=
self.the_value[name_construct],
command=lambda proc=proc,
widget=name_construct:
self.order_change(proc,widget))
self.widgets[name_construct].grid(row=index+2, column=2, columnspan=2,
sticky="nwse", padx=10, pady=10)
def order_change(self,proc,widget):
# Get the index of the last changed Menubutton
index_user_change = list(self.widgets.keys()).index(widget)
procs_order = [] # Current order from widgets
for index in range(8):
name_construct = 'nr' + str(index)
procs_order.append(self.widgets[name_construct].cget("text"))
# 1 change may lead to 1 double and 1 missing process
doubles = self.list_duplicates_of(procs_order,proc)
if len(doubles) == 2: # If double processes are present...
doubles.remove(index_user_change) # ...remove user input, change the other
missing_proc = str(set(self.active_procs)^set(procs_order)).strip('{"\'}')
index_change_along = int(doubles[0])
# Update references
self.active_procs[index_user_change] = proc
self.active_procs[index_change_along] = missing_proc
# Update widgets
name_c2 = 'nr'+str(index_change_along)
self.the_value[name_c2].set(self.active_procs[index_change_along])
self.widgets[name_c2].configure(text=missing_proc)
def list_duplicates_of(self,seq,item):
start_at = -1
locs = []
while True:
try:
loc = seq.index(item,start_at+1)
except ValueError:
break
else:
locs.append(loc)
start_at = loc
return locs
root = tk.Tk()
root.title("OptionMenu test")
app = Application(master=root)
root.mainloop()