【问题标题】:Why can't I access the Screen.ids?为什么我无法访问 Screen.ids?
【发布时间】:2015-01-11 00:49:04
【问题描述】:

更新:

Ryan P 的回答提供了解决方案。但是,我采用了该解决方案 并对其进行了一些更改,丢弃了所有不存在的数据 正确初始化为 RootWidget 的 on_enter 方法 屏幕。这效果很好。

直到今天,我的 RootWidget 类都是 Widget 的子类,我可以毫无问题地访问它的 id 来获取“grid”的值。然而,我只是改变了 它是Screen的子类,现在它说由于某种原因ID是空的...... Screen确实有一个ID和所有这些,但由于某种原因,它没有注册那个 我在 kv 文件中为 id ''grid'' 分配了一个 GridLayout。谁能告诉我为什么?

回溯:

[INFO   ] [Logger      ] Record log in /home/yerman/.kivy/logs/kivy_14-11-13_201.txt
[INFO   ] Kivy v1.9.0-dev
[INFO   ] [Python      ] v2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2]
[INFO   ] [Factory     ] 172 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_pygame, img_pil, img_gif (img_sdl2, img_ffpyplayer ignored)
[INFO   ] [Window      ] Provider: pygame(['window_egl_rpi'] ignored)
[WARNING] [WinPygame   ] Video: failed (multisamples=2)
[WARNING] [WinPygame   ] trying without antialiasing
[INFO   ] [GL          ] OpenGL version <2.1 Mesa 10.1.3>
[INFO   ] [GL          ] OpenGL vendor <Intel Open Source Technology Center>
[INFO   ] [GL          ] OpenGL renderer <Mesa DRI Intel(R) Ironlake Mobile >
[INFO   ] [GL          ] OpenGL parsed version: 2, 1
[INFO   ] [GL          ] Shading version <1.20>
[INFO   ] [GL          ] Texture max size <8192>
[INFO   ] [GL          ] Texture max units <16>
[INFO   ] [Window      ] virtual keyboard not allowed, single mode, not docked
[INFO   ] [Text        ] Provider: pygame(['text_sdl2'] ignored)
{}                     #<<< note the emtpy ids I printed out
 Traceback (most recent call last):
   File "main.py", line 169, in <module>
     MineSweeperApp().run()
   File "/usr/lib/python2.7/dist-packages/kivy/app.py", line 799, in run
     root = self.build()
   File "main.py", line 163, in build
     return Manager()
   File "/usr/lib/python2.7/dist-packages/kivy/uix/screenmanager.py", line 844, in __init__
     super(ScreenManager, self).__init__(**kwargs)
   File "/usr/lib/python2.7/dist-packages/kivy/uix/floatlayout.py", line 66, in __init__
     super(FloatLayout, self).__init__(**kwargs)
   File "/usr/lib/python2.7/dist-packages/kivy/uix/layout.py", line 66, in __init__
     super(Layout, self).__init__(**kwargs)
   File "/usr/lib/python2.7/dist-packages/kivy/uix/widget.py", line 269, in __init__
     Builder.apply(self)
   File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 1837, in apply
     self._apply_rule(widget, rule, rule)
   File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 1942, in _apply_rule
     child = cls(__no_builder=True)
   File "main.py", line 43, in __init__
     self.grid = self.ids["grid"]
 KeyError: 'grid'

kv 文件:

#:kivy 1.8.0

<RootWidget>:
    GridLayout:
        id: grid
        size: root.size
        cols: root.sides

<Blank>:
    background_color: 1, 1, 1, 1
    background_disabled_down: "kivy_white_bg.png"
    on_press: self.parent.parent.sweep(self)

<Mine>:
    background_color: 1, 1, 1, 1
    background_disabled_down: "kivy_white_bg.png"
    on_press: self.parent.parent.sweep(self)

<TryAgain>:
    anchor_x: 'center'
    anchor_y: 'center'
    BoxLayout:
        size: root.size
        orientation: 'vertical'
        padding_bottom: '20dp'

        Label:
            font_size: '20dp'
            text: root.text

        BoxLayout:
            size_hint: 1, .3
            spacing: 10
            padding: 10
            Button:
                size_hint: .4, 1
                font_size: '20dp'
                text: "yes"
                on_press: app.stop(); app.run()
            Button:
                size_hint: .4, 1
                font_size: '20dp'
                text: "no"
                on_press: root.quit()

<Menu>:
    GridLayout:
        rows: 2
        Button:
            text: "8x8"
            on_press: root.manager.current = 'game_screen'
        Button:
            text: "16x16"
            on_press: root.manager.current = 'game_screen'
        Button:
            text: "30x16"
            on_press: root.manager.current = 'game_screen'
        Button:
            text: "custom"
            on_press: root.manager.current = 'game_screen'

<Manager>:
    id: _manager
    menu: menu
    game: game
    current: menu_screen

    Menu:
        id: menu
        manager: _manager
        name: 'menu_screen'

    RootWidget:
        id: game
        manager: _manager
        name: 'game_screen'

ma​​in.py:

#!/usr/bin/env python

from random import sample
import sys
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.properties import NumericProperty, ListProperty, StringProperty, ObjectProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.modalview import ModalView
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock

class Blank(Button):

    index = ListProperty([0, 0])
    count = NumericProperty(0)

    def __init__(self, **kwargs):
        super(Blank, self).__init__(**kwargs)


class Mine(Button):

    index = ListProperty([0, 0])
    count = NumericProperty(0) # not really necessary

    def __init__(self, **kwargs):
        super(Mine, self).__init__(**kwargs)


