1

Hace unos días, estuve preguntando sobre cómo añadir botones de forma dinámica y pasar información entre clases en python y Kivy.

Sin embargo, he intentado seguir con el desarrollo de la aplicación y vuelvo a estar atascado con un problema distinto. Esta vez, a raíz del código que aportó el usuario FJSevilla, quiero hacer que cuando le presione a un botón de los que aparece se muestre una pantalla distinta con cierta información que tengo que recibir de unos sensores por medio de un servidor MQTT. También podría ser que en vez de abrir una pantalla distinta, se muestre en un menú desplegable debajo del botón. Un funcionamiento u otro me es indiferente.

Os pido por favor si me podéis echar una mano. Llevo desde la semana pasada intentando hacer que eso funcione, pero no lo consigo. Os añado el código que hay hasta el momento, aunque podéis verlo también en el hilo de la pregunta anterior.

main.py

import os   # Paquete necesario para las funciones que requieren de recursos del sistema operativo
import threading
import subprocess   # Paquete necesario para crear y llamar a subprocesos del sistema

import kivy     # Paquete general para crear la interfaz
from kivy.app import App    # Funciones para implementar nuestra ventana o App
from kivy.uix.boxlayout import BoxLayout  # Funciones para implementar una capa base donde se colocarán los elementos
from kivy.uix.anchorlayout import AnchorLayout  # Funciones para colocar elementos en sitios concretos del layout
from kivy.uix.gridlayout import GridLayout  # Funciones para organizar elementos en matrices
from kivy.uix.button import Button  # Funciones para manejar botones
from kivy.uix.listview import ListItemButton    # Funciones para manejar listas
from kivy.clock import Clock,  mainthread  # Necesario para actualizar los elementos
from kivy.event import EventDispatcher  # Necesario para crear propiedades y eventos
from kivy.properties import ObjectProperty    # Importación de propiedades de lista Kivy
from kivy.config import Config  # Para las configuraciones que sean necesarias


# Configuración del tamaño de pantalla
Config.set('graphics', 'width', 1024)
Config.set('graphics', 'height', 600)
# Los botones, widgets, layout y demás son todos widgets

def ping_scan():  # Función para detectar dispositivos en la red.
    with open(os.devnull, "wb") as limbo:  # devnull es como un pozo sin fondo del que no se puede recuperar nada
        # y elimina el error por pantalla en tiempo de ejecución
        for n in range(0, 10 + 1):  # Se añade el + 1 para que alcance el límite máximo introducido
            ip = "192.168.1.{0}".format(n)
            res = subprocess.Popen(['ping', '-n', '1', '-w', '200', ip], stdout=limbo, stderr=limbo).wait()
            if res:
                print("INACTIVA => " + ip)
            else:
                print("ACTIVA => " + ip)
                yield ip


class Contenedor(BoxLayout):    # Creamos una clase Contenedor que hereda las funciones de BoxLayout
    scanning = threading.Event()
    def __init__(self):
        super().__init__()    # Con super se heredan todas las propiedades de Contenedor. Esto es
        # necesario para añadir nuevas propiedades

        self.button_box = ButtonBox()  # Instanciación a caja para botones
        self.info_box = InfoBox()    # Instanciación a caja para información
        self.logo_box = LogoBox()    # Instanciación a caja para logo

        self.add_widget(self.button_box)    # Añade la caja al layout
        self.button_box.add_widget(self.logo_box)  # Añade la caja para el logo en la caja de botones
        self.add_widget(self.info_box)    # Añade la caja para info

        self.button_box.btn_buscar.bind(on_press=self.start_ping_scan)

    def start_ping_scan(self, event=None):
        if self.scanning.is_set():
            self.scanning.clear()
            self.button_box.btn_buscar.text = "Buscar"
        else:
            self.scanning.set()
            threading.Thread(target=self._ping_scan).start()
            self.button_box.btn_buscar.text = "Cancelar búsqueda"

    def _ping_scan(self):
        self.info_box.limpiar_info()
        for ip in ping_scan():
            if not self.scanning.is_set():
                return
            self.info_box.agregar_dispositivo(ip)


