【问题标题】:Kivy dynamically adding buttons to gridlayout gives errorKivy 动态地将按钮添加到 gridlayout 会出错
【发布时间】:2019-04-20 17:52:05
【问题描述】:

我正在尝试动态填充 ScrollView 内的 Gridlayout。但是,当我尝试添加 Builder.load_string 生成的按钮时,出现以下错误: kivy.uix.widget.WidgetException:无法添加,它已经有一个父级 我不确定我做错了什么。 我可以通过构造函数在python中生成每个按钮,但我宁愿不重写按钮生成。

main.py

# IMSA Computational Science and Data Science Club: Brummet Client
# Written for ssh into IMSA SLURM cluster
# Written by: Arthur Lu, Jacob Levine
# Use at one's own risk

__author__ = ("Arthur Lu <alu1@imsa.edu>", "Jacob Levine <jlevine@imsa.edu>")

from kivy.config import Config
#Config.set('graphics', 'resizable', False)

from kivy.app import App

from kivy.properties import StringProperty, ObjectProperty

from kivy.core.window import Window

from kivy.uix.screenmanager import ScreenManager, Screen, SlideTransition, NoTransition
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.label import Label

from kivy.lang.builder import Builder

from kivy.clock import Clock

import csv
import paramiko
import time
#import os

def load_csv(filepath):
    with open(filepath, newline='') as csvfile:
        file_array = list(csv.reader(csvfile))
        csvfile.close() 
    return file_array

class Client(Screen):

    def on_pre_enter(self, *args):
        Window.size = (1280, 720)
        Window.top = 100
        Window.left = 100

    def client(self, ssh, sftp):

        self.ssh = ssh
        self.sftp = sftp

        self.sftp.chdir('brummet_projects')

        Clock.schedule_interval(self.auto, 1)

    def auto(self, dt):

        projects = self.sftp.listdir('.')

        for file in projects:

            if file[0] == ".":

                projects.remove(file)

        list_view = self.ids.list_files

        """
        Button:
            background_color: 0,0,0,0
            Image:
                source:'data\customui\client_file_bar.png'
                x: self.parent.x
                y: self.parent.y
                width: self.parent.width
                height: self.parent.height
                allow_stretch: True
                keep_ratio: False

            Image:
                source:'data\customui\python.png'
                y: self.parent.y + 10
                x: - self.parent.width/2 + 25
                width: self.parent.width - 20
                height: self.parent.height - 20

            Label:
                size_hint:(0.9, 1)
                text: "hello there"
                y: self.parent.y
                x: self.parent.x + self.parent.width*0.05
                width: self.parent.width
                height: self.parent.height
                text_size: self.size
                font_size: self.height - 30
                halign: 'left'
                valign: 'middle'

            Label:
                size_hint:(0.9, 1)
                text: "no u"
                y: self.parent.y
                x: self.parent.x + self.parent.width*0.7
                width: self.parent.width
                height: self.parent.height
                text_size: self.size
                font_size: self.height - 30
                halign: 'left'
                valign: 'middle'

            Label:
                size_hint:(0.9, 1)
                text: "dong big dumb"
                y: self.parent.y
                x: self.parent.x + self.parent.width*0.8
                width: self.parent.width
                height: self.parent.height
                text_size: self.size
                font_size: self.height - 30
                halign: 'left'
                valign: 'middle'
        """

        template = Builder.load_string("""
Button:
    background_color: 0,0,0,0
    Image:
        source:'data\customui\client_file_bar.png'
        x: self.parent.x
        y: self.parent.y
        width: self.parent.width
        height: self.parent.height
        allow_stretch: True
        keep_ratio: False

    Image:
        source:'data\customui\python.png'
        y: self.parent.y + 10
        x: - self.parent.width/2 + 25
        width: self.parent.width - 20
        height: self.parent.height - 20

    Label:
        size_hint:(0.9, 1)
        text: "hello there"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.05
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

    Label:
        size_hint:(0.9, 1)
        text: "no u"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.7
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

    Label:
        size_hint:(0.9, 1)
        text: "dong big dumb"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.8
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'
            """)

        print(projects)

        for file in projects:
            list_view.add_widget(template)

