Tyty87
Tyty87

Reputation: 3

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

It seems a lot of people have trouble with the on_press argument with Kivy, but I haven't found answers to my issue... Here is what's happening: I'm getting started with my first app in python/kivy. I know python, but perhaps not enough concerning classes... I am able to create a button, with a on_press action that opens a popup. Now the goal is the following: I have a function affiche_grille, which displays a grid with lines on the screen. Inside each square, I create a button, with a text (a number). This works. I would like to bind an on_press event on these buttons : but now, the syntax does not work any more... Maybe is it because the button is created in a "with self.canvas" instruction, and the self.function is not appropriate any more ?

Here is the (complete after edit) code :

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()

Everything is interpreted, but the buttons inside the grid are not working... I've seen the functools.partial() tip, but it does not seem to be enough...

Does anyone have an idea of what is happening ? I am not very familiar with classes in python and I have certainly missed something. Thank you in advance, and sorry if the question is too basic.

Upvotes: 0

Views: 1455

Answers (1)

Mox
Mox

Reputation: 411

Well, you now know that you can't add a widget to canvas. Also, you shouldn't have a build method in your Ecran_Principal class. build() only belongs in the Sudoku() App class. Use __init__ instead.

I think if you try separating the visual stuff into kv language things will be easier. Below is an example utilizing spacing and padding with GridLayouts to 'draw' the game board. The buttons are hooked up with a simple callback.

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"

Upvotes: 2

Related Questions