Tkinter中的多行组合框

时间:2018-11-03 15:56:12

标签: python tkinter

是否可能有一个带有下拉选项的多行文本输入字段?

我目前有一个带有多行文本小部件的GUI,用户可以在其中编写一些注释,但是我想为这些注释提供一些预设选项,用户可以单击下拉按钮进行选择。

据我所知,“组合框”小部件不允许更改文本输入字段的高度,因此实际上限制为一行(任意扩展宽度不是一种选择)。因此,我认为我需要做的是对Text小部件进行子类化,并以某种方式添加功能来显示这些(可能被截断的)预设选项。

我预见到这条路线将面临许多挑战,并希望确保我不会遗漏任何可以满足我需要的内置小部件。

2 个答案:

答案 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()