Кнопка меню Kivy - PullRequest
       10

Кнопка меню Kivy

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

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

from kivy.app import App
from kivy.clock import Clock
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.relativelayout import RelativeLayout
from functools import partial
from kivy.uix.behaviors.button import ButtonBehavior
from kivy.uix.label import Label
from kivy.properties import StringProperty, DictProperty


class MenuButton(ButtonBehavior, BoxLayout):

    # code inspired from:
        # https://github.com/kivy/kivy/wiki/Menu-on-long-touch

    # example use:
        # MenuButton(text = 'Button Text', action = 'function_as_string', options = { 'Option 1': 'function_as_string', 'Option 2': 'other_fun_as_str' } )
        # text will be applied to the buttons label, and action will only occur if the menu isn't triggered
        # in options, the keys are used as the menu options, and their values are the functions you wish to call


    text = StringProperty('')                                                           # stores the button text
    action = StringProperty('')                                                         # stores the button release action
    options = DictProperty({})                                                          # stores menu button text and actions

    def __init__(self, **kwargs):
        super(MenuButton,self).__init__(**kwargs)
        # by default, contains a button and relative layout
        # the menu() function adds the menu to the relative layout
        # this way, the menu can appear below the button
        self.orientation = 'vertical'
        self.size_hint_y = None
        button = Button(text=self.text, size_hint_y=None, height=32)
        self.add_widget(button)
        self.bind(on_touch_down=self.create_clock, on_touch_up=self.delete_clock)
        self.m = RelativeLayout()
        self.add_widget(self.m)

    def create_clock(self, widget, touch, *args):
        if self.children[1].collide_point(touch.x, touch.y):
            callback = partial(self.menu, touch)
            Clock.schedule_once(callback, .3)
            touch.ud['event'] = callback


    def delete_clock(self, widget, touch, *args):
        if self.children[1].collide_point(touch.x, touch.y):
            Clock.unschedule(touch.ud['event'])
            if self.m.children == []:
                eval(self.action)

    def menu(self, touch, *args):
        menu = BoxLayout(id='box',pos=[0,0],orientation='vertical',center=touch.pos)
        for text, fun_pass in self.options.iteritems():
            but = Button(id=fun_pass, text=text, height=32)
            but.bind(on_release=partial(self.close_menu, menu))
            but.bind(on_release=self.call_function)
            menu.add_widget(but)
        close = Button(text='close', height=32)
        close.bind(on_release=partial(self.close_menu, menu))
        menu.add_widget(close)
        self.m.add_widget(menu)


    def close_menu(self, widget, *args):                                                # clears all menu widgets from the relative layout
        self.m.clear_widgets()

    def call_function(self, widget, *args):                                             # calls function in the app by literally evaluating the string
        eval(widget.id) 


class Test(App):

    def build(self):
        f = GridLayout(cols=1)
        f.add_widget( MenuButton(text='Testing', action='Test().pp("going")', options={ '1': 'Test().pp("a")', '2': 'Test().pp("b")' } ))
        f.add_widget( MenuButton(text='Testing', action='Test().pp("going")', options={ '1': 'Test().pp("a")', '2': 'Test().pp("b")' } ))
        self.root = f

    def pp(self,text):
        print text

if __name__ == '__main__':
    Test().run()

Проблема в том, что когда ясоздайте несколько MenuButton (), между ними будет много места.Я бы хотел, чтобы между ними не было пробела, и когда кнопка нажата, раскрывающееся меню будет перекрывать любые виджеты, которые находятся под ней.Как мне это сделать?

1 Ответ

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

Я думаю, что основной проблемой является использование GridLayout, потому что оно явно пытается избежать наложения виджетов.Если ваше приложение использует FloatLayout, ограничений нет, поэтому наложение виджетов легко.Вот моя версия вашего кода с использованием FloatLayout:

from kivy.app import App
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.relativelayout import RelativeLayout
from functools import partial
from kivy.uix.behaviors.button import ButtonBehavior
from kivy.properties import StringProperty, DictProperty


