Search code examples
pythonkivykivymd

KivyMD: how can I change active item of MDSegmentedControl


I don't know how to change active (selected) segment of MDSegmentedControl in KivyMD without touching it (e.g. after loading data from db and opening a screen). I can set current_active_segment property to one of the MDSegmentedControlItems but it has no impact on UI. Example code below:

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.input.providers.mouse import MouseMotionEvent
from kivy.clock import Clock

kvFloatLayout = """
MDScreen:
    id: mdscr
    
    MDBoxLayout:
        orientation: "vertical"
        adaptive_height: True
        pos_hint: {"top": 1}
        spacing: "12dp"
        
        MDTopAppBar:
            id: top_app_bar
            pos_hint: {"top": 1}
            title: "Test App"
            elevation: 4
                        
        MDBoxLayout:
            orientation: "vertical"
            adaptive_height: True
            pos_hint: {"top": 1}
            padding: "12dp", "0dp", "12dp", "12dp"
            spacing: "12dp"
            
            MDRaisedButton:
                id: btn_resize
                text: "Resize"
                on_release: app.seg_resize()
               
            MDRaisedButton
                id: btn_change
                text: "Change selection"
                on_release: app.seg_change_selection()
                
            MDSegmentedControl:
                id: seg_options
                MDSegmentedControlItem:
                    id: sci_option1
                    text: "Option left"
                MDSegmentedControlItem:
                    id: sci_option2
                    text: "Option right"
                    
"""

class TestApp(MDApp):
    
    def build(self):
        return Builder.load_string(kvFloatLayout)

    def seg_resize(self):
        #Workaround from stackoverflow (size not working in .kv - https://stackoverflow.com/questions/74999000/kivymd-mdsegmentedcontrol-size)
        self.root.ids.seg_options.ids.segment_panel.width = (Window.width - dp(24))
        
    def seg_change_selection(self):
        sc = self.root.ids.seg_options
        print("---sc:")
        print(sc)
        sci = self.root.ids.sci_option2
        print("---sci:")
        print(sci)
        print("---sc.current_active_segment before:")
        print(sc.current_active_segment)
        sc.current_active_segment = sci
        print("---sc.current_actilve_segment after:")
        print(sc.current_active_segment.text)
        #A poor workaround with mouse click:
        #Clock.schedule_once(self.simulate_mouse_click, 1)
     
    def simulate_mouse_click(self, *args):
        x = 0.75*Window.width
        y = Window.height - \
            self.root.ids.top_app_bar.height - \
            self.root.ids.btn_resize.height - \
            self.root.ids.btn_change.height - \
            dp(12) * 3 - 40
        touch = MouseMotionEvent(None, 0, (x,y))  # args are device, id, spos
        touch.button = 'left'
        touch.pos = (x,y)
        touch.x = touch.px = touch.ox = x
        touch.y = touch.py = touch.oy = y
        print("---touch")
        print(str(touch))
        Window.dispatch('on_touch_down', touch)
        #1/0
        
TestApp().run()

Any ideas or workarounds?

The only way I was able to make it work was scheduling a mouse click over the right segment item but of course this is far from being a good and reliable solution. It is this part of code:

#Clock.schedule_once(self.simulate_mouse_click, 1)

Solution

  • Unfortunately, it does not appear that the designers of the MDSegmentedControl did not allow for the utility that you want. Another way to trick the MDSegmentedControl into thinking that it has been clicked on is to create a FakeTouch class:

    class FakeTouch(object):
        pass
    

    And then, use that class in your seg_change_selection() method:

    def seg_change_selection(self):
        sc = self.root.ids.seg_options
        print("---sc:")
        print(sc)
        sci = self.root.ids.sci_option2
        print("---sci:")
        print(sci)
        print("---sc.current_active_segment before:")
        print(sc.current_active_segment)
        # sc.current_active_segment = sci
        touch = FakeTouch()
        touch.x = sci.center_x
        touch.y = sci.center_y
        sc.on_press_segment(sci, touch)
        print("---sc.current_actilve_segment after:")
        print(sc.current_active_segment.text)
        # A poor workaround with mouse click:
        # Clock.schedule_once(self.simulate_mouse_click, 1)
    

    Similar to your simulated mouse click, but perhaps a but simpler.