Kivy - on_press 选项在 "with self.canvas" 中不起作用?

Kivy - on_press option does not work within a "with self.canvas"?

似乎很多人都对与 Kivy 的 on_press 争论感到困惑,但我还没有找到我的问题的答案... 这是正在发生的事情: 我在 python/kivy 开始使用我的第一个应用程序。我知道 python,但关于 类 可能还不够...我可以创建一个按钮,使用 on_press 操作打开一个弹出窗口。 现在的目标如下:我有一个函数 affiche_grille,它在屏幕上显示一个带有线条的网格。在每个方块内,我创建了一个带有文本(数字)的按钮。这行得通。我想在这些按钮上绑定一个 on_press 事件:但是现在,语法不再起作用了......也许是因为按钮是在 "with self.canvas" 指令中创建的,而 self.function 不再合适了?

这是(编辑后完成的)代码:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
from kivy.graphics import Color, Line
from kivy.core.window import Window
from kivy.core.text.text_layout import layout_text
from kivy.uix.floatlayout import FloatLayout
import numpy as np
from functools import partial # for on_press syntax

class Ecran_Principal(BoxLayout):
    def build(self):
        self.orientation='vertical'
        self.liste_txt = np.zeros([9,9], dtype=object) # will contain Label
        self.grille_affichee = np.zeros([9,9])
        self.lGrille() # layout for the grid
        self.lMenu() # layout for the menu


    def lGrille(self):
        LayGrille = GridLayout(cols=1,size_hint_y=0.8)
        # window dimensions, in pixels
        (L, H) = Window.size
        H = int(0.8*H) # because of the 20% menu
        if L > H: # landscape format - computer case
            self.ymin = int(0.25*H) + int(0.05*H)
            self.delta = int(0.1*H)
            self.xmin = int((L-9*self.delta)/2.)
        else: # portrait format - phone case
            self.xmin = int(0.05*L)
            self.delta = int(0.1*L)
            self.ymin = int(0.25*H) + int((H-9*self.delta)/2.)
        # end dimensions
        self.deltaxrel = self.delta/H
        self.deltayrel = self.delta/L
        # grid definition (without numbers)
        with self.canvas:
            Color(1, 1, 1) # white
            # automatic line traces
            for i in range(4):
                # big vertical lines
                ymax = self.ymin+9*self.delta
                xligne = self.xmin+i*3*self.delta
                Line(points=[xligne, self.ymin, xligne, ymax], width=2)
                # big horizontal lines
                xmax = self.xmin+9*self.delta
                yligne = self.ymin+i*3*self.delta
                Line(points=[self.xmin, yligne, xmax, yligne], width=2)
                # little intermediary lines
                for ipetit in range(3):
                    if i ==3:
                        break
                    xpetit = xligne + ipetit*self.delta
                    Line(points=[xpetit, self.ymin, xpetit, ymax], width=1)
                    ypetit = yligne + ipetit*self.delta
                    Line(points=[self.xmin, ypetit, xmax, ypetit], width=1)
                # end little lines
            # end for
            # grid display :
            self.affiche_grille()
        self.add_widget(LayGrille)
        # end with

    def affiche_grille(self):
        # I tried to remove this 'with' instruction and does not change anything
        with self.canvas:
            for i in range(9): # line
                for j in range(9): # colomn
                    valeur = self.grille_affichee[i,j]
                    val = "{0:.0f}".format(valeur)
                    layout = FloatLayout(size=(self.xmin + (j+0.5)*self.delta,
                                               self.ymin + (8.5-i)*self.delta),
                                         pos_hint=(None, None))
                    montexte = Button(text=val,
                                      size_hint=(self.deltaxrel,
                                                 self.deltayrel),
                                      pos=(self.xmin + (j+0.5)*self.delta,
                                           self.ymin + (8.5-i)*self.delta),
                                      background_color = (0,0.2,0,1),
                                      background_normal = '',
                                      on_press=partial(self.choisir_valeur, i, j)
                                      )
                    self.liste_txt[i, j] = montexte
                    # THE BUTTONS AND THE TEXT ARE DISPLAYED,
                    # BUT NOTHING HAPPENS WHEN YOU PRESS THE BUTTONS
                    layout.add_widget(self.liste_txt[i, j])
                # end j
            # end i
        # end with

    def choisir_valeur(self, i, j):
        print("Hello !") # NEVER DISPLAYED :(
        #champ = TextInput(text=str(self.grille_affichee[i, j]),
        #                  font_size=30,
        #                  focus=True,
        #                  multiline=False)
        champ = Button(text=str(self.grille_affichee[i, j]))
        popup = Popup(title='Value in line {} - colomn {}'.format(i, j),
                      content=champ,
                      size_hint=(0.5,0.5))
        champ.bind(on_press=popup.dismiss)
        popup.open()

    def lMenu(self):
        LayMenu = GridLayout(cols=2, rows=2, size_hint_y=0.2)
        # Bouton Résoudre
        self.BoutonResoudre=Button(text='Resoudre',size_hint=(0.5,0.1),pos_hint={'left': 0.},background_color=[0.9,0.9,0.9,1])
        self.BoutonResoudre.bind(on_press=self.resoudre)
        LayMenu.add_widget(self.BoutonResoudre)
        # Bouton Remplir
        self.BoutonScanner=Button(text='Scanner',size_hint=(0.5,0.1),pos_hint={'left': 0.5},background_color=[0.9,0.9,0.9,1])
        self.BoutonScanner.bind(on_press=self.scanner)
        LayMenu.add_widget(self.BoutonScanner)
        # Bouton Indice
        self.BoutonIndice=Button(text='Indice',size_hint=(0.5,0.1),pos_hint={'bottom': 0.},background_color=[0.9,0.9,0.9,1])
        self.BoutonIndice.bind(on_press=self.indice)
        LayMenu.add_widget(self.BoutonIndice)
        # Bouton Quitter
        self.BoutonQuitter=Button(text='Quitter',size_hint=(0.5,0.1),background_color=[0.9,0.9,0.9,1])
        self.BoutonQuitter.bind(on_press=self.quitter)
        LayMenu.add_widget(self.BoutonQuitter)

        self.add_widget(LayMenu)

    def resoudre(self, instance):
        content = Button(text='Resolution du sudoku', font_size=20)
        popup = Popup(title='RESOLUTION',content=content, size_hint=(0.5,0.5))
        content.bind(on_press=popup.dismiss)
        popup.open()

    def scanner(self, instance):
        content = Button(text='Remplissage auto par photo', font_size=20)
        popup = Popup(title='SCAN PHOTO',content=content, size_hint=(0.5,0.5))
        content.bind(on_press=popup.dismiss)
        popup.open()

    def indice(self, instance):
        content = Button(text='Affichage d\'un indice', font_size=20)
        popup = Popup(title='INDICE',content=content, size_hint=(0.5,0.5))
        content.bind(on_press=popup.dismiss)
        popup.open()

    def quitter(self, instance):
        content = Button(text='Au revoir !', font_size=20)
        popup = Popup(title='QUITTER',content=content, size_hint=(0.5,0.5))
        content.bind(on_press=exit())
        popup.open()


