Reputation: 3
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
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