Многолинейный комбобокс в Ткинтере - PullRequest
0 голосов
/ 03 ноября 2018

Возможно ли иметь многострочное поле для ввода текста с раскрывающимися опциями?

В настоящее время у меня есть графический интерфейс пользователя с многострочным текстовым виджетом, в котором пользователь пишет некоторые комментарии, но я хотел бы иметь несколько предустановленных параметров для этих комментариев, чтобы пользователь мог нажать на раскрывающуюся кнопку для выбора.

Насколько я могу судить, виджет Combobox не позволяет изменять высоту поля ввода текста, поэтому он фактически ограничен одной строкой (произвольное увеличение ширины не вариант). Поэтому я думаю, что мне нужно сделать подкласс виджета «Текст» и каким-то образом добавить функциональность для раскрывающегося списка, чтобы показать эти (потенциально усеченные) предустановленные параметры.

Я предвидел ряд проблем с этим маршрутом, и хотел убедиться, что я не упускаю ничего очевидного из существующих встроенных виджетов, которые могут делать то, что мне нужно.

Ответы [ 2 ]

0 голосов
/ 10 ноября 2018

Из отзывов Терри стало ясно, что не существует простого способа решить эту проблему, поэтому я создал собственный класс, который оборачивает текст и кнопку в рамку, с Toplevel, содержащим список, порожденный функцией обратного вызова кнопки. Я добавил пару полезных функций, таких как подсветка опций в окне списка, и сопоставил привязки основного виджета с внутренним виджетом «Текст», чтобы с ним было легче работать. Пожалуйста, оставьте комментарий, если есть какие-либо явные плохие практики здесь; Я определенно все еще довольно неопытен! Но я надеюсь, что это поможет всем, кто ищет многострочный комбинированный список!

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()
0 голосов
/ 03 ноября 2018

Я не думаю, что вы что-то упускаете. Обратите внимание, что ttk.Combobox является составным виджетом. Это подклассы ttk.Entry и имеет прикрепленный ttk.Listbox.

Чтобы сделать многострочный эквивалент, подкласс Text. как вы предложили. Возможно, назовите это ComboText. Прикрепите фрейм с несколькими текстами, доступными только для чтения, или текст с несколькими записями, каждый с отдельным тегом. Выберите метод, чтобы открыть комбинированный текст, и методы, чтобы закрыть его, с копированием выделения или без него в основной текст. Запишите исходный документ, описывающий, как управлять вещью.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...