KivyMD 2.0.1dev - BottomAppBar (function binding not working)

51 views
Skip to first unread message

J. E.

unread,
Nov 20, 2024, 10:27:37 AMNov 20
to Kivy users support
Hi there,

I struggle for several days now. I want to use KivyMD 2.0.1dev in my App Project. Right now I try to build a demonstrator and want to use the bottomappbar.
But it seems I cannot bind my python functions to the on_release or other button functions..
I klick the buttons (action, fabbutton) on the bottomappbar, but nothing happens. When I use a normal button it works, but not on the bottomappbar. I dont know what I am doing wrong? Anybody else got this problem?

#:kivy 2.3.0
#:import Window kivy.core.window.Window
#:import MDActionBottomAppBarButton kivymd.uix.appbar.MDActionBottomAppBarButton

<PhotoCard>:
size_hint: None, None
width: Window.width * 0.9
height: Window.height * 0.8
radius: [15, 15, 15, 15]
elevation: 2
pos_hint: {"center_x": 0.5}
style: "elevated"
md_bg_color: app.theme_cls.surfaceColor

BoxLayout:
orientation: 'vertical'
padding: "10dp"
spacing: "5dp"
MDLabel:
text: "Bild"
halign: "center"
size_hint_y: None
height: root.height * 0.1
theme_text_color: "Primary"
Image:
source: root.source
size_hint: 1, None
height: root.height * 0.8
allow_stretch: True
keep_ratio: True
MDLabel:
text: f"Nr: {root.index}"
halign: "center"
size_hint_y: None
height: root.height * 0.1
theme_text_color: "Primary"

<MainScreen>:
mainlayout: mainlayout
image_layout: image_layout
BoxLayout:
id: mainlayout
orientation: 'vertical'
spacing: '5dp'

# TopAppBar für Menü
MDTopAppBar:
type: "small"
#size_hint_x: .8
pos_hint: {"center_x": .5}
elevation: 2
MDTopAppBarLeadingButtonContainer:
MDActionTopAppBarButton:
icon: "dots-vertical"
on_release: root.open_menu(self)
MDTopAppBarTitle:
text: "Photo App"
pos_hint: {"center_x": .5}

# ScrollView für Bilder
ScrollView:
id: scroll_view
#size_hint: 1, 0.85 # Angepasst für BottomAppBar
do_scroll_x: False
do_scroll_y: True
BoxLayout:
id: image_layout
orientation: 'vertical'
size_hint_y: None
spacing: '10dp'
padding: '5dp'
height: self.minimum_height

# BottomAppBar für Aktionen
MDBottomAppBar:
id: bottom_appbar
action_items:
[
MDActionBottomAppBarButton(icon="exit-to-app", on_action_button_press=root.stop_app(self)),
]

MDFabBottomAppBarButton:
icon: "camera"
on_release: root.generate_random_image(self)



ElliotG

unread,
Nov 20, 2024, 2:17:46 PMNov 20
to Kivy users support
use on_press or on_release, not on_action_button_press.  You can use a lambda (or functools partial)  if you want to directly call a function with it's parameter, otherwise you would just use the name of the method you want to call.
Here is an example:

from kivy.lang import Builder
from kivymd.app import MDApp

KV = '''
#:import MDActionBottomAppBarButton kivymd.uix.appbar.MDActionBottomAppBarButton

MDScreen:
    md_bg_color: self.theme_cls.backgroundColor

    MDBottomAppBar:
        id: bottom_appbar
        action_items:
            [
            MDActionBottomAppBarButton(icon="gmail", on_release=lambda obj: print('hello using a lambda')),
            MDActionBottomAppBarButton(icon="bookmark", on_release=app.another_action),
            ]

        MDFabBottomAppBarButton:
            icon: "plus"
'''


class Example(MDApp):

    def another_action(self, obj):
        print(f'Another action was called...')

    def build(self):
        self.theme_cls.theme_style = "Dark"
        return Builder.load_string(KV)


Example().run()


Message has been deleted

J. E.