class RootWidget(Screen):

    sides = NumericProperty(10)
    mine_count = NumericProperty(20)

    def __init__(self, **kwargs):
        super(RootWidget, self).__init__(**kwargs)
        self.grid = self.ids["grid"]

        # generate random mine indices
        mines = sample(xrange(self.sides**2), self.mine_count)

        x, y = -1, 0
        for i in xrange(self.sides**2):
            if x == self.sides - 1:
                x = 0
                y += 1
            else:
                x += 1

            if i not in mines:
                b = Blank(index=[x, y])

            else:
                b = Mine(index=[x, y])
            self.grid.add_widget(b)

        # record mine, blank and safe blank indices
        self.all_btns = [c.index for c in self.grid.children]
        self.mines = [c.index for c in self.grid.children if isinstance(c, Mine)]
        self.blanks = [c.index for c in self.grid.children if isinstance(c, Blank)]
        # a safe blank has no adjacent mines
        self.safe_blanks = [c.index for c in self.grid.children if self.is_safe(c)]

        # give each btn an 'adjacent mines count'
        for x, y in self.all_btns:
            btn = self.get_child_by_index([x, y])
            for index in self.field(x, y):
                if index in self.mines:
                    btn.count += 1

    def field(self, x, y):
        """ the minefield surrounding a btn """
        field = [[x-1, y], [x+1, y], [x, y+1], [x, y-1],
            [x+1, y+1], [x-1, y-1], [x+1, y-1], [x-1, y+1]]

        get = self.get_child_by_index        
        return [i for i in field if i in self.all_btns and get(i).disabled == False]

    def sweep(self, instance):
        instance.disabled = True

        if instance.index in self.mines: 
            print "Boom!"              # It's a mine! You lose
            instance.text = "Boom!"
            self.game_over() 

        pressed = sum(1 for c in self.grid.children if c.disabled == True)
        print pressed
        if self.sides**2 - pressed == self.mine_count:
            self.game_over(win=True)

        if instance.count > 0:
            instance.text = str(instance.count)
            instance.disabled = True
            return
        else:
            x, y = instance.index

            for index in self.field(x, y):
                if index not in self.mines:
                    blank = self.get_child_by_index(index)
                    blank.disable = True
                    if blank.count > 0:
                        blank.text = str(blank.count)
                    self.sweep(blank)

    def is_safe(self, btn):
        x, y = btn.index
        for index in self.field(x, y):
            if index in self.mines:
                return False
        return True

    def get_child_by_index(self, index):
        for child in self.grid.children:
            if child.index == index:
                return child

    def game_over(self, q=False, win=False):
        if q == True:
            sys.exit()

        if win == True:
            result = "Win"
        elif win == False:
            result = "lost"

        view = TryAgain(
            size_hint = (None, None),
            width = self.width/2,
            height = self.height/2,
            center = self.center,
            text = "You {}! Try Again?".format(result))
        view.open()

class TryAgain(ModalView):

    text = StringProperty('')

    def quit(self):
        sys.exit()

class Menu(Screen):
    pass


class Manager(ScreenManager):

    menu = ObjectProperty(None)
    game = ObjectProperty(None)


class MineSweeperApp(App):

    def build(self):
        return Manager()



if __name__ == "__main__":

    MineSweeperApp().run()

【问题讨论】:

  • 从日志看来您使用的是 v1.9.0_dev。在 v1.8.0 中向屏幕添加 id 没有问题。您是否尝试过与该版本相同的代码?
  • 我没有。我不确定如何降级
  • 您能否发布更多代码,使其可执行,我将在我的机器上尝试一下?
  • 我已经包含了完整的程序,注意在 kv 文件中使用的一个纹理.. 很多代码仍然是部分实验,部分只是没有完成
  • 也因为我开始遇到这个错误,我从来没有测试过整个屏幕的实现,所以要小心。

标签: python python-2.7 kivy


【解决方案1】:

在原始Widget 完成实例化之前,不会应用kv 规则。在这种情况下,您的 Manager 小部件是初始小部件 - 它反过来创建其他小部件,包括 RootWidget。这意味着在您的RootWidget.__init__ 中,ids 尚未填充!它们将在 Manager 完成实例化后立即出现 - 所以最好的方法是延迟其余的初始化,如下所示:

class RootWidget(Screen):
    def __init__(self, **kwargs):
        super(RootWidget, self).__init__(**kwargs)
        Clock.schedule_once(self._finish_init)

    def _finish_init(self, dt):
        self.grid = self.ids.grid
        # etc

【讨论】:

  • 成功了!而且我觉得我应该看到它,事情发生的顺序。谢谢!不过我不得不说,这个问题让我很恼火,我不喜欢它必须以一种感觉像是修补过的方式来完成。
  • 这似乎是我们绝对可以做出的改进。随时在github.com/kivy/kivy/issues 上提交功能请求或找到解决方案并提出拉取请求:)
【解决方案2】:

Since Kivy 1.11.0,你可以使用on_kv_post事件:

在与小部件关联的所有 kv 规则以及任何这些规则中的所有其他小部件都应用了所有 kv 规则后触发。 base_widget 是最基础的小部件,其实例化触发了 kv 规则(即从 Python 实例化的小部件,例如 MyWidget())。

class RootWidget(Screen):
    def on_kv_post(self, base_widget):
        # self.ids will be populated here

【讨论】:

  • 这个可以代替init?
  • 取决于你想用它做什么。 __init__()on_kv_post() 在不同的时间以不同的参数触发;它们有不同的用途。
猜你喜欢
  • 2014-04-06
  • 1970-01-01
  • 1970-01-01
  • 2021-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多