我最近一直在尝试使用 Kivy 来实现我想做的个人项目:一个可以让你创建带有一些细节的 DnD 角色的应用程序。该角色将存储在 JSON 文件中并加载到主屏幕上。问题是,当使用复选框进行性别选择时,它根本不起作用,我找不到问题。
这是main.py文件:
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()
还有我的 Kivy 文件:
<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'
我希望该应用程序允许您选中和取消选中复选框,但将选择限制为只有一个框(仅限一种性别)。
代码本身也可能非常混乱,但目前它运行良好 XD
您可以通过使用
group
的 CheckBox
属性来获得您想要的行为。请参阅文档。
试试这个:
GridLayout:
cols: 2
spacing: 40
padding: 20
size_hint_y: None
height: self.minimum_height
Label:
text: "Man"
CheckBox:
id: man_checkbox
group: "gender"
Label:
text: "Woman"
CheckBox:
id: woman_checkbox
group: "gender"