class ButtonBox(BoxLayout):    # LayOut para añadir los botones
    def __init__(self):
        # nonlocal lista_ip   # Hacemos que este valor sea no local
        super().__init__()
        self.btn_buscar = Button(text="Buscar dispositivos")  # Crea un botón para buscar dispositivos
        self.btn_conectar = Button(text="Conectar")  # Crea un botón para conectarse a los dispositivos
        self.btn_desconectar = Button(text="Desconectar")  # Crea un botón para desconectarse de los dispositivos
        self.add_widget(self.btn_buscar)    # Añade el botón de buscar al LayOut
        self.add_widget(self.btn_conectar)   # Añade el botón de conectar al LayOut
        self.add_widget(self.btn_desconectar)  # Añade el botón de desconectar al LayOut


class InfoBox(BoxLayout):  # LayOut para añadir la información de los dispositivos
    ips = ObjectProperty(None)
    def __init__(self):
        super().__init__()
        self._btn_disp = [] # Lista con las instancias d cada botón

    @mainthread  # Método llamado  desde el hilo hijo
    def agregar_dispositivo(self, ip):
        btn = Button(text=ip)
        self._btn_disp.append(btn)
        self.ips.add_widget(btn)

    @mainthread  # Método llamado  desde el hilo hijo
    def limpiar_info(self):
        for btn in self._btn_disp:
            self.ips.remove_widget(btn)
        self._btn_disp.clear()


class LogoBox(AnchorLayout):  # LayOut para añadir el logo del programa en una esquina
    pass


class InterfazApp(App):  # Creación de la aplicación como tal. Debe llevar el mismo nombre que el archivo .kv
    title = 'Centro de control'  # Nombre del programa
    def build(self):    # Función para que se ponga en marcha nuestra App
        return Contenedor()

    def on_stop(self):
        # Si cerramos la app mintras se están escanenado ips, debemos detener el hilo
        self.root.scanning.clear()


if __name__ == "__main__":  # Obligatorio, aungue no necesario, para Android y Kivy, es un convencionalismo
    InterfazApp().run()

main.kv

<Contenedor>:
    orientation: 'vertical'
    spacing: 10
    # spacing es el espacio que hay entre widgets
    padding: 10
    # padding es el espacio entre el borde de la ventana y el contenido => iz - a - de - ab => Lista para distintos
    canvas:
    # Las instrucciones canvas son instrucciones gráficas para personalizar los widgets
        Color:
            rgb: 0, 0, 0
            # Son valores en tanto por uno. Con rgba añadimos el alfa
        Rectangle:
            size: self.size
            pos: self.pos
            # self hace referencia al widget o layout máx póximo a la indentación
            # En este caso, mismo tamaño y misma posición que Contenedor

<ButtonBox>:
# Por defecto, las BoxLayout vienen orientadas de forma horizontal
    spacing: 10
    padding: 10
    size_hint: 1, None
    # Deshabilitación del tamaño relativo en X e Y
    # width: 650
    height: 50
    canvas:
        Color:
            # rgb: 0.78, 0.78, 0.78
            rgb: 0.65, 0.65, 0.65
        Rectangle:
            size: self.size
            pos: self.pos

<InfoBox>:
    ips: ips
    id: info_root
    orientation: 'vertical'
    spacing: 10, 10

    canvas:
        Color:
            rgba: 1, 1, 1, 0.25
        Rectangle:
            size: self.size
            pos: self.pos

    ScrollView:
        size: self.size
        GridLayout:
            id: ips
            cols: 1
            size_hint_y: None
            height: self.minimum_height
            row_default_height: '50dp'
            row_force_default: True


<LogoBox>:
    spacing: 2
    padding: 5
    size_hint: None, None
    width: 40
    height: 32
    canvas:
        Rectangle:
            source: 'UNIT_n.png'
            size: self.size
            pos: self.pos 

Os doy las gracias de antemano y espero que podáis ayudarme. Si necesitáis más información pedidla y os la daré lo antes posible.

Saludos.

Dratcher
  • 37
  • 6

1 Answers1

2

Tienes múltiples opciones para conseguir lo que quieres.

