[Python] My Kivy code doesn't detect checkbox selection

Discussão em 'Python' iniciado por Stack, Março 13, 2025.

  1. Stack

    Stack Membro Participativo

    I have recently been trying Kivy for a personal project I wanted to do: an app that lets you create a DnD character with a few details.

    That character would store on a JSON file and load up on the main screen. The problem is, when using checkboxes for the gender selection, it doesn't work at all and I can't find the problem.

    Here is the main.py file:

    from kivy.app import App
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.graphics import Color, Line
    from kivy.uix.button import Button
    from kivy.properties import ObjectProperty, ListProperty
    import random
    import json
    import os


    class MainScreen(Screen):
    def get_random_color(self):
    # Returns a random RGBA color
    return [random.random() for _ in range(3)] + [0.8]

    def add_character_button(self, character_data):
    # Create a button for each saved character with a random color and only the name
    button = Button(
    text=character_data["name"],
    size_hint_y=None,
    height=50,
    background_normal='',
    background_color=character_data['color']
    )
    button.custom_color = button.background_color.copy()
    button.bind(pos=self.update_border, size=self.update_border)

    # Add a border to the button
    with button.canvas.before:
    Color(0, 0, 0, 1) # Set black color for border
    self.border_line = Line(rectangle=(button.x, button.y, button.width, button.height), width=1)

    button.bind(on_release=lambda btn: self.show_character_details(character_data))

    self.ids.character_list.add_widget(button)

    def update_border(self, button, _):
    # Update border position and size when button size or position changes
    self.border_line.rectangle = (button.x, button.y, button.width, button.height)

    def load_user_characters(self):
    # Load characters from a JSON file and add them to the screen
    if os.path.exists('characters.json'):
    with open('characters.json', 'r') as f:
    characters = json.load(f)
    for character in characters:
    self.add_character_button(character)

    def show_character_details(self, character_data):
    character_details_screen = self.manager.get_screen('character_details')
    character_details_screen.set_character_data(character_data) # Pass character data
    self.manager.current = 'character_details'


    class SecondScreen(Screen):
    character_name = ObjectProperty(None)
    character_age = ObjectProperty(None)
    character_power1 = ObjectProperty(None)
    character_power2 = ObjectProperty(None)

    def save_character(self):
    main_screen = self.manager.get_screen('main')
    name = self.ids.character_name.text
    age = self.ids.character_age.text
    gender = 'Man' if self.ids.man_checkbox.active else 'Woman' if self.ids.woman_checkbox.active else 'Other'
    power1 = self.ids.character_power1.text
    power2 = self.ids.character_power2.text
    color = main_screen.get_random_color()

    character_data = {
    'name': name,
    'age': age,
    'gender': gender,
    'power1': power1,
    'power2': power2,
    'color': color
    }

    # Save character to the list
    self.save_characters_to_file(character_data)

    # Add button to the main screen dynamically
    main_screen.add_character_button(character_data)

    # Clear the inputs after saving
    self.ids.character_name.text = ''
    self.ids.character_age.text = ''
    self.ids.character_power1.text = ''
    self.ids.character_power2.text = ''
    self.ids.man_checkbox.active = False
    self.ids.woman_checkbox.active = False

    # Switch back to the main screen
    self.manager.current = 'main'

    def save_characters_to_file(self, new_character):
    characters = []

    # Load existing characters from file if it exists
    if os.path.exists('characters.json'):
    with open('characters.json', 'r') as f:
    characters = json.load(f)

    # Add the new character
    characters.append(new_character)

    # Save updated character list to file
    with open('characters.json', 'w') as f:
    json.dump(characters, f)

    selected_gender = None
    def set_gender(self, checkbox, value):
    if value == True: # If this checkbox is active
    # Uncheck the others
    if checkbox == self.ids.man_checkbox:
    self.ids.woman_checkbox.active = False
    elif checkbox == self.ids.woman_checkbox:
    self.ids.man_checkbox.active = False


    class CharacterDetailsScreen(Screen):
    character_color = ListProperty([1, 1, 1, 1]) #default
    character_data = None #default
    def on_enter(self):
    if self.character_data: # Check if character_data is set
    self.ids.character_name.text = self.character_data['name']
    self.ids.character_age.text = self.character_data['age']
    self.ids.character_gender.text = self.character_data['gender']
    self.ids.character_power1.text = self.character_data['power1']
    self.ids.character_power2.text = self.character_data['power2']
    self.character_color = self.character_data['color']

    def set_character_data(self, character):
    self.character_data = character # Store character data

    class RosterApp(App):
    def build(self):
    sm = ScreenManager()
    sm.add_widget(MainScreen(name='main'))
    sm.add_widget(SecondScreen(name='second'))
    sm.add_widget(CharacterDetailsScreen(name='character_details'))

    # Load characters into the main screen when the app starts
    sm.get_screen('main').load_user_characters()

    return sm


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



    And my Kivy file:

    <MainScreen>:
    FloatLayout:
    orientation: 'vertical'

    # Light yellow background
    FloatLayout:
    canvas.before:
    Rectangle:
    pos: self.pos
    size: self.size
    source: 'img.png'

    # Round button in the bottom right corner
    Button:
    text: ""
    size_hint: None, None
    size: 50, 50
    pos_hint: {'right': 0.95, 'bottom': 0.05}
    background_normal: 'plus.webp'
    background_color: 0.737, 0.635, 0.514, 1 # light almost-off golden
    border: (50, 50, 50, 50) # Simulates a round border
    canvas.before:
    Color:
    rgba: 0.086, 0.086, 0.086, 0.75 # Same dark gray color
    Ellipse: # Draw an ellipse to make the button round
    pos: self.pos
    size: self.size
    on_release:
    app.root.current = 'second'

    # dark gray top bar with app name
    FloatLayout:
    size_hint_y: None
    height: 50
    pos_hint: {'top': 1}
    canvas.before:
    Color:
    rgba: 0.086, 0.086, 0.086, 0.75 # dark gray color
    Rectangle:
    pos: self.pos
    size: self.size
    Label:
    text: "DnD Roster Panel"
    color: 1, 1, 1, 1 # white text
    pos_hint: {'center_x': 0.5, 'center_y': 0.5}

    ScrollView:
    size_hint: (1, 0.8)
    pos_hint: {'top': 0.9}
    BoxLayout:
    id: character_list
    orientation: 'vertical'
    size_hint_y: None
    height: self.minimum_height # Adjusts height to fit content
    spacing: 10
    padding: 10


    <SecondScreen>:
    FloatLayout:
    canvas.before:
    Rectangle:
    pos: self.pos
    size: self.size
    source: 'img.png'

    FloatLayout:
    orientation: 'vertical'
    FloatLayout:
    size_hint_y: None
    height: 50
    pos_hint: {'top': 1}
    canvas.before:
    Color:
    rgba: 0.086, 0.086, 0.086, 0.75 # dark gray color
    Rectangle:
    pos: self.pos
    size: self.size
    Label:
    text: "DnD Roster Panel"
    color: 1, 1, 1, 1 # white text
    pos_hint: {'center_x': 0.5, 'center_y': 0.5}

    ScrollView:
    size_hint: 1, 0.85 # Make it take up the remaining space
    pos_hint: {'top': 0.85}
    do_scroll_x: False # Disable horizontal scrolling

    BoxLayout:
    orientation: 'vertical'
    size_hint_y: None
    height: self.minimum_height
    padding: [20, 20, 20, 20] # Add some padding for spacing
    spacing: 20 # Add some space between elements

    Label:
    text: 'Name'
    color: 1, 1, 1, 1
    halign: "center"

    TextInput:
    id: character_name
    hint_text: ""
    multiline: False
    size_hint_y: None
    height: 40

    Label:
    text: 'Age'
    color: 1, 1, 1, 1
    halign: "center"

    TextInput:
    id: character_age
    hint_text: ""
    multiline: False
    input_filter: 'int'
    size_hint_y: None
    height: 40

    Label:
    text: 'Select Gender'
    color: 1, 1, 1, 1
    halign: "center"

    GridLayout:
    cols: 2
    spacing: 40
    padding: 20
    size_hint_y: None
    height: self.minimum_height
    Label:
    text: "Man"
    CheckBox:
    id: man_checkbox
    on_active: root.set_gender(self, self.active) # Uncheck female if male is checked

    Label:
    text: "Woman"
    CheckBox:
    id: woman_checkbox
    on_active: root.set_gender(self, self.active) # Uncheck male if female is checked


    Label:
    text: 'First Power'
    color: 1, 1, 1, 1
    halign: "center"

    TextInput:
    id: character_power1
    hint_text: ""
    multiline: False
    size_hint_y: None
    height: 40

    Label:
    text: 'Second Power'
    color: 1, 1, 1, 1
    halign: "center"

    TextInput:
    id: character_power2
    hint_text: ""
    multiline: False
    size_hint_y: None
    height: 40

    Button:
    text: "Save Character"
    size_hint_y: None
    height: 50
    on_release:
    root.save_character() # Call save_character method

    <CharacterDetailsScreen>:
    FloatLayout:
    canvas.before:
    Rectangle:
    pos: self.pos
    size: self.size
    source: 'img.png'

    FloatLayout:
    orientation: 'vertical'
    FloatLayout:
    size_hint_y: None
    height: 50
    pos_hint: {'top': 1}
    canvas.before:
    Color:
    rgba: self.parent.parent.character_color
    Rectangle:
    pos: self.pos
    size: self.size
    Label:
    text: 'DnD Roster Panel'
    color: 1, 1, 1, 1 # white text
    pos_hint: {'center_x': 0.5, 'center_y': 0.5}

    Label:
    id: character_name
    text: 'Name'
    size_hint_y: None
    pos_hint: {'center_x': 0.5, 'top': 0.85}
    height: 20
    font_size: 30


    GridLayout:
    cols: 2
    spacing: 10
    padding: 20
    size_hint_y: None
    height: self.minimum_height # Adjusts height to fit content

    # Age
    Label:
    text: "Age: "
    size_hint_y: None
    height: 50
    canvas.before:
    Color:
    rgba: 1, 1, 1, 1 # Black border color
    Line:
    rectangle: (self.x, self.y, self.width, self.height)

    Label:
    id: character_age
    text: "Age:"
    size_hint_y: None
    height: 50
    canvas.before:
    Color:
    rgba: 1, 1, 1, 1 # Black border color
    Line:
    rectangle: (self.x, self.y, self.width, self.height)

    # Gender
    Label:
    text: "Gender: "
    size_hint_y: None
    height: 50
    canvas.before:
    Color:
    rgba: 1, 1, 1, 1 # Black border color
    Line:
    rectangle: (self.x, self.y, self.width, self.height)

    Label:
    id: character_gender
    text: "Gender:"
    size_hint_y: None
    height: 50
    canvas.before:
    Color:
    rgba: 1, 1, 1, 1 # Black border color
    Line:
    rectangle: (self.x, self.y, self.width, self.height)

    # First Power
    Label:
    text: "First Power:"
    size_hint_y: None
    height: 50
    canvas.before:
    Color:
    rgba: 1, 1, 1, 1 # Black border color
    Line:
    rectangle: (self.x, self.y, self.width, self.height)

    Label:
    id: character_power1
    text: "First Power:"
    size_hint_y: None
    height: 50
    canvas.before:
    Color:
    rgba: 1, 1, 1, 1 # Black border color
    Line:
    rectangle: (self.x, self.y, self.width, self.height)

    # Second Power
    Label:
    text: "Second Power:"
    size_hint_y: None
    height: 50
    canvas.before:
    Color:
    rgba: 1, 1, 1, 1 # Black border color
    Line:
    rectangle: (self.x, self.y, self.width, self.height)

    Label:
    id: character_power2
    text: "Second Power:"
    size_hint_y: None
    height: 50
    canvas.before:
    Color:
    rgba: 1, 1, 1, 1 # Black border color
    Line:
    rectangle: (self.x, self.y, self.width, self.height)

    Button:
    text: ""
    size_hint: None, None
    size: 50, 50
    pos_hint: {'left': 0.95, 'bottom': 0.05}
    background_normal: 'back.png'
    background_color: 0.737, 0.635, 0.514, 1 # light almost-off golden
    border: (50, 50, 50, 50) # Simulates a round border
    canvas.before:
    Color:
    rgba: 0.086, 0.086, 0.086, 0.75 # Same dark gray color
    Ellipse: # Draw an ellipse to make the button round
    pos: self.pos
    size: self.size
    on_release:
    app.root.current = 'main'


    I would like the app to let you check and uncheck the checkboxes, but restricting the selection to only one box (only one gender).

    The code itself might also be very messy, but for now it was working fine XD

    Continue reading...

Compartilhe esta Página