是否可能有一个带有下拉选项的多行文本输入字段?
我目前有一个带有多行文本小部件的GUI,用户可以在其中编写一些注释,但是我想为这些注释提供一些预设选项,用户可以单击下拉按钮进行选择。
据我所知,“组合框”小部件不允许更改文本输入字段的高度,因此实际上限制为一行(任意扩展宽度不是一种选择)。因此,我认为我需要做的是对Text小部件进行子类化,并以某种方式添加功能来显示这些(可能被截断的)预设选项。
我预见到这条路线将面临许多挑战,并希望确保我不会遗漏任何可以满足我需要的内置小部件。
答案 0 :(得分:1)
我认为您没有丢失任何东西。注意ttk.Combobox是一个复合窗口小部件。它是ttk.Entry的子类,并附加了ttk.Listbox。
要使多行等效,请子类化Text。如您所建议。也许将其称为ComboText。将带有多个只读文本的框架或带有多个条目的文本附加在一起,每个条目都有一个单独的标签。选择一种方法来打开组合文本,并选择一种方法来关闭组合文本,无论是否将选择复制到主文本中。写下描述如何操作事物的初始文档。
答案 1 :(得分:1)
Terry的反馈清楚地表明,没有简单的方法可以解决此问题,因此我创建了一个自定义类,该类将Text和Button包装到框架中,并且顶层包含由按钮的回调函数产生的Listbox。我添加了几个“精打细算”的功能,例如列表框中的选项突出显示,并且将主窗口小部件的绑定映射到内部的文本窗口小部件,以使其更易于使用。如果此处有任何明显的不良做法,请发表评论;我绝对还是没有经验的!但是我希望这对正在寻找多行组合框的其他人有所帮助!
class ComboText(tk.Frame):
def __init__(self, parent=None, **kwargs):
super().__init__(parent)
self.parent = parent
self._job = None
self.data = []
self['background'] = 'white'
self.text = tk.Text(self, **kwargs)
self.text.pack(side=tk.LEFT, expand=tk.YES, fill='x')
symbol = u"\u25BC"
self.button = tk.Button(self,width = 2,text=symbol, background='white',relief = 'flat', command = self.showOptions)
self.button.pack(side=tk.RIGHT)
#pass bindings from parent frame widget to the inner Text widget
#This is so you can bind to the main ComboText and have those bindings
#apply to things done within the Text widget.
#This could also be applied to the inner button widget, but since
#ComboText is intended to behave "like" a Text widget, I didn't do that
bindtags = list(self.text.bindtags())
bindtags.insert(0,self)
self.text.bindtags(tuple(bindtags))
def showOptions(self):
#Get the coordinates of the parent Frame, and the dimensions of the Text widget
x,y,width,height = [self.winfo_rootx(), self.winfo_rooty(), self.text.winfo_width(), self.text.winfo_height()]
self.toplevel = tk.Toplevel()
self.toplevel.overrideredirect(True) #Use this to get rid of the menubar
self.listbox = tk.Listbox(self.toplevel,width=width, height =len(self.data))
self.listbox.pack()
#Populate the options in the listbox based on self.data
for s in self.data:
self.listbox.insert(tk.END,s)
#Position the Toplevel so that it aligns well with the Text widget
list_height = self.listbox.winfo_reqheight()
self.toplevel.geometry("%dx%d+%d+%d" % (width, list_height, x, y+height))
self.listbox.focus_force()
self.listbox.bind("<Enter>", self.ListboxHighlight)
self.listbox.bind("<Leave>",self.stopListboxHighlight)
self.listbox.bind("<Button-1>",self.selectOption)
self.toplevel.bind("<Escape>", self.onCancel)
self.toplevel.bind("<FocusOut>", self.onCancel)
def ListboxHighlight(self,*ignore):
#While the mouse is moving within the listbox,
#Highlight the option the mouse is over
x,y = self.toplevel.winfo_pointerxy()
widget = self.toplevel.winfo_containing(x,y)
idx = self.listbox.index("@%s,%s" % (x-self.listbox.winfo_rootx(),y-self.listbox.winfo_rooty()))
self.listbox.selection_clear(0,100) #very sloppy "Clear all"
self.listbox.selection_set(idx)
self.listbox.activate(idx)
self._job = self.after(25,self.ListboxHighlight)
def stopListboxHighlight(self,*ignore):
#Stop the recurring highlight function.
if self._job:
self.after_cancel(self._job)
self._job = None
def onCancel(self,*ignore):
#Stop callback function to avoid error once listbox destroyed.
self.stopListboxHighlight()
#Destroy the popup Toplevel
self.toplevel.destroy()
def selectOption(self,event):
x,y = [event.x,event.y]
idx = self.listbox.index("@%s,%s" % (x,y))
if self.data:
self.text.delete('1.0','end')
self.text.insert('end',self.data[idx])
self.stopListboxHighlight()
self.toplevel.destroy()
self.text.focus_force()
def setOptions(self,optionList):
self.data = optionList
#Map the Text methods onto the ComboText class so that
#the ComboText can be treated like a regular Text widget
#with some other options added in.
#This was necessary because ComboText is a subclass of Frame, not Text
def __getattr__(self,name):
def textMethod(*args, **kwargs):
return getattr(self.text,name)(*args, **kwargs)
return textMethod
if __name__ == '__main__':
root = tk.Tk()
ct = ComboText(root, width = 50, height = 3)
ct.pack()
ct.setOptions(['Option %d' % i for i in range (0,5)])
root.mainloop()