Para añadir la información debajo de cada botón podrías:

  • Usar un widget DropDown
  • Usar un Accordion.
  • Crear tu propio widget basado en un layout con dos partes, un ToggleButton como título del item (dispositivo en tu caso) y otro widget que se pueda ocultar o mostrar según se seleccione el botón y que contenga la información y opciones para ese item.

    import os   # Paquete necesario para las funciones que requieren de recursos del sistema operativo
    
    import threading
    import subprocess
    
    import kivy
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.anchorlayout import AnchorLayout 
    from kivy.uix.gridlayout import GridLayout
    from kivy.uix.button import Button
    from kivy.clock import Clock,  mainthread
    from kivy.config import Config
    from kivy.uix.togglebutton import ToggleButton
    from kivy.properties import ObjectProperty, StringProperty, BooleanProperty, NumericProperty
    from kivy.uix.scrollview import ScrollView
    from kivy.uix.label import Label
    
    # Configuración del tamaño de pantalla
    Config.set('graphics', 'width', 1024)
    Config.set('graphics', 'height', 600)
    # Los botones, widgets, layout y demás son todos widgets
    
    def ping_scan():
        with open(os.devnull, "wb") as limbo:
            for n in range(0, 10 + 1):
                ip = "192.168.1.{0}".format(n)
                res = subprocess.Popen(['ping', '-n', '1', '-w', '200', ip], stdout=limbo, stderr=limbo).wait()
                if res:
                    print("INACTIVA => " + ip)
                else:
                    print("ACTIVA => " + ip)
                    yield ip
    
    
    class DropDownItem(GridLayout):
        container_title = ObjectProperty() 
        container = ObjectProperty()
        title = StringProperty("")
        hidden = BooleanProperty(False)
        title_height = NumericProperty()
    
        def __init__(self, title="", title_height="50dp", **kwargs):
            super().__init__(**kwargs)
            self.cols = 1
            self.size_hint_y = None
            self.title = title
            self.title_height = title_height
    
            container_title = ToggleButton(height=title_height, size_hint_y=None, text=title)
            container_title.bind(state=self.on_drop_down)
    
            container = GridLayout(size_hint_y=None, cols=1)
            self.add_widget(container_title)
            self.add_widget(container)
    
            self.bind(minimum_height=self.setter("height"))
            self.bind(title=container_title.setter("text"))
            self.bind(title_height=container_title.setter("height"))
            container.bind(minimum_height=container.setter("height"))
            container.padding = ("10dp", "5dp", "10dp", "10dp")
    
            self.container = container
            self.container_title = container_title
            self.hidden = True
    
        def add_widget(self, widget, **kargs):
            if self.container is not None:
                self.container.add_widget(widget, **kargs)
            else:
                super().add_widget(widget, **kargs)
    
        def on_drop_down(self, obj, value):
            print(value)
            if value == "down":
                self.hidden = False
            else:
                self.hidden = True
    
        def on_hidden(self, obj, hidden):
            if hidden:
                self.container.opacity = 0
                self.container.size_hint = 0, 0
                self.container.size = 0, 0
                self.container.disabled = True
            else:
                self.container.opacity = 1
                self.container.size_hint = 1, None
                self.container.disabled = False
                self.container.height = self.container.minimum_height
    
    
    class DropDownList(BoxLayout):
        container_layout = ObjectProperty(None)
        scroll_view = ObjectProperty(None)
        only_one = BooleanProperty(False)
    
        def __init__(self, only_one=False, **kwargs):
            super().__init__(**kwargs)
            self.orientation = "vertical"
            self.spacing = 10, 10
    
            self.scroll_view = ScrollView()
            self.bind(size=self.scroll_view.setter("size"))
            self.add_widget(self.scroll_view)
    
            self.container_layout = GridLayout(cols=1, size_hint_y=None)
            self.container_layout.bind(minimum_height=self.container_layout.setter('height'))
            self.scroll_view.add_widget(self.container_layout)
    
            self.only_one = only_one
            self._items = []
    
        def add_widget(self, widget, **kargs):
            if self.container_layout is not None:
                self.container_layout.add_widget(widget, **kargs)
                self._items.append(widget)
            else:
                super().add_widget(widget, **kargs)
    
        def on_only_one(self, obj, value):
            for item in self._items:
                item.container_title.group = "_ddlist" if value else None
    
        def clear(self):
            for item in self._items:
                self.container_layout.remove_widget(item)
            self._items.clear()
    
    
    class Contenedor(BoxLayout):
        scanning = threading.Event()
    
        def __init__(self):
            super().__init__()
            self.button_box = ButtonBox()  # Instanciación a caja para botones
            self.info_box = InfoBox()    # Instanciación a caja para información
            self.logo_box = LogoBox()    # Instanciación a caja para logo
    
            self.add_widget(self.button_box)    # Añade la caja al layout
            self.button_box.add_widget(self.logo_box)  # Añade la caja para el logo en la caja de botones
            self.add_widget(self.info_box)    # Añade la caja para info
    
            self.button_box.btn_buscar.bind(on_press=self.start_ping_scan)
    
        def start_ping_scan(self, event=None):
            if self.scanning.is_set():
                self.scanning.clear()
            else:
                self.scanning.set()
                threading.Thread(target=self._ping_scan).start()
    
    
        def _ping_scan(self):
            self.info_box.limpiar_info()
            self.button_box.cambiar_texto_btn_buscar("Cancelar búsqueda")
            for ip in ping_scan():
                if not self.scanning.is_set():
                    break
                self.info_box.agregar_dispositivo(ip)
            self.scanning.clear()
            self.button_box.cambiar_texto_btn_buscar("Buscar")
    
    
    
    class ButtonBox(BoxLayout):
        def __init__(self):
            super().__init__()
            self.btn_buscar = Button(text="Buscar dispositivos")
            self.btn_conectar = Button(text="Conectar")
            self.btn_desconectar = Button(text="Desconectar")
            self.add_widget(self.btn_buscar)
            self.add_widget(self.btn_conectar)
            self.add_widget(self.btn_desconectar)
    
        @mainthread  # Método llamado  desde el hilo
        def cambiar_texto_btn_buscar(self, texto):
            self.btn_buscar.text = texto
    
    
    class Info(BoxLayout):
        """Clase  on la opciones de cada dispositivo, ver .kv"""
        info_label = ObjectProperty()
    
    
    class Dispositivo(DropDownItem):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self._info_is_loaded = False
    
        def on_drop_down(self, obj, value):
            self.cargar_informacion()
            super().on_drop_down(obj, value)
    
        def cargar_informacion(self):
            """Método que obtiene la información de cada dispositivo"""
            if not self._info_is_loaded:
                info = Info()
                nombre = "Dispositivo"
                mac = "1A:E2:36:56:7C:BD"
                info.info_label.text = ("[size=24][color=#ff6600]Nombre: [/color][/size]"
                                        f"[size=18][color=#2db300]{nombre}[/color][/size]\n"
                                        "[size=24][color=#ff6600]MAC:      [/color][/size]"
                                        f"[size=18][color=#2db300]{mac}[/color][/size]\n"
                )
                self.add_widget(info)
                self._info_is_loaded = True
    
    
    class InfoBox(DropDownList):  # LayOut para añadir la información de los dispositivos
    
        @mainthread  # Método llamado  desde el hilo hijo
        def agregar_dispositivo(self, ip):
            dispositivo = Dispositivo(title=ip)
            self.add_widget(dispositivo)
    
        @mainthread  # Método llamado  desde el hilo
        def limpiar_info(self):
            self.clear()
    
    
    
    
    
    class LogoBox(AnchorLayout):
        pass
    
    
    class InterfazApp(App):
        title = 'Centro de control'  # Nombre del programa
    
        def build(self):
            return Contenedor()
    
        def on_stop(self):
            self.root.scanning.clear()
    
    
    if __name__ == "__main__":
        InterfazApp().run()
    

    interfaz.kv

    <Contenedor>:
        orientation: 'vertical'
        spacing: 10
        padding: 10
    
        canvas:
            Color:
                rgb: 0, 0, 0
            Rectangle:
                size: self.size
                pos: self.pos
    
    <ButtonBox>:
        spacing: 10
        padding: 10
        size_hint: 1, None
        height: 50
        canvas:
            Color:
                # rgb: 0.78, 0.78, 0.78
                rgb: 0.65, 0.65, 0.65
            Rectangle:
                size: self.size
                pos: self.pos
    
    <LogoBox>:
        spacing: 2
        padding: 5
        size_hint: None, None
        width: 40
        height: 32
        canvas:
            Rectangle:
                source: 'UNIT_n.png'
                size: self.size
                pos: self.pos 
    
    <Info>:
        info_label: info_label
    
        orientation: "vertical"
        size_hint_y: None
        height: "200dp"
    
        canvas:
            Color:
                rgba: 0.52, 0, 0.7, 0.5
            Rectangle:
                pos: self.pos
                size: self.size
        Label:
            id: info_label
            markup: True
    
        BoxLayout:
            size_hint_y: None
            height: "50dp"
            Button:
                text: "Conectar"
            Button:
                text: "Desconectar"
    