class Sudoku(App):
    def build(self):
        ecran=Ecran_Principal()
        ecran.build()
        return ecran


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

一切都已解释,但网格内的按钮不起作用... 我看过 functools.partial() 提示,但似乎还不够...

有人知道发生了什么事吗?我对 python 中的 类 不是很熟悉,我肯定错过了一些东西。 在此先感谢您,如果问题太基础,我们深表歉意。

好吧,您现在知道无法将小部件添加到 canvas。此外,您的 Ecran_Principal class 中不应包含构建方法。 build() 仅属于 Sudoku() 应用 class。请改用 __init__

我认为如果您尝试将视觉内容分离为 kv 语言,事情会更容易。下面是一个使用 GridLayouts 的间距和填充到 'draw' 游戏板的示例。这些按钮与一个简单的回调相连。

btngrid.py

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.button import Button


class SmallGrid(GridLayout):
    pass


class BigGrid(GridLayout):
    pass


class GameBoard(AnchorLayout):
    # A nice layout to use to keep things centered. Only one widget can be added to this.

    def __init__(self, *args, **kwargs):
        super(GameBoard, self).__init__(*args, **kwargs)

        big = BigGrid()

        for i in range(9):
            small = SmallGrid()
            for j in range(9):
                small.add_widget(Button(text="{}, {}".format(i, j), on_release=self.callback))

            big.add_widget(small)

        self.add_widget(big)

    def callback(self, button):
        print button.text


class RootWidget(BoxLayout):

    def __init__(self, *args, **kwargs):
        super(RootWidget, self).__init__(*args, **kwargs)
        self.orientation = 'vertical'
        self.add_widget(GameBoard())
        bottom_btns_container = GridLayout(cols=2, size_hint=(1, .2))

        for i in range(4):
            # Just for show, they don't do anything
            bottom_btns_container.add_widget(Button(text="Button {}".format(i)))

        self.add_widget(bottom_btns_container)


class BtnGridApp(App):

    def build(self):
        return RootWidget()

btngird.kv

<BigGrid>:
    cols: 3
    size_hint: (None, .8)  # scales
    width: self.height  # scales
    spacing: 5  # Width of lines
    padding: 5  # perimeter border
    # This draws a background for the whole grid.
    # When used with spacing and padding, part of the background will show.
    # Same with SmallGrid below
    canvas.before:
        Color:
            rgba: [.9, .9, .9, 1]
        Rectangle:
            pos: self.pos
            size: self.size    

<SmallGrid>:
    cols: 3
    size_hint: (None, .8)  # scales
    width: self.height  # scales
    spacing: .25
    canvas.before:
        Color:
            rgba: [.6, .6, .6, 1]  # White lines
        Rectangle:
            pos: self.pos
            size: self.size    

<GameBoard>:
    anchor_x: "center"
    anchor_y: "center"