How to insert a scrollbar to a menu?

限于喜欢 提交于 2021-01-28 05:56:16

问题


I want to add another widget, in this case a scale widget, to a menu widget in tkinter. Right now the only solutions I see are creating a new command and open a new window with the scale widget or creating the scale widget elsewhere. Both don't seem too appealing to me.

Any ideas how to archive this are welcome :)


回答1:


You cant add a scrollbar to it, but I have coded something similar to this. Its a hacky way and maybe its hard to understand but I can try to explain.

Note as Bryan mentioned in the linked Thread, this seems to be a a Windows only solution.

import tkinter as tk


def my_first_function():
    print('first')

def my_second_function():
    print('second')

def check_for_scroll(event):
    check = root.call(event.widget, "index","active")
    if check == 0: #index of button up
        scroll_up()
        root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
    if check == file_menu.index('end'):
        scroll_down()
        root.after(100,lambda e=event:check_for_scroll(e))

def scroll_up():
    index_of_first_command=1
    index_of_last_command=1
    label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
    try:
        for i, k in enumerate(dict_of_commands):
            if k == label_of_last_command:
                previous_command_label = list(dict_of_commands)[i-1]
                previous_command = list(dict_of_commands.values())[i-1]
                if i != 0: #avoid to get the last as first
                    file_menu.delete(index_of_first_command) #first before pull down button
                    file_menu.insert_command(index_of_first_command,
                                             label=previous_command_label,
                                             command=previous_command)
    except Exception as e:
        print(e)

def scroll_down():
    index_of_first_command=1
    index_of_last_command=1
    label_of_last_command = file_menu.entrycget(index_of_last_command, 'label')
    try:
        for i, k in enumerate(dict_of_commands):
            if k == label_of_last_command:
                next_command_label = list(dict_of_commands)[i+1]
                next_command = list(dict_of_commands.values())[i+1]

                file_menu.delete(index_of_first_command) #first after pull up button
                file_menu.insert_command(index_of_last_command,
                                         label=next_command_label,
                                         command=next_command)
    except:
        pass

space = '         '
dict_of_commands = {'first' : my_first_function,
                    'second': my_second_function}

root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)

file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.bind('<<MenuSelect>>', check_for_scroll)

file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])

file_menu.add_command(label='first', command=my_first_function)

file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])


root.mainloop()

So this code creates your window and a menubar on it as usal:

root = tk.Tk()
menubar = tk.Menu(root)
root.config(menu=menubar)
file_menu = tk.Menu(menubar,tearoff=0)
menubar.add_cascade(label='File', menu=file_menu)
file_menu.add_command(label=space+u'\u25B2'+space, font=["Arial", 8,'bold'])
file_menu.add_command(label='first', command=my_first_function)
file_menu.add_command(label=space+u'\u25BC'+space, font=["Arial", 8,'bold'])
root.mainloop()

Important for you, is this line here:

file_menu.bind('<<MenuSelect>>', check_for_scroll)

This line binds the event MenuSelect and it happens/triggers if your cursor hovers over a command of your menu. To this event I have bound a function called check_for_scroll and it looks like this:

def check_for_scroll(event):
    check = root.call(event.widget, "index","active")
    if check == 0: #index of button up
        scroll_up()
        root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms
    if check == file_menu.index('end'):
        scroll_down()
        root.after(100,lambda e=event:check_for_scroll(e))

The line below checks for the index of the command that has triggered the event. With this we check if its button of our interest like the first or last, with the arrows.

check = root.call(event.widget, "index","active")

if its the first for example this code here is executed:

if check == 0: #index of button up
        scroll_up()
        root.after(100,lambda e=event:check_for_scroll(e)) # check again after 100ms

it calls/triggers the function scroll_up and uses then the after method of tkinter to retrigger itself, like a loop. The scroll_up function is build like the scroll_down just in the opposite direction. Lets have a closer look:

def scroll_up():
    index_of_first_command=1
    index_of_last_command=1
    label_of_last_command = file_menu.entrycget(index_of_first_command, 'label')
    try:
        for i, k in enumerate(dict_of_commands):
            if k == label_of_last_command:
                previous_command_label = list(dict_of_commands)[i-1]
                previous_command = list(dict_of_commands.values())[i-1]
                if i != 0: #avoid to get the last as first
                    file_menu.delete(index_of_first_command) #first before pull down button
                    file_menu.insert_command(index_of_first_command,
                                             label=previous_command_label,
                                             command=previous_command)
    except Exception as e:
        print(e)

In this function we need to know the first and the last position of commands, because we want to delete one and insert another on that position/index. To achieve this I had created a dictionary that contains the label and the the function of the command item of tkinter like below. (This could be created dynamically, but lets keep it for another question)

dict_of_commands = {'first' : my_first_function,
                    'second': my_second_function}

So we iterate over this enumerated/indexed dictionary in our function and check if the k/key/label is our item of interest. If true, we get the previous_command by listing the dictionary keys and get the extract the key before by this line:

next_command_label = list(dict_of_commands)[i+1]

similar to the value of the dictionary with this line:

next_command = list(dict_of_commands.values())[i+1]

After all we can delete one and insert one where we like to with this:

file_menu.delete(index_of_first_command) #first after pull up button
file_menu.insert_command(index_of_last_command,
                         label=next_command_label,
                         command=next_command)

I know that this code can improved by a lot but it seems hard enough to understand as it is. So dont judge me please.

I hope this solves your question, even if it isnt in the way you wanted to. But this code avoids you from hardly code a own menubar.

If there are questions left on my answer, let me know.



来源:https://stackoverflow.com/questions/65887661/how-to-insert-a-scrollbar-to-a-menu

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!