introducir la descripción de la imagen aquí

Para mostrar la información en otra ventana podrías:

  • Usar Screen, teniendo en cuenta que kivy está pensado para dispositivos móviles y táctiles, por lo que el concepto de "ventana" no es el mismo que en otros widgets

  • Usar un Popup o un ModalView para mostrar la información encima de tu ventana actual, por ejemplo, usando un modal:

    import os
    import threading
    import subprocess
    
    import kivy
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.anchorlayout import AnchorLayout
    from kivy.uix.gridlayout import GridLayout
    from kivy.uix.button import Button
    from kivy.clock import Clock,  mainthread
    from kivy.config import Config
    from kivy.uix.togglebutton import ToggleButton
    from kivy.properties import ObjectProperty, StringProperty, BooleanProperty, NumericProperty
    from kivy.uix.scrollview import ScrollView
    from kivy.uix.label import Label
    from kivy.uix.modalview import ModalView
    
    Config.set('graphics', 'width', 1024)
    Config.set('graphics', 'height', 600)
    
    
    def ping_scan():
        with open(os.devnull, "wb") as limbo:
            for n in range(0, 10 + 1):
                ip = "192.168.1.{0}".format(n)
                res = subprocess.Popen(['ping', '-n', '1', '-w', '200', ip], stdout=limbo, stderr=limbo).wait()
                if res:
                    print("INACTIVA => " + ip)
                else:
                    print("ACTIVA => " + ip)
                    yield ip
    
    
    class DropDownItem(GridLayout):
        container_title = ObjectProperty() 
        container = ObjectProperty()
        title = StringProperty("")
        hidden = BooleanProperty(False)
        title_height = NumericProperty()
    
        def __init__(self, title="", title_height="50dp", **kwargs):
            super().__init__(**kwargs)
            self.cols = 1
            self.size_hint_y = None
            self.title = title
            self.title_height = title_height
    
            container_title = ToggleButton(height=title_height, size_hint_y=None, text=title)
            container_title.bind(state=self.on_drop_down)
    
            container = GridLayout(size_hint_y=None, cols=1)
            self.add_widget(container_title)
            self.add_widget(container)
    
            self.bind(minimum_height=self.setter("height"))
            self.bind(title=container_title.setter("text"))
            self.bind(title_height=container_title.setter("height"))
            container.bind(minimum_height=container.setter("height"))
            container.padding = ("10dp", "5dp", "10dp", "10dp")
    
            self.container = container
            self.container_title = container_title
            self.hidden = True
    
        def add_widget(self, widget, **kargs):
            if self.container is not None:
                self.container.add_widget(widget, **kargs)
            else:
                super().add_widget(widget, **kargs)
    
        def on_drop_down(self, obj, value):
            print(value)
            if value == "down":
                self.hidden = False
            else:
                self.hidden = True
    
        def on_hidden(self, obj, hidden):
            if hidden:
                self.container.opacity = 0
                self.container.size_hint = 0, 0
                self.container.size = 0, 0
                self.container.disabled = True
            else:
                self.container.opacity = 1
                self.container.size_hint = 1, None
                self.container.disabled = False
                self.container.height = self.container.minimum_height
    
    
    class DropDownList(BoxLayout):
        container_layout = ObjectProperty(None)
        scroll_view = ObjectProperty(None)
        only_one = BooleanProperty(False)
    
        def __init__(self, only_one=False, **kwargs):
            super().__init__(**kwargs)
            self.orientation = "vertical"
            self.spacing = 10, 10
    
            self.scroll_view = ScrollView()
            self.bind(size=self.scroll_view.setter("size"))
            self.add_widget(self.scroll_view)
    
            self.container_layout = GridLayout(cols=1, size_hint_y=None)
            self.container_layout.bind(minimum_height=self.container_layout.setter('height'))
            self.scroll_view.add_widget(self.container_layout)
    
            self.only_one = only_one
            self._items = []
    
        def add_widget(self, widget, **kargs):
            if self.container_layout is not None:
                self.container_layout.add_widget(widget, **kargs)
                self._items.append(widget)
            else:
                super().add_widget(widget, **kargs)
    
        def on_only_one(self, obj, value):
            for item in self._items:
                item.container_title.group = "_ddlist" if value else None
    
        def clear(self):
            for item in self._items:
                self.container_layout.remove_widget(item)
            self._items.clear()
    
    
    class Contenedor(BoxLayout):
        scanning = threading.Event()
    
        def __init__(self):
            super().__init__()
    
            self.button_box = ButtonBox()
            self.info_box = InfoBox()
            self.logo_box = LogoBox()
    
            self.add_widget(self.button_box)
            self.button_box.add_widget(self.logo_box)
            self.add_widget(self.info_box)
            self.button_box.btn_buscar.bind(on_press=self.start_ping_scan)
    
        def start_ping_scan(self, event=None):
            if self.scanning.is_set():
                self.scanning.clear()
            else:
                self.scanning.set()
                threading.Thread(target=self._ping_scan).start()
    
    
    
        def _ping_scan(self):
            self.info_box.limpiar_info()
            self.button_box.cambiar_texto_btn_buscar("Cancelar búsqueda")
            for ip in ping_scan():
                if not self.scanning.is_set():
                    break
                self.info_box.agregar_dispositivo(ip)
            self.scanning.clear()
            self.button_box.cambiar_texto_btn_buscar("Buscar")
    
    
    class ButtonBox(BoxLayout):
        def __init__(self):
            super().__init__()
            self.btn_buscar = Button(text="Buscar dispositivos")
            self.btn_conectar = Button(text="Conectar")
            self.btn_desconectar = Button(text="Desconectar")
            self.add_widget(self.btn_buscar)
            self.add_widget(self.btn_conectar)
            self.add_widget(self.btn_desconectar)
    
        @mainthread
        def cambiar_texto_btn_buscar(self, texto):
            self.btn_buscar.text = texto
    
    
    class InfoBox(BoxLayout):
        ips = ObjectProperty(None)
        def __init__(self):
            super().__init__()
            self._btn_disp = []
    
        @mainthread
        def agregar_dispositivo(self, ip):
            btn = Button(text=ip)
            btn.bind(on_press=self.mostrar_info)
            self._btn_disp.append(btn)
            self.ips.add_widget(btn)
    
        @mainthread
        def limpiar_info(self):
            for btn in self._btn_disp:
                self.ips.remove_widget(btn)
            self._btn_disp.clear()
    
        def mostrar_info(self, obj):
            nombre = "Dispositivo"
            mac = "1A:E2:36:56:7C:BD"
            text = ("[size=24][color=#ff6600]Nombre: [/color][/size]"
                    f"[size=18][color=#2db300]{nombre}[/color][/size]\n"
                    "[size=24][color=#ff6600]MAC:      [/color][/size]"
                    f"[size=18][color=#2db300]{mac}[/color][/size]\n"
                )
            modal = InfoModal()
            modal.title = obj.text
            modal.info = text
            modal.open()
    
    class InfoModal(ModalView):
        title = StringProperty("")
        info = StringProperty("")
    
    
    class LogoBox(AnchorLayout):
        pass
    
    
    class InterfazApp(App):
        title = 'Centro de control'
        def build(self):
            return Contenedor()
    
        def on_stop(self):
            self.root.scanning.clear()
    
    
    if __name__ == "__main__":
        InterfazApp().run()
    

    interfaz.kv

    <Contenedor>:
        orientation: 'vertical'
        spacing: 10
        padding: 10
    
        canvas:
            Color:
                rgb: 0, 0, 0
            Rectangle:
                size: self.size
                pos: self.pos
    
    <ButtonBox>:
        spacing: 10
        padding: 10
        size_hint: 1, None
        # Deshabilitación del tamaño relativo en X e Y
        # width: 650
        height: 50
        canvas:
            Color:
                # rgb: 0.78, 0.78, 0.78
                rgb: 0.65, 0.65, 0.65
            Rectangle:
                size: self.size
                pos: self.pos
    
    <InfoBox>:
        ips: ips
        id: info_root
        orientation: 'vertical'
        spacing: 10, 10
    
        canvas:
            Color:
                rgba: 1, 1, 1, 0.25
            Rectangle:
                size: self.size
                pos: self.pos
    
        ScrollView:
            size: self.size
            GridLayout:
                id: ips
                cols: 1
                size_hint_y: None
                height: self.minimum_height
                row_default_height: '50dp'
                row_force_default: True
    
    
    <LogoBox>:
        spacing: 2
        padding: 5
        size_hint: None, None
        width: 40
        height: 32
        canvas:
            Rectangle:
                source: 'UNIT_n.png'
                size: self.size
                pos: self.pos 
    
    
    <InfoModal>:
        auto_dismiss: False
        info: ""
        title: "Modal"
        size_hint: 0.8,0.8
    
        BoxLayout:
            orientation: "vertical"
            BoxLayout:
                size_hint_y: None
                height: "40 dp"
    
                canvas:
                    Color:
                        rgba: 0.30, 0.30, 0.30, 1
                    Rectangle:
                        pos: self.pos
                        size: self.size
    
                Label:
                    text: root.title
                    markup: True
    
                AnchorLayout:
                    size_hint_x: None
                    width: "30dp"
                    Button:
                        size_hint_y: None
                        height: "30dp"
                        font_size: "30dp"
                        text: "X"
                        background_color: 1, 0, 0, 1
                        color: 1, 1, 1, 1
                        on_press: root.dismiss()
    
            BoxLayout:
                orientation: "vertical"
                canvas:
                    Color:
                        rgba: 0.52, 0, 0.7, 1
                    Rectangle:
                        pos: self.pos
                        size: self.size
                Label:
                    text: root.info
                    markup: True
    
                BoxLayout:
                    size_hint_y: None
                    height: "50dp"
                    Button:
                        text: "Conectar"
                    Button:
                        text: "Desconectar"
    