unread,
Nov 21, 2024, 4:43:41 AMNov 21
to Kivy users support
It doesn work, tried also the kivy example (https://kivymd.readthedocs.io/en/latest/components/appbar/#bottomappbar).
If I press the button in the bottomappbar, nothing happens.

I use kivymd 2.0.1dev.

The button function in the topappbar menu works.


from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.card import MDCard
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
from PIL import Image as PILImage
import numpy as np
import os
import random
import logging
import traceback
from kivy.logger import Logger
import sys
from kivymd.uix.appbar import (
MDTopAppBar,
MDTopAppBarTitle,
MDTopAppBarLeadingButtonContainer,
MDTopAppBarTrailingButtonContainer,
MDActionTopAppBarButton,
MDBottomAppBar,
MDFabBottomAppBarButton,
MDActionBottomAppBarButton
)
from kivymd.uix.menu import MDDropdownMenu
from kivy.properties import StringProperty, NumericProperty
from kivymd.uix.appbar import MDActionBottomAppBarButton


# Logger Configuration
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# Set Logging Level
Logger.setLevel(logging.DEBUG)

# Add Crash Handler
def custom_exception_handler(exc_type, exc_value, exc_traceback):
"""Handle uncaught exceptions"""
Logger.error('Uncaught exception:', exc_info=(exc_type, exc_value, exc_traceback))
# Call original sys.excepthook
sys.__excepthook__(exc_type, exc_value, exc_traceback)

sys.excepthook = custom_exception_handler

def is_android():
"""Check if app is running on Android"""
try:
from kivy.utils import platform
return platform == "android"
except ImportError:
return False

def get_image_dir():
"""Determine correct image directory based on platform"""
try:
if is_android():
# Android-specific import
from android import mActivity
context = mActivity.getApplicationContext()
image_dir = os.path.join(context.getExternalFilesDir(None).getAbsolutePath(), "images")
Logger.debug(f'Android image directory: {image_dir}')
else:
# Desktop directory
image_dir = "./images"
Logger.debug(f'Desktop image directory: {image_dir}')
# Create directory if it doesn't exist
if not os.path.exists(image_dir):
os.makedirs(image_dir)
Logger.debug(f'Created image directory: {image_dir}')
return image_dir
except Exception as e:
Logger.error(f'Error creating image directory: {str(e)}')
return "./images" # Fallback

# Global image directory
IMAGE_DIR = get_image_dir()

class PhotoCard(MDCard):
"""Class for photo cards"""
source = StringProperty("") # Property for image path
index = NumericProperty(0) # Property for index
def __init__(self, photo_path, index, **kwargs):
super().__init__(**kwargs)
self.source = photo_path
self.index = index
Logger.debug(f'Created PhotoCard with image: {photo_path} and index: {index}')

class MainScreen(MDScreen):
"""Main screen of the app"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Define menu items
self.menu_items = [
{
"text": "Toggle Theme",
"on_release": self.toggle_theme,
"leading_icon": "theme-light-dark"
}
]
# Create dropdown menu
self.dropdown_menu = MDDropdownMenu(
items=self.menu_items,
width_mult=2,
)

# ScrollView for images (95%)
self.scroll_view = ScrollView(
size_hint=(1, 0.95),
do_scroll_x=False,
do_scroll_y=True
)
# BoxLayout for images
self.image_layout = BoxLayout(
orientation='vertical',
size_hint_y=None,
spacing='10dp',
padding=('5dp', '5dp', '5dp', '5dp')
)
self.image_layout.bind(minimum_height=self.image_layout.setter('height'))
self.scroll_view.add_widget(self.image_layout)
self.add_widget(self.scroll_view)

self.image_count = 0

# Debug events for buttons
self.bind(on_kv_post=self.setup_buttons)

def setup_buttons(self, *args):
"""Set up button events after KV loading"""
Logger.debug('Setting up button events')
try:
camera_btn = self.ids.camera_btn
exit_btn = self.ids.exit_btn
camera_btn.bind(
on_press=lambda x: Logger.debug('Camera button pressed'),
on_release=self.generate_random_image
)
exit_btn.bind(
on_press=lambda x: Logger.debug('Exit button pressed'),
on_release=self.stop_app
)
Logger.debug('Button events setup complete')
except Exception as e:
Logger.error(f'Error setting up buttons: {str(e)}')

def change_actions_items(self):
self.root.ids.bottom_appbar.action_items = [
MDActionBottomAppBarButton(icon="magnify"),
MDActionBottomAppBarButton(icon="trash-can-outline"),
MDActionBottomAppBarButton(icon="download-box-outline"),
]


def generate_random_image(self, instance):
"""Generate a new image"""
print("DEBUG: generate_random_image called")
Logger.debug('MainScreen: generate_random_image called')
try:
Logger.debug('Starting image generation/capture...')
if is_android():
# Android camera functionality
from android.permissions import request_permissions, Permission
request_permissions([
Permission.CAMERA,
Permission.WRITE_EXTERNAL_STORAGE,
Permission.READ_EXTERNAL_STORAGE
])
from plyer import camera
# Filename for photo
img_path = os.path.join(IMAGE_DIR, f"camera_image_{random.randint(1, 10000)}.jpg")
def on_camera_status(status):
"""Callback for camera status"""
if status == 'completed':
Logger.debug(f'Photo successfully taken: {img_path}')
# Add image as new card
self.image_count += 1
self.add_photo_card(img_path, self.image_count)
Logger.debug('Camera image successfully added')
else:
Logger.error(f'Camera status: {status}')
# Start camera
try:
camera.take_picture(
filename=img_path,
callback=on_camera_status
)
Logger.debug(f'Camera started with target file: {img_path}')
except Exception as camera_error:
Logger.error(f'Error starting camera: {str(camera_error)}')
Logger.error(traceback.format_exc())
else:
# Desktop: Generate random image
Logger.debug('Desktop mode: Generating random image...')
width = min(Window.width, 1080)
height = int(width * 16 / 9)
# Create 3D array for image
img_array = np.zeros((height, width, 3), dtype=np.uint8)
# Generate random color
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
Logger.debug(f'Generated color RGB: ({r}, {g}, {b})')
# Fill array with color
img_array[:, :, 0] = r # Red channel
img_array[:, :, 1] = g # Green channel
img_array[:, :, 2] = b # Blue channel
# Convert to PIL Image
img = PILImage.fromarray(img_array, 'RGB')
img_path = os.path.join(IMAGE_DIR, f"random_image_{random.randint(1, 10000)}.png")
Logger.debug(f'Saving image to: {img_path}')
# Save with full quality
img.save(img_path, format='PNG', quality=100)

self.image_count += 1
self.add_photo_card(img_path, self.image_count)
Logger.debug(f'Image successfully generated and added with RGB: ({r}, {g}, {b})')
except Exception as e:
Logger.error(f'Error generating/capturing image: {str(e)}')
Logger.error(traceback.format_exc())

def add_photo_card(self, photo_path, index):
"""Add a new PhotoCard"""
try:
Logger.debug(f'Adding PhotoCard with image: {photo_path} and index: {index}')
card = PhotoCard(photo_path=photo_path, index=index)
self.image_layout.add_widget(card)
Logger.debug('PhotoCard successfully added')
except Exception as e:
Logger.error(f'Error adding PhotoCard: {str(e)}')
Logger.error(traceback.format_exc())

def stop_app(self, instance):
"""Stop the app"""
print("DEBUG: stop_app called")
Logger.debug('MainScreen: stop_app called')
self.clear_images()
Logger.debug('MainScreen: Images cleared')
MDApp.get_running_app().stop()
Logger.debug('MainScreen: App stopping')

def clear_images(self):
"""Clear all images"""
for widget in self.image_layout.children[:]:
self.image_layout.remove_widget(widget)
# Delete all images in directory
for file in os.listdir(IMAGE_DIR):
file_path = os.path.join(IMAGE_DIR, file)
if os.path.isfile(file_path):
os.remove(file_path)

def toggle_theme(self, *args):
"""Toggle between light and dark theme"""
app = MDApp.get_running_app()
app.theme_cls.theme_style = (
"Light" if app.theme_cls.theme_style == "Dark" else "Dark"
)
self.dropdown_menu.dismiss()
Logger.debug(f'Theme switched to: {app.theme_cls.theme_style}')

class TestApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Olive"
self.theme_cls.material_style = "M3"
Logger.debug('App: Initialization started')
# Set orientation at startup
if is_android():
try:
from jnius import autoclass
activity = autoclass('org.kivy.android.PythonActivity').mActivity
activity.setRequestedOrientation(1)
Window.rotation = 0
Window.orientation = 'portrait'
Logger.debug('Android mode: Portrait orientation forced in __init__')
except Exception as e:
Logger.error(f'Error setting orientation: {str(e)}')

def build(self):
try:
Logger.debug('App: Build method started')
if is_android():
Window.rotation = 0
Window.orientation = 'portrait'
Logger.debug('Android mode: Portrait orientation confirmed in build')
else:
Window.size = (480, 1066)
Logger.debug('Desktop mode: Window size set')
screen = MainScreen()
# Set screen background color
screen.md_bg_color = self.theme_cls.backgroundColor
Logger.debug('App: MainScreen created')
return screen
except Exception as e:
Logger.critical(f'App: Critical error in build(): {str(e)}')
Logger.critical(traceback.format_exc())
return None

def on_start(self):
try:
Logger.debug('App: on_start() called')
if is_android():
Logger.debug('Android mode: Requesting permissions')
from android.permissions import request_permissions, Permission
# Log detailed platform information
from kivy import platform
Logger.debug(f'Platform: {platform}')
Logger.debug(f'Image directory: {IMAGE_DIR}')
request_permissions([
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE"
])
super().on_start()
except Exception as e:
Logger.critical(f'App: Error in on_start(): {str(e)}')
Logger.critical(traceback.format_exc())

def on_pause(self):
Logger.debug('App: on_pause() called')
return True

def on_resume(self):
Logger.debug('App: on_resume() called')
pass

def on_stop(self):
Logger.debug('App: on_stop() called')
pass

# Main program
if __name__ == "__main__":
TestApp().run()



KV

#:kivy 2.3.0
#:import Window kivy.core.window.Window
#:import MDBoxLayout kivymd.uix.boxlayout.MDBoxLayout
#:import MDButton kivymd.uix.button.MDButton
#:import MDButtonIcon kivymd.uix.button.MDButtonIcon
#:import MDButtonText kivymd.uix.button.MDButtonText
#:import MDActionBottomAppBarButton kivymd.uix.appbar.MDActionBottomAppBarButton
#:import MDFabBottomAppBarButton kivymd.uix.appbar.MDFabBottomAppBarButton
size_hint: 1, 0.85
do_scroll_x: False
do_scroll_y: True
BoxLayout:
id: image_layout
orientation: 'vertical'
size_hint_y: None
spacing: '10dp'
padding: '5dp'
height: self.minimum_height

# BottomAppBar unterer Rand
MDBottomAppBar:
id: bottom_appbar
action_items:
[
MDActionBottomAppBarButton(icon="gmail"),
MDActionBottomAppBarButton(icon="bookmark"),
]

MDFabBottomAppBarButton:
icon: "plus"
on_release: root.change_actions_items(self)




ElliotG

unread,
Nov 21, 2024, 9:47:24 PMNov 21
to Kivy users support
I don't have the time tonight to debug this... I can see using the Inspector that the ScrollView is filling the WIndow.  The ScrollView is covering the buttons, so they can not be touched.
The inspector is a very useful tool for debugging layout problems.
> python main.py -m inspector

Then type cntrl-e and touch a widget

ElliotG

unread,
Nov 21, 2024, 9:54:22 PMNov 21
to Kivy users support
Found it... you were declaring a ScrollView in the Python code for mainscreen.  This is redundant, you have defined the ScrollView in KV. 
Changes attached.  This should allow you to make progress.  I do suggest you play around with the inspector it is a very useful tool.

main.py
test.kv
Reply all
Reply to author
Forward
0 new messages