class MenuButton(ButtonBehavior, BoxLayout):

    # code inspired from:
        # https://github.com/kivy/kivy/wiki/Menu-on-long-touch

    # example use:
        # MenuButton(text = 'Button Text', action = 'function_as_string', options = { 'Option 1': 'function_as_string', 'Option 2': 'other_fun_as_str' } )
        # text will be applied to the buttons label, and action will only occur if the menu isn't triggered
        # in options, the keys are used as the menu options, and their values are the functions you wish to call


    text = StringProperty('')                                                           # stores the button text
    action = StringProperty('')                                                         # stores the button release action
    options = DictProperty({})                                                          # stores menu button text and actions

    def __init__(self, **kwargs):
        super(MenuButton,self).__init__(**kwargs)
        # by default, contains a button and relative layout
        # the menu() function adds the menu to the relative layout
        # this way, the menu can appear below the button
        self.orientation = 'vertical'
        self.size_hint_y = None
        self.button = Button(text=self.text, size_hint_y=None, height=32)
        self.add_widget(self.button)
        self.height = self.button.height
        self.bind(on_touch_down=self.create_clock, on_touch_up=self.delete_clock)
        self.m = RelativeLayout()
        self.m.size_hint_y = None
        self.m.height = 0
        self.add_widget(self.m)

    def create_clock(self, widget, touch, *args):
        if self.children[1].collide_point(touch.x, touch.y):
            callback = partial(self.menu, touch)
            Clock.schedule_once(callback, .3)
            touch.ud['event'] = callback


    def delete_clock(self, widget, touch, *args):
        if self.children[1].collide_point(touch.x, touch.y):
            if 'event' in touch.ud:   # avoid non-existent key errors
                Clock.unschedule(touch.ud['event'])
            if self.m.children == []:
                eval(self.action)

    def menu(self, touch, *args):
        menu = BoxLayout(orientation='vertical')
        height_calc  = 0   # calculate the total height needed when the menu is opened
        for text, fun_pass in self.options.iteritems():
            but = Button(id=fun_pass, text=text, height=32, size_hint_y=None)
            height_calc += but.height
            but.bind(on_release=partial(self.close_menu, menu))
            but.bind(on_release=self.call_function)
            menu.add_widget(but)
        close = Button(text='close', height=32, size_hint_y=None)
        height_calc += close.height
        close.bind(on_release=partial(self.close_menu, menu))
        menu.add_widget(close)
        self.m.add_widget(menu)

        # adjust position and height of expanded MenuButton
        self.m.height = height_calc
        self.height = self.button.height + self.m.height
        self.pos = (0, self.orig_y - self.m.height)

        # make sure this MenuButton is drawn on top of any other MenuButtons
        app = App.get_running_app()
        app.root.remove_widget(self)
        app.root.add_widget(self)


    def close_menu(self, widget, *args):                                                # clears all menu widgets from the relative layout
        self.m.clear_widgets()

        # reset height and position of MenuButton when not expanded
        self.m.height = 0
        self.height = self.button.height
        self.pos = (0, self.orig_y)

    def call_function(self, widget, *args):                                             # calls function in the app by literally evaluating the string
        eval(widget.id)


class Test(App):

    def build(self):
        f = FloatLayout()
        self.mb1 = MenuButton(text='Testing1', action='App.get_running_app().pp("going")', options={ '1': 'App.get_running_app().pp("a")', '2': 'App.get_running_app().pp("b")' } )
        self.mb1.orig_y = f.height - self.mb1.button.height    # save the original y position, so it can be restored later
        self.mb1.pos = (0, self.mb1.orig_y)
        f.add_widget(self.mb1)
        self.mb2 = MenuButton(text='Testing2', action='App.get_running_app().pp("going")', options={ '1': 'App.get_running_app().pp("a")', '2': 'App.get_running_app().pp("b")' } )
        self.mb2.orig_y = f.height - self.mb1.button.height - self.mb2.button.height    # save the original y position, so it can be restored later
        self.mb2.pos = (0, self.mb2.orig_y)
        f.add_widget(self.mb2)
        self.root = f

        f.bind(size=self.sizeChanged)    # handle size adjustments when app is displayed

    def sizeChanged(self, *args):
        # make sure the MenuButtons are always at the top
        self.mb1.orig_y = self.root.height - self.mb1.button.height
        self.mb2.orig_y = self.root.height - self.mb1.button.height - self.mb2.button.height
        self.mb1.pos = (0, self.mb1.orig_y)
        self.mb2.pos = (0, self.mb2.orig_y)

    def pp(self,text):
        print text

if __name__ == '__main__':
    Test().run()

Я также сделал пару других изменений.Я добавляю защиту от ключевых ошибок в методе delete_clock (я видел это иногда).Также добавлена ​​привязка для настройки размеров и положения MenuButtons, когда изменяется размер приложения.

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