introducir la descripción de la imagen aquí Obviamente hay muchas más formas de hacer lo que deseas.

FJSevilla
  • 55,603
  • 7
  • 35
  • 58
  • ¡Muchísimas gracias otra vez! Las dos soluciones me parecen estupendas, pero creo que va a ser más adecuada la primera, porque lo más seguro es que la aplicación acabe en una Raspberry con pantalla táctil, mientras tanto todo lo hago desde el PC. Igualmente, tener las dos soluciones es perfecto y también puede servirle a alguien que busque algo similar. De verdad, muchísimas gracias por dedicarme tu tiempo a solucionarme el problema. – Dratcher May 14 '19 at 10:56
  • Tengo una duda acerca de cómo actuar ahora. Tengo en la clase InterfazApp(App) definida la función on_start(self) que dentro tiene declaradas otras tantas funciones que son las necesarias para el servidor MQTT. Eso funciona como es debido, pero lo que no me termino de aclarar es cómo pasar el mensaje recibido por el servidor a la variable 'nombre' que se utiliza en la función cargar_informacion(self) dentro de class Dispositivo(DropDownItem). He hecho nombre = InterfazApp.on_start y eso me devuelve cierta información de la función, pero no lo que quiero. Si añado más se cuelga.Qué puedo hacer? – Dratcher May 18 '19 at 18:43