class Connect(Screen):
    def on_pre_enter(self, *args):
        Window.size = (600, 300)

    def routine(self, host, port, username, password):

        ssh = None
        sftp = None

        #print(username, password)
        self.ids.status.text = "connecting"

        try:
            self.ids.status.text = "attempting to connect to " + host + ":" + str(port)
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(host, port, username, password)

            transport = paramiko.Transport((host, port))
            transport.connect(username = username, password = password)
            sftp = paramiko.SFTPClient.from_transport(transport)

            self.ids.status.text = "connected to " + host + ":" + str(port)

            Clock.schedule_once(self.continue_to_client, 0.1)
            self.manager.get_screen('client').client(ssh, sftp)


        except Exception as e:
            if sftp is not None:
                sftp.close()
            if ssh is not None:
                ssh.close()

            self.ids.status.text = "connection failed: " + str(e)
            Clock.schedule_once(self.return_to_login, 4)
            #self.manager.current = 'login'

    def return_to_login(self, *args):
        self.manager.transition = SlideTransition(direction = "right")
        self.manager.current = 'login'
            #time.sleep(5)

    def continue_to_client(self, *args):

        self.manager.transition = NoTransition()
        self.manager.current = 'client'

class Login(Screen):

    def on_pre_enter(self, *args):
        Window.size = (600, 300)

    def do_login(self, loginText, passwordText, hostText, portText):
        app = App.get_running_app()

        if hostText == "":
            hostText = "titanrobotics.ddns.net"
        if portText == "":
            portText = "60022"

        host = hostText
        port = int(portText)

        username = loginText
        password = passwordText

        self.manager.transition = SlideTransition(direction = "left")
        self.manager.current = "connect"

        self.manager.get_screen('connect').routine(host, port, username, password)

    def resetForm(self):
        self.ids['login'].text = ""
        self.ids['password'].text = ""


manager = ScreenManager()

class BrummetApp(App):

    username = StringProperty(None)
    password = StringProperty(None)

    title = 'Brummet Client v ' + load_csv("data/meta")[0][1]

    def check_resize(self, instance, x, y):
        # resize X
        #screenName = manager.current
        #print(screenName)
        if manager.current != "client":

            target_x = 600
            target_y = 300


            if x >  target_x:
                Window.size = (target_x, Window.size[1])

            if y > target_y:
                Window.size = (Window.size[0], target_y)

            if x <  target_x:
                Window.size = (target_x, Window.size[1])

            if y < target_y:
                Window.size = (Window.size[0], target_y)

        if manager.current == "client":

            target_x = 1280
            target_y = 720

            if x <  target_x:
                Window.size = (target_x, Window.size[1])

            if y < target_y:
                Window.size = (Window.size[0], target_y)

    def build(self):

        manager.add_widget(Login(name = 'login'))
        manager.add_widget(Connect(name = 'connect'))
        manager.add_widget(Client(name = 'client'))

        Window.bind(on_resize=self.check_resize)

        return manager

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

brummet.kv

<Login>:
    BoxLayout:
        id: login_layout
        orientation: 'vertical'
        padding: [10,10,10,10]
        spacing: 10

        BoxLayout:
            spacing: 10
            orientation:'vertical'

            Label:
                id: title
                text: 'Brummet Client'
                halign: 'center'
                valign: 'middle'
                font_size: 24

            Label:
                text: 'Please log in with IMSA SLURM credentials'
                halign: 'center'
                valign: 'middle'
                font_size: 20

        BoxLayout:
            orientation: 'horizontal'
            Label:
                size_hint: (0.15, 1)
                text: 'Username'
                font_size: 18
                halign: 'left'

            TextInput:
                size_hint: (0.7, 1)
                id: username
                multiline: False
                font_size: 18
                write_tab: False

        BoxLayout:
            orientation: 'horizontal'
            Label:
                size_hint: (0.15, 1)
                text: 'Password'
                halign: 'left'
                font_size: 18

            TextInput:
                size_hint: (0.7, 1)
                id: password
                multiline: False
                password: True
                font_size: 18
                write_tab: False

        BoxLayout:
            orientation: 'horizontal'
            Label:
                size_hint: (0.15, 1)
                text: 'Host'
                halign: 'left'
                font_size: 18

            TextInput:
                size_hint: (0.7, 1)
                hint_text: 'slurm.imsa.edu'
                id: host
                multiline: False
                font_size: 18
                write_tab: False

        BoxLayout:
            orientation: 'horizontal'
            Label:
                size_hint: (0.15, 1)
                text: 'Port'
                halign: 'left'
                font_size: 18

            TextInput:
                size_hint: (0.7, 1)
                input_type: 'number'
                input_filter: 'int'
                hint_text: '22'
                id: port
                multiline: False
                font_size: 18
                write_tab: False

        Button:
            text: 'Log In'
            font_size: 24
            id: submit
            on_press:
                root.do_login(username.text, password.text, host.text, port.text)

<Connect>:
    BoxLayout:
        orientation: 'vertical'
        padding: [0,100,0,100]
        spacing: 0

        Label:
            text:'Logging In'
            font_size: 24
            halign: 'center'
            valign: 'middle'

        Label:
            id: status
            test:''
            font_size: 12
            halign: 'center'
            valign: 'middle'
            text_size: self.size
            size_hint: 1,1
            shorten: True

<Client>:
    BoxLayout:
        orientation: 'horizontal'
        padding: [5, 5, 5, 5]
        spacing: 5
        GridLayout:
            size_hint: (0.2, 1)
            row_default_height: self.height / 10
            row_force_default: True
            cols: 1
            Button:
            Button:
        BoxLayout:
            orientation: 'vertical'
            Button:
                x: 0
                y: 0
                size: (self.parent.width, 50)
                size_hint:(None, None)
                background_color: 0,0,0,0

                Label:
                    size_hint:(0.9, 1)
                    text: "My Projects"
                    y: self.parent.y
                    x: self.parent.x + self.parent.width*0
                    width: self.parent.width
                    height: self.parent.height
                    text_size: self.size
                    font_size: self.height - 10
                    halign: 'left'
                    valign: 'middle'

            Button:
                x: 0
                y: 0
                size: (self.parent.width, 50)
                size_hint:(None, None)
                background_color: 0,0,0,0

                Label:
                    size_hint:(0.9, 1)
                    text: "Project Name"
                    y: self.parent.y
                    x: self.parent.x + self.parent.width*0
                    width: self.parent.width
                    height: self.parent.height
                    text_size: self.size
                    font_size: self.height - 30
                    halign: 'left'
                    valign: 'middle'

                Label:
                    size_hint:(0.9, 1)
                    text: "Type"
                    y: self.parent.y
                    x: self.parent.x + self.parent.width*0.7
                    width: self.parent.width
                    height: self.parent.height
                    text_size: self.size
                    font_size: self.height - 30
                    halign: 'left'
                    valign: 'middle'

                Label:
                    size_hint:(0.9, 1)
                    text: "Date Modified"
                    y: self.parent.y
                    x: self.parent.x + self.parent.width*0.8
                    width: self.parent.width
                    height: self.parent.height
                    text_size: self.size
                    font_size: self.height - 30
                    halign: 'left'
                    valign: 'middle'

            ScrollView:
                GridLayout:
                    id:list_files
                    orientation: "vertical"
                    size_hint_y: None
                    height: self.minimum_height
                    row_default_height:50
                    cols:1

                    Button:
                        background_color: 0,0,0,0
                        Image:
                            source:'data\customui\client_file_bar.png'
                            x: self.parent.x
                            y: self.parent.y
                            width: self.parent.width
                            height: self.parent.height
                            allow_stretch: True
                            keep_ratio: False

                        Image:
                            source:'data\customui\python.png'
                            y: self.parent.y + 10
                            x: - self.parent.width/2 + 25
                            width: self.parent.width - 20
                            height: self.parent.height - 20

                        Label:
                            size_hint:(0.9, 1)
                            text: "hello there"
                            y: self.parent.y
                            x: self.parent.x + self.parent.width*0.05
                            width: self.parent.width
                            height: self.parent.height
                            text_size: self.size
                            font_size: self.height - 30
                            halign: 'left'
                            valign: 'middle'

                        Label:
                            size_hint:(0.9, 1)
                            text: "no u"
                            y: self.parent.y
                            x: self.parent.x + self.parent.width*0.7
                            width: self.parent.width
                            height: self.parent.height
                            text_size: self.size
                            font_size: self.height - 30
                            halign: 'left'
                            valign: 'middle'

                        Label:
                            size_hint:(0.9, 1)
                            text: "dong big dumb"
                            y: self.parent.y
                            x: self.parent.x + self.parent.width*0.8
                            width: self.parent.width
                            height: self.parent.height
                            text_size: self.size
                            font_size: self.height - 30
                            halign: 'left'
                            valign: 'middle'

错误发生在以下代码中:

template = Builder.load_string("""
Button:
    background_color: 0,0,0,0
    Image:
        source:'data\customui\client_file_bar.png'
        x: self.parent.x
        y: self.parent.y
        width: self.parent.width
        height: self.parent.height
        allow_stretch: True
        keep_ratio: False

    Image:
        source:'data\customui\python.png'
        y: self.parent.y + 10
        x: - self.parent.width/2 + 25
        width: self.parent.width - 20
        height: self.parent.height - 20

    Label:
        size_hint:(0.9, 1)
        text: "hello there"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.05
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

    Label:
        size_hint:(0.9, 1)
        text: "no u"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.7
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

    Label:
        size_hint:(0.9, 1)
        text: "dong big dumb"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.8
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'
            """)

        print(projects)

        for file in projects:
            list_view.add_widget(template)      

上面的代码应该在 ScrollView 中生成一些按钮,但是会产生以下错误: kivy.uix.widget.WidgetException: 无法添加,它已经有一个父级

【问题讨论】:

  • 请发布一个 minimal 可运行示例,使用尽可能少的代码来演示问题。
  • 依赖的东西太多了,但是相关的代码就是Builder.load_string所在的地方。在 Client 类下。

标签: python python-3.x kivy


【解决方案1】:

根本原因 - 添加相同的实例,模板

for loop 中,第一个add_widget() template 工作正常。但是template on-wards 的同一实例的第二个add_widget() 正在抛出错误。因为template 已经有一个父级了。

解决方案 - 添加新实例,模板

这个问题有两种解决方案。

方法 1 - 创建一个新的 kv 文件,template.kv

创建一个新的 kv 文件 template.kv 并在 for 循环中添加该对象的一个​​新实例。

模板.kv

Button:
    background_color: 0,0,0,0
    Image:
        source: 'data\customui\client_file_bar.png'
        x: self.parent.x
        y: self.parent.y
        width: self.parent.width
        height: self.parent.height
        allow_stretch: True
        keep_ratio: False

    Image:
        source: 'data\customui\python.png'
        y: self.parent.y + 10
        x: - self.parent.width/2 + 25
        width: self.parent.width - 20
        height: self.parent.height - 20

    Label:
        size_hint:(0.9, 1)
        text: "hello there"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.05
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

    Label:
        size_hint:(0.9, 1)
        text: "no u"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.7
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

    Label:
        size_hint:(0.9, 1)
        text: "dong big dumb"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.8
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

main.py

class Client(Screen):
    ...
    def auto(self, dt):

        projects = self.sftp.listdir('.')

        for file in projects:

            if file[0] == ".":
                projects.remove(file)

        list_view = self.ids.list_files

        print(projects)

        for file in projects:
            list_view.add_widget(Builder.load_file('template.kv'))

方法 2 - 添加新类

在kv文件中创建一个类规则,并在for循环中添加这个对象的一个​​新实例。

片段 - kv 文件

<FileTemplate>:
    background_color: 0,0,0,0
    Image:
        source: 'data\customui\client_file_bar.png'
        x: self.parent.x
        y: self.parent.y
        width: self.parent.width
        height: self.parent.height
        allow_stretch: True
        keep_ratio: False

    Image:
        source: 'data\customui\python.png'
        y: self.parent.y + 10
        x: - self.parent.width/2 + 25
        width: self.parent.width - 20
        height: self.parent.height - 20

    Label:
        size_hint:(0.9, 1)
        text: "hello there"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.05
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

    Label:
        size_hint:(0.9, 1)
        text: "no u"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.7
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'

    Label:
        size_hint:(0.9, 1)
        text: "dong big dumb"
        y: self.parent.y
        x: self.parent.x + self.parent.width*0.8
        width: self.parent.width
        height: self.parent.height
        text_size: self.size
        font_size: self.height - 30
        halign: 'left'
        valign: 'middle'
...

<Client>:
    ...
            ScrollView:
                GridLayout:
                    id:list_files
                    orientation: "vertical"
                    size_hint_y: None
                    height: self.minimum_height
                    row_default_height:50
                    cols:1

片段 - main.py

​​>
class FileTemplate(Button):
    pass


class Client(Screen):
    ...
    def auto(self, dt):

        projects = self.sftp.listdir('.')

        for file in projects:

            if file[0] == ".":
                projects.remove(file)

        list_view = self.ids.list_files

        print(projects)

        for file in projects:
            list_view.add_widget(FileTemplate())

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-24
    • 1970-01-01
    • 1970-01-01
    • 2016-09-07
    • 2012-02-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多