kivy button text="" logic error - python

I am new to python and kivy, I am trying to learn python bymaking a small minesweeper game, however, I feel the logic in below code is correct, but somehow seems not to work: complete file is as follows:
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
import random
class spot(Button):
'''
classdocs
'''
def __init__(self, **kwargs):
'''
Constructor
'''
super(spot,self).__init__(**kwargs)
self.ismine=False
self.text="X"
if 'id' in kwargs:
self.id=kwargs['id']
#else:
# self.text="X"
class minesholder(GridLayout):
def __init__(self,**kwargs):
super(minesholder,self).__init__(**kwargs)
class game(BoxLayout):
spots={}
mines=[]
def __init__(self,**kwargs):
super(game,self).__init__(**kwargs)
self.m=minesholder(rows=5, cols=5)
self.add_widget(self.m)
self.attachtogrid()
def attachtogrid(self):
self.m.clear_widgets()
self.spots.clear()
for r in range(0,5):
for c in range(0,5):
idd=str(r)+","+str(c)
self.spots[idd]=idd
s = spot(id=idd)
self.m.add_widget(s)
s.bind(on_press=self.spottouched)
print(idd)
self.createmines()
def createmines(self):
self.mines.clear()
count=0
while (count <= 10):
c=str(random.randint(0,4))+','+str(random.randint(0,4))
print(c)
if self.mines.count(c)==0:
self.mines.append(c)
count+=1
def spottouched(self,spotted):
#if self.mines.count(str(spotted.id))==1:
# spotted.text="BOMB"
#else: spotted.text=""
for k in self.mines:
if k==spotted.id: spotted.text="BOMB"
else:
spotted.text=""
The issue is the last 4 lines, when I remove the "spotted.text=""", the code is working perfect, but when I keep the text="" the code is not working any more, despite of having 11 bombs only 1 is getting detected, with out the text="", all the bombs are getting detected correctly (text="BOMB" works).

Each time spottouched() is called, you loop through each mine and set the text accordingly. But let's say you have two bombs - let's call the bombs ['bomb-a', 'bomb-b'].
Now you touch the button with the id 'bomb-a'. spottouched() loops through the mines. The first mine in the list is 'bomb-a' - so it sets the text to "BOMB". Then it loops - the second mine in the list is 'bomb-b', so the text is set back to "". So the only mine which will show the "BOMB" text is the very last mine in the list.
Try something like this instead:
def spottouched(self, spotted):
spotted.text = "BOMB" if spotted.id in self.mines else ""

Related

Tkinter automatically update Label after Entry in Python

I made an tkinter window for some kind of calculation from the data entered. I simplified my code below to illustrate my question. Currently, the result will show up once I click search. However, I want the result to show up automatically once text are entered in the entry box without the "search" button. I tried "after" using window.after(100, self.searchbarcode) but it did not work for me. Appreciate your inputs. Thank you!
from tkinter import *
import pandas as pd
import tkinter as tk
import os.path
import numpy as np
class searchloc:
def __init__(self):
window=tk.Tk()
window.geometry("800x300")
window.title("Search Location")
Label(window, text="Scan",font="Helvetica 24").grid(row=1,column=1,sticky=W)
self.barcode=StringVar()
self.outcomes=StringVar()
self.text1=tk.Entry(window,textvariable=self.barcode,font="Helvetica 36")
self.text1.grid(row=1,column=2,padx=(0,5))
Label(window,textvariable=self.outcomes,font="Helvetica 68 bold").grid(row=7,column=2,sticky=E)
wsheet1 = gsheet.worksheet("Sheet2")
mydata1 = wsheet1.get_values()
mydata2=mydata1[1:]
cool=mydata1[0]
look = pd.DataFrame(mydata2, dtype=str)
#window.after(1, self.searchbarcode())
#self.text1.bind('<Enter>', self.searchbarcode())
Button(window,text='search',command=self.searchbarcode,font="Helvetica 38").grid(row=5,column=2,padx=(100,5),pady=5,sticky=W)
#Button(window,text='clear',command=self.clear_text,font="Helvetica 38").grid(row=5,column=2,padx=(100,5),pady=5,sticky=E)
window.mainloop()
#def clear_text(self):
def searchbarcode(self):
bar = self.barcode.get()
outtt=bar[1:5]
self.outcomes.set(outtt)
self.text1.delete(0, 'end')
searchloc()
If you want to happen when you press the return key, you almost had it right. The function has to accept an event parameter even if you don't use it, and you need to make sure you pass the function itself, not the result of calling the function (ie: self.searchbarcode instead of self.searchbarcode()).
Also, the event is <Return>. <Enter> is for when the mouse enters the widget.
def __init__(self):
...
self.text1.bind('<Return>', self.searchbarcode)
...
def searchbarcode(self, event):
...
If you want to call searchbarcode both with or without the event parameter, give it a default value of None:
def searchbarcode(self, event=None):
...

Text-Based adventure in Kivy, Python

I'm very new to programming, just have an introductory seminar in university, and I'm supposed to program a little app in Python. For that, I want to use Kivy, but I got stuck.
I have a text file which should include the question, the possible answers and where it's supposed to go considering which answer the user chose:
0|"Will you rather do A or B?"|"A"|"B"|1|2
1|"Congrats, you chose A. Now go to B."|"Go to B"|"Go to B"|2|2
2|"That's B. Incredible. Want to discover C?"|"Yes."|"Stay here."|3|6
3|Wow, C is so cool, isn't it? There's also a D.|D? Crazy!|Boring. Go back.|4|0
4|Welcome to the depths of D. You are curious, aren't you?|Yep.|Nope.|5|0
5|Cool. There's nothing else here.|There must be.|Knew it.|4|0
6|Surprise! You should really discover C.|Alright.|Is there a D?|3|4
Now I want the game to go to the according line, replace the displayed text and go on. In theory, this is kind of working with my Code (I'm sorry if it's messed up, as I said, I'm new to this topic):
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
with open('try.txt') as t:
Lines = t.readlines()
first_line = Lines[0].strip()
x = first_line.split("|")
answer_b = int(x[5])
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.inside = GridLayout()
self.inside.cols = 2
self.btna = Button(text=x[2])
self.btna.bind(on_press=self.a)
self.inside.add_widget(self.btna)
self.btnb = Button(text=x[3])
self.btnb.bind(on_press=self.b)
self.inside.add_widget(self.btnb)
self.main_text = Label(text=x[1])
self.add_widget(self.main_text)
self.add_widget(self.inside)
def a(self, instance):
answer_a = int(x[4])
next_line_a = Lines[answer_a].strip()
print(next_line_a)
print(answer_a)
x = next_line_a.split("|")
self.main_text.text = x[1]
self.btna.text = x[2]
self.btnb.text = x[3]
self.btna.bind(on_press=self.a)
def b(self, instance):
next_line_b = Lines[answer_b].strip()
print(next_line_b)
print(answer_b)
x = next_line_b.split("|")
self.main_text.text = x[1]
self.btna.text = x[2]
self.btnb.text = x[3]
class Game(App):
def build(self):
return MyGrid()
if __name__ == '__main__':
Game().run()
The problem is that it stays with the first line I defined and I don't really know how to go around that problem. I would imagine that I first define x with the first line, and after that x gets redefined with the according new line. But the next_line and x variable are both dependent on each other - I tried two different ways with answer a and b, but both don't really work. B will just continuously take the first_line-x, A tells me that x is referenced before assignment.
It would be great if someone could help me out of my confusion, because everything I tried just didn't work out...
Thanks!
I changed it so you pass items into the object that you create. It's challenging to get the inheritance correct.
I also added an initializer to the Games object. I think this works but to be honest I am not expert in the workings of Kivy and have gotten this pattern to work but I don't know for sure if it is best practice.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
with open('try.txt') as t:
Lines = t.readlines()
class MyGrid(GridLayout):
def __init__(self, Lines: list):
super(MyGrid, self).__init__()
self.first_line = Lines[0].strip()
self.xx = self.first_line.split("|")
self.answer_b = int(self.xx[5])
self.cols = 1
self.inside = GridLayout()
self.inside.cols = 2
self.btna = Button(text=self.xx[2])
self.btna.bind(on_press=self.a)
self.inside.add_widget(self.btna)
self.btnb = Button(text=self.xx[3])
self.btnb.bind(on_press=self.b)
self.inside.add_widget(self.btnb)
self.main_text = Label(text=self.xx[1])
self.add_widget(self.main_text)
self.add_widget(self.inside)
def a(self, instance):
answer_a = int(self.xx[4])
next_line_a = Lines[answer_a].strip()
print(next_line_a)
print(answer_a)
self.xx = next_line_a.split("|")
self.main_text.text = self.xx[1]
self.btna.text = self.xx[2]
self.btnb.text = self.xx[3]
self.btna.bind(on_press=self.a)
def b(self, instance):
next_line_b = Lines[self.answer_b].strip()
print(next_line_b)
print(self.answer_b)
self.xx = next_line_b.split("|")
self.main_text.text = self.xx[1]
self.btna.text = self.xx[2]
self.btnb.text = self.xx[3]
class Game(App):
def __init__(self, **kwargs):
self._arguments_to_pass_through = kwargs
super().__init__()
def build(self):
return MyGrid(**self._arguments_to_pass_through)
if __name__ == '__main__':
Game(Lines=Lines).run()

Choosing from a List of methods in a tkinter Button

Good Day,
I'm new to this forum (and quite new to programming), so I hope my question is properly formulated.
I've been trying to create a GUI in python using tkinter, and I want to have two buttons calling methods of two different classes. One method is defining an integer, the second one is reporting content. I'd have a list of objects of the latter class, and I want to choose the right instance by the integer. Here's a MWE:
import tkinter as tk
class data:
def __init__(self, content):
self.content = content
def report(self):
print("This is reported as self.content:" + str(self.content)) #This doesnt report the correct value for some reason?
print("The Class does register the correct idx:" + str(Selector.idx))
print("Using the Dict the correct value can be returned:" + str(vocables[Selector.idx].content))
class increment:
def __init__(self):
self.idx = 0
def increase(self):
self.idx += 1
print(self.idx)
vocables[self.idx].report()
root = tk.Tk()
Selector = increment()
vocables = []
for id in range(10):
vocables.append(data(id))
# print(vocables[id].content)
CheckVocable = tk.Button(root, text="Report", command=vocables[Selector.idx].report)
CheckVocable.pack()
NextVocable = tk.Button(root, text="Increase Index", command=Selector.increase)
NextVocable.pack()
root.mainloop()
I do not understand why the print of line 8 always reports the value of the first item in the list (vocabules[0] in this instance) instead of my desired value, which is returned in all other print cases. Am I messing up the work with classes or is the button behavior confusing me?
Thanks in advance!

Tkinter - How to change the value of an argument for an event binding with lambda function?

I have a list named chosenTestHolder (imported from the my_config file) that consists of several objects each with the attribute 'sentence'.
When pressing the button 'Press' for the first time, the attribute 'sentence' of the first object in the chosenTestHolder should be displayed in the text widget. The next time the button 'Press' is pressed the attribute 'sentence' of the second object in chosenTestHolder should be displayed and so on.
I am using lambda event for binding the 'Press' button and tries to use a new sentences as its first arguments after each pressing of the 'Press' button. However, it keeps showing the first sentence.
When searching Stackoverflow I have seen in
Using lambda function to change value of an attribute that you can't use assignments in lambda expressions but by reading that I still have not figured out how to solve my problem.
Grateful for help! Code is below!
main.py
from tkinter import font
import tkinter as tk
import tkinter.ttk as ttk
import my_config
import Testlist as tl
class TestWidgetTest:
def __init__(self):
ram = tk.Frame(root)
ram.grid(in_=root,row=0, column=0)
self.myText = tk.Text(ram, height = 5)
self.myText.grid(row=0,column=1)
my_config.counter = 0
self.myButton = tk.Button(ram, text = 'Press')
self.myButton.grid(row =1, column =0, columnspan =2)
indata =[my_config.chosenTestHolder[my_config.counter] , self.myText]
self.myButton.bind('<ButtonRelease-1>',lambda event, arg=indata : self.TagConfigure(event, arg))
def TagConfigure(self, event, arg):
arg[1].delete('1.0',tk.END)
arg[1].insert('1.0',arg[0].sentence)
my_config.counter += 1
root = tk.Tk()
TestWidgetTest()
root.mainloop()
my_config.py
import Testlist as tl
testListHolder = [ ['Fabian was very tired'],
['Thomas light the fire'],
['Anna eat a red apple ']]
chosenTestHolder = []
count = 0
while count <(len(testListHolder)):
chosenTestHolder.append(tl.Testlist(testListHolder[count][0]))
count += 1
counter = 0
Testlist.py
class Testlist:
def __init__(self, sentence):
self.sentence = sentence
Your issue is the assignment of indata.
You do only assign in init.
To get your code working you need to re-configure your sentecte...
indata =[my_config.chosenTestHolder[my_config.counter] , self.myText]
self.myButton.bind('&ltButtonRelease-1&gt',lambda event, arg=indata : self.TagConfigure(event, arg))
I would advise to keep track of the current sentence as an instance variable.
class Test_widget(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, args, kwargs)
self.sentences=["a", "b", "c", "d"] # the data
self.show = tk.StringVar() # the current displayed data
self.show.set("NULL")
self.counter=0 # the indexer
tk.Label(self, textvariable=self.show).grid(row=0)
tk.Button(self, command=self.click).grid(row=1)
def click(self, event):
self.show.set("%s"%self.sentences[self.counter]) # use the indexer to access the data
self.counter = self.counter + 1 # modify the indexer
if self.counter = len(self.sentences): # make sure you dont run in index-err
self.counter = 0
As you see, there is no need at all for the lambdas.
Edit
As to your questions:
The change in your original code was not intended.
I do not see a use case where you can use a lambda for its use inside your code.
At least none where a lambda is necessary.
Please remember to use lambda only and exclusively if there are
no ( == NULL ) other options.
Using inheritance (thats what the mechanism is called), you can inherit functions, "default" behaviour from other classes. It is a common mechanism in programming and not exclusive to python.
It is used like any normal object except you have to call the constructor of the base class (what I do using tk.Frame.__init__(self, args, kwargs) inside the init method. For more information on inheritance please refer to the uncounted manuals and tutorials available for that topic (google is your friend now that you know what the mechanism is called).

Refer Names of a Button created from for loops Iteration variables and use it in python kivy

I am facing a very basic issue here . I want to create 100s of Buttons and to make them I want to use loops preferably for loop and create Button Names in the loop itself so i can use it later in the program logic i.e. example for i=1 i want to create Button1 and for i=100 i want button Button100 , however i am not sure how to do that in Python Kivy .
This is same thing which we can do in Linux using &variable name and sometime eval .Please see the code below for better . Comments will describe the issue here :
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.graphics import Color
from kivy.clock import Clock
class RootWidget(GridLayout):
pass
class MainApp(App):
def build(self):
self.parent = GridLayout(cols=6)
for i in (1,2,3,4,5,6):
for j in (1,2,3,4,5,6):
#Want to make self.BName s value as ButtonName%s%s
self.BName= 'ButtonName%s%s'%(i,j)
print "Button Name should be ",self.BName
self.BName = Button(text='%s%s'%(i,j))
self.parent.add_widget(self.BName)
Clock.schedule_once(lambda a:self.update(),1)
return self.parent
def update(self):
print "I am update function"
for child in self.parent.children:
print child
#Want to make use ButtonName11 and not BName
self.BName.text = "hello"
#self.ButtonName11.text= "hello"
if __name__ == '__main__':
MainApp().run()
Have you tried using this concept?
class Foo:
def __init__(self):
for i in range(100):
self.__dict__['Button{}'.format(i)] = Button(text='Button no. {}'.format(i))
>>> foo = Foo()
>>> foo.Button5.text
Button no. 5
>>> foo.Button98.text
Button no. 98
Scorpion_God way of doing it should work, however, a clearer way is using setattr.
Here's some example code:
class Foo(object):
def create_buttons(self):
for i in range(10):
setattr(self, 'my_name{}'.format(i), i)
foo = Foo()
foo.my_name3
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'my_name3'
foo.create_buttons()
foo.my_name3
3

Categories