Skip to content
Advertisement

Interrupting Kivy Toggle Button State Change on Click

I’ve got a toggle button that I’m using to start and stop a process. When the process is being stopped, I have a popup box that comes up, asking the user to enter a password to confirm they want to end the process.

Only once the correct password is provided do I want the state of the toggle button to change from “down” to “normal” as the process being ended uses the toggle button state to determine if the process should continue running.

The problem I’m having at the moment is that the moment the toggle button is pressed, the state changes from “down” to “normal” and thus ends the process before the password box to authenticate the user is shown.

Any advice on how to interrupt the state change of the toggle button when it is clicked?

EDITED!

main.py:

# import kivy modules
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from kivy.config import Config
Config.set('kivy', 'exit_on_escape', '0')

# import self defined backend class
from ProcessPanel import ProcessPanel


class Automation(App):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        return AppPanels()


class AppPanels(TabbedPanel):

    process_tab = ObjectProperty(None)
    process_tab_panel = ObjectProperty(None)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)


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

ProcessPanel.py:

# import kivy modules
from kivy.properties import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.properties import ObjectProperty

# import self defined backend classes
from ToolStopWindow import ToolStopWindow


class ProcessPanel(BoxLayout):

    process_toggle = ObjectProperty()

    def __init__(self, **kwargs):

        # inherit BoxLayout Attributes and Methods
        super().__init__(**kwargs)
        self.num = 0
        # create the custom query pop up window
        self.TSW = ToolStopWindow(passwd="testPassword",
                                  title_text="Are you sure you want to stop the process?",
                                  sub_title_text="Enter Password to Stop Process...",
                                  external_button=self.process_toggle)

        self.TSW.confirm_btn.bind(on_release=self.end_process)

        self.process_toggle.bind(_do_press=self.toggle_switch_state())

        self.process_schedule = []

    # Determine if process has been activated
    def toggle_switch_state(self):
        if self.process_toggle.state == "normal":
            self.process_schedule = Clock.schedule_interval(self.my_process, 5)
            self.process_toggle._do_unpress()
        else:
            self.TSW.open()

    def my_process(self, dt):
        self.num = self.num + 1
        print(self.num)

    def end_process(self):
        self.process_schedule.cancel()


class StartStopToggle(ToggleButton):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def _do_unpress(self):
        if (not self.allow_no_selection and
                self.group and self.state == 'down'):
            return
        self._release_group(self)
        self.state = 'normal' if self.state == 'down' else 'down'

ToolStopWindow.py:

import time

from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup


class ToolStopWindow(Popup):

    passwd_box = ObjectProperty()
    passwd = ObjectProperty()

    confirm_btn = ObjectProperty()
    confirm_btn_callback = ObjectProperty()

    cancel_btn = ObjectProperty()

    title_text = ObjectProperty()

    sub_title = ObjectProperty()
    sub_title_text = ObjectProperty()

    external_button = ObjectProperty()

    def __init__(self, **kwargs):

        super().__init__(**kwargs)
        self.is_true = False

    def check_pass(self):

        if self.passwd_box.text == self.passwd:
            self.sub_title.text = "Password Correct!"
            time.sleep(1)
            self.external_button._do_unpress()
            self.dismiss()

        else:
            self.is_true = False
            self.passwd_box.text = ""
            self.sub_title.text = "Invalid Password!"

        return

    def reset_label_text(self):

        if self.sub_title.text != self.sub_title_text:
            self.sub_title.text = self.sub_title_text

Automation.kv:

<AppPanels>:
    process_tab: process_tab
    process_tab_panel: process_tab_panel

    id: Tab_Level
    size_hint: 1, 1
    pos_hint: {'center_x': .5, 'center_y': .5}
    do_default_tab: False
    tab_pos: 'top_mid'
    tab_width: root.width/5
    font_size: 24

    TabbedPanelItem:
        id: process_tab
        text: "Process Tab"

        ProcessPanel:
            id: process_tab_panel

<ProcessPanel>:
    orientation: "vertical"

    process_toggle: process_toggle

    Button:
        text: "normal button"
        on_release: root.my_process(self)

    StartStopToggle:
        id: process_toggle
        on_state: root.toggle_switch_state()

<StartStopToggle>:
    text: "Start Process Schedule"
    font_size: 36

<ToolStopWindow>:
    id: close_window
    auto_dismiss: False
    title: root.title_text
    size_hint: 0.8, 0.8

    passwd_box: passwd_box
    confirm_btn: confirm_btn
    cancel_btn: cancel_btn
    sub_title: sub_title

    BoxLayout:
        id: main_panel
        orientation: "vertical"

        Label:
            id: sub_title
            text: root.sub_title_text
            font_size: 36

        TextInput:
            id: passwd_box
            multiline: False
            password: True
            on_text: root.reset_label_text()
            on_text_validate: root.check_pass()

        BoxLayout:
            id: Buttons
            orientation: "horizontal"

            Button:
                id: confirm_btn
                text: "Confirm!"
                on_release: root.check_pass()

            Button:
                id: cancel_btn
                text: "Cancel"
                on_release: root.dismiss()

What I would like to do is bind the _do_press function to a function in the ProcessPanel class, but I keep getting an attribute error saying “‘None type’ object has no attribute ‘bind'”, i assume this is becuase the id’s i’m assigning to the UI objects are assigned after init?

Additionally, there i’m a bit stuck on how to cancel the clock i have created to periodically call the my_process function when the correct password is given.

I hope the addition of this example is helpful!

Advertisement

Answer

Hi I figured out how to use your answer in my code, posted answer below in case anyone finds it useful.

Not sure why but in a stand alone example, the on_request_close feature isnt working, despite the code being the same as the project i’m working on and it working there, spent a while trying to locate the issue but can’t find it…. Either way its working for me now so that even when i try to close the app, i am asked for a password to stop the process before the app will close

main.py:

# import kivy modules
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from kivy.config import Config
Config.set('kivy', 'exit_on_escape', '0')

# import self defined backend class
from StartStopToggle import StartStopToggle
from ProcessPanel import ProcessPanel
from ToolStopWindow import ToolStopWindow


class Automation(App):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        Window.bind(on_request_close=self.on_request_close)
        return AppPanels()

    def on_request_close(self, *args):
        if self.root.process_tab_panel.process_toggle.state == "down":
            self.root.process_tab_panel.process_toggle.TSW.opening_object = self
            self.root.process_tab_panel.process_toggle.TSW.open()
        else:
            self.stop()
        return True

    def do_tsw_function(self):
        self.stop()


class AppPanels(TabbedPanel):

    process_tab = ObjectProperty(None)
    process_tab_panel = ObjectProperty(None)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)




if __name__ == '__main__':
    Automation().run()# import kivy modules
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.properties import ObjectProperty
from kivy.config import Config
Config.set('kivy', 'exit_on_escape', '0')

# import self defined backend class
from ProcessPanel import ProcessPanel


class Automation(App):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        return AppPanels()


class AppPanels(TabbedPanel):

    process_tab = ObjectProperty(None)
    process_tab_panel = ObjectProperty(None)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)


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

ProcessPanel.py:

# import kivy modules
from kivy.properties import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.properties import ObjectProperty

# import self defined backend classes
from ToolStopWindow import ToolStopWindow


class ProcessPanel(BoxLayout):

    process_toggle = ObjectProperty()

    def __init__(self, **kwargs):

        # inherit BoxLayout Attributes and Methods
        super().__init__(**kwargs)
        self.num = 0
        # create the custom query pop up window

        self.process_schedule = []

    # Determine if process has been activated
    def toggle_switch_state(self):
        if self.process_toggle.state == "down":
            self.process_schedule = Clock.schedule_interval(self.my_process, 5)
            self.process_toggle.text = "Stop Process Schedule"
        else:
            self.process_schedule.cancel()
            self.process_toggle.text = "Start Process Schedule"

    def my_process(self, dt):
        self.num = self.num + 1
        print(self.num)

    def end_process(self):
        self.process_schedule.cancel()

ToolStopWindow.py

import time

from kivy.properties import ObjectProperty
from kivy.uix.popup import Popup


class ToolStopWindow(Popup):

    passwd_box = ObjectProperty()
    passwd = ObjectProperty()

    confirm_btn = ObjectProperty()
    confirm_btn_callback = ObjectProperty()

    cancel_btn = ObjectProperty()

    title_text = ObjectProperty()

    sub_title = ObjectProperty()
    sub_title_text = ObjectProperty()

    opening_object = ObjectProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def check_pass(self):
        if self.passwd_box.text == self.passwd:
            self.passwd_box.text = ""
            self.sub_title.text = "Password Correct!"
            time.sleep(1)
            self.dismiss()
            self.opening_object.do_tsw_function()

        else:
            self.passwd_box.text = ""
            self.sub_title.text = "Invalid Password!"

    def reset_label_text(self):
        if self.sub_title.text != self.sub_title_text:
            self.sub_title.text = self.sub_title_text

StartStopToggle.py:

from kivy.uix.togglebutton import ToggleButton
from ToolStopWindow import ToolStopWindow


class StartStopToggle(ToggleButton):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.TSW = ToolStopWindow(passwd="password",
                                  title_text="Are you sure you want to stop the process?",
                                  sub_title_text="Enter Password to Stop Process...")

    def _do_press(self):
        self.TSW.opening_object = self
        self.TSW.open()

    def do_tsw_function(self):
        self._do_actual_press()

    def _do_actual_press(self):
        if (not self.allow_no_selection and
                self.group and self.state == 'down'):
            return
        self._release_group(self)
        self.state = 'normal' if self.state == 'down' else 'down'

Automation.kv

<AppPanels>:
    process_tab: process_tab
    process_tab_panel: process_tab_panel

    id: Tab_Level
    size_hint: 1, 1
    pos_hint: {'center_x': .5, 'center_y': .5}
    do_default_tab: False
    tab_pos: 'top_mid'
    tab_width: root.width/5
    font_size: 24

    TabbedPanelItem:
        id: process_tab
        text: "Process Tab"

        ProcessPanel:
            id: process_tab_panel

<ProcessPanel>:
    orientation: "vertical"

    process_toggle: process_toggle

    Button:
        text: "normal button"
        on_release: root.my_process(self)

    StartStopToggle:
        id: process_toggle
        on_state: root.toggle_switch_state()

<StartStopToggle>:
    text: "Start Query Schedule"


<ToolStopWindow>:
    id: close_window
    auto_dismiss: False
    title: root.title_text
    size_hint: 0.8, 0.8

    passwd_box: passwd_box
    confirm_btn: confirm_btn
    cancel_btn: cancel_btn
    sub_title: sub_title

    BoxLayout:
        id: main_panel
        orientation: "vertical"

        Label:
            id: sub_title
            text: root.sub_title_text
            font_size: 36

        TextInput:
            id: passwd_box
            multiline: False
            password: True
            on_text: root.reset_label_text()
            on_text_validate: root.check_pass()

        BoxLayout:
            id: Buttons
            orientation: "horizontal"

            Button:
                id: confirm_btn
                text: "Confirm!"
                on_release: root.check_pass()

            Button:
                id: cancel_btn
                text: "Cancel"
                on_release: root.dismiss()
Advertisement