The code below generates several "social media posts" with images nested within MDCards using a while loop. When the user double taps on one of the images, I want to be able to somehow know which of the images they double-tapped so that that image can call a function.
My intention is to create a mini internal blog for my app, when the user double-taps, for example, post #2 in the newsfeed, the "likes" for that post should increase.
Currently, in the code below, all the cards call the same function
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
from kivymd.app import MDApp
from kivymd.uix.card import MDCard
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.utils.fitimage import FitImage
Window.size = (440, 760)
class ImageContainer(FitImage):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.is_double_tap:
print("double tap")
class blog(MDApp):
def build(self):
blog_page = MDGridLayout(cols=1, spacing=40, size_hint_y=None, padding=[20,])
blog_page.bind(minimum_height=blog_page.setter('height'))
count = 1
while count <= 5:
BlogPost = MDCard(elevation=1, radius=8, size_hint=(.9, None), height=300)
BlogPost.add_widget(ImageContainer(source='data/assets/img/placeholder.jpg', radius=[8,]))
blog_page.add_widget(BlogPost)
count += 1
blog = ScrollView(size_hint=(1, None), size=(Window.width, Window.height), pos_hint={'center_x': 0.5, 'top': 1})
blog.add_widget(blog_page)
return blog
if __name__== '__main__':
blog().run()
The self argument of the on_touch_down() method identifies which ImageContainer was double tapped. You can see this if you modify that method:
class ImageContainer(FitImage):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.is_double_tap:
print("double tap on", self, ", Image source:", self.source)
Related
I have a problem when creating and using data table in kivy, this is my code structure in brief.
There are two buttons in the screen one inside the Example class and one outside the Example class
and the two buttons should execute the same function.
I need to call the method update_row_data to update the data table rows from Example() class when click any one of the two buttons.
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.datatables import MDDataTable
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.button import MDRaisedButton
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen
from kivy.lang import Builder
Builder.load_string("""
<All>:
Test:
Example:
""")
class All(Screen):
pass
class Test(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.test()
def test(self):
self.add_widget(MDRaisedButton(
text="Change 2 row",
pos_hint={"center_x": 0.5},
on_press = Example().update_row_data,
y=24,
))
class Example(BoxLayout):
data_tables = None
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.build_()
def build_(self):
self.layout = MDFloatLayout()
self.layout.add_widget(
MDRaisedButton(
text="Change 2 row",
pos_hint={"center_x": 0.5},
on_release=self.update_row_data,
y=24,
)
)
self.data_tables = MDDataTable(
pos_hint={"center_y": 0.5, "center_x": 0.5},
size_hint=(0.9, 0.6),
use_pagination=False,
column_data=[
("No.", dp(30)),
("Column 1", dp(40)),
("Column 2", dp(40)),
("Column 3", dp(40)),
],
row_data=[(f"{i + 1}", "1", "2", "3") for i in range(3)],
)
self.layout.add_widget(self.data_tables)
self.add_widget(self.layout)
def update_row_data(self, instance_button):
self.data_tables.row_data = [('1','1','1','1')]
print(self.data_tables.row_data)
class App(MDApp):
def build(self):
return All()
App().run()
The problem is you are creating a new instance of class Example and invoke it's method update_row_data each time you press the button. That way though it indeed does the job, you can't see it happening because it is not in your current GUI model.
The fix is to acess exactly that Example class you have added to your root. One of the many ways to do that is keeping a reference of that class (say, by id) and access that properly. Below is the corresponding implementation of this idea,
First create a reference in kvlang,
<All>:
Test:
Example:
id: example
Now in method test of class Test,
def test(self):
self.add_widget(MDRaisedButton(
text="Change 2 rows",
pos_hint={"center_x": 0.5},
on_press = self.update_row,
y=24,
))
def update_row(self, btn):
# Access that class already added to your `root`.
example_cls = self.parent.ids.example
# Now make the call.
example_cls.update_row_data(btn)
Note:
You have added both of your classes to a Screen (without specifying relative size or pos, you can check it yourself by adding canvas). Thus they will sit on top of other and may be troublesome if there are different types of widgets. You better chose ScreenManager instead.
Im writing an app in Kivy which automaticaly adds Buttons and gives them a unique id using a for loop. This id is then used as a key in the dictionary for a link. So the dictionary works fine and after printing it, it outputs {'button0': 'somewebsite', 'button1': 'other website', 'button2': 'andanotherwebsite'} which is exactly what I want but the button callback function always prints out button2 instead of its own id. Am I assigning the ids wrong? The example below demonstrates my problem.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivymd.utils import asynckivy
from kivy.clock import Clock
class TestButton(Button):
def callback(self):
print(self.id)
class RootWidget(BoxLayout):
def __init__(self):
super().__init__()
self.links = ["somewebsite", "other website", "andanotherwebsite"]
self.dic_btn_to_lnk = {}
self.size_hint = (None, None)
self.size = ("600dp", "50dp")
Clock.schedule_once(self.add_widgets, 0)
def add_widgets(self, *args):
async def update():
number = 0
for link in self.links:
button = TestButton()
button.text = link
button.size = ("200dp", "50dp")
button.pos_hint = {"center_x": .5}
btn_id = "button" + str(number)
button.id = btn_id
button.bind(on_release=lambda x: button.callback())
number += 1
self.dic_btn_to_lnk[btn_id] = link
self.add_widget(button)
print(self.dic_btn_to_lnk)
asynckivy.start(update())
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
The problem is that your on_release binding is calling button.callback(), and button will be the last Button added by the time the on_release is triggered. The solution is to use partial, which freezes its arguments to their values when partial is executed, so the on_release calls the correct button.callback. Like this:
button.bind(on_release=partial(button.callback))
And to simplify the above, the definition of callback is changed to:
class TestButton(Button):
def callback(self, instance):
print(self.id)
I am building a multiple screen App with Kivy and I would like to use the ScreenManager to navigate between the multiple screens. I have seen examples and documentation for how to create the screens within a .kv file, but I want to know how to create them within the .py file.
Problem: When I create the screen subclasses as shown below, my app
window returns a blank screen.
Question: What is the correct way to
create the Screen subclasses within a .py file?
Right now I have two Screen subclasses defined: 'welcomeScreen', and 'functionScreen'. Each consists of a layout with some widgets.
kivy.require('1.9.1')
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
import kivy.uix.boxlayout
import kivy.uix.button
from kivy.uix.screenmanager import ScreenManager, Screen
class PanelBuilderApp(App): # display the welcome screen
def build(self):
# Create the screen manager and add widgets to the base sm widget
sm = kivy.uix.screenmanager.ScreenManager()
sm.add_widget(Screen(name='welcomeScreen'))
sm.add_widget(Screen(name='functionScreen'))
# sm.current= 'welcomeScreen'
return sm
class welcomeScreen(Screen): #welcomeScreen subclass
def __init__(self, **kwargs): #constructor method
super(welcomeScreen, self).__init__(**kwargs) #init parent
welcomePage = FloatLayout()
box = kivy.uix.boxlayout.BoxLayout(orientation='vertical', size_hint=(0.4, 0.3),
padding=8, pos_hint={'top': 0.5, 'center_x': 0.5})
welcomeLabel = Label(text='Hello and welcome to the Panel Builder version 1.0.\nApp by John Vorsten\nClick below to continue',
halign= 'center', valign= 'center', size_hint= (0.4, 0.2), pos_hint= {'top': 1, 'center_x': 0.5})
welcomeBox = kivy.uix.button.Button(text= 'Click to continue')
welcomeBox.bind(on_press= self.callback)
welcomeBox2 = kivy.uix.button.Button(text='not used')
welcomePage.add_widget(welcomeLabel)
box.add_widget(welcomeBox)
box.add_widget(welcomeBox2)
welcomePage.add_widget(box)
self.add_widget(welcomePage)
def callback(instance):
print('The button has been pressed')
sm.switch_to(Screen(name= 'functionScreen'))
# sm.current = Screen(name= 'functionScreen')
class functionScreen(Screen): #For later function navigation
def __init__(self, **kwargs): #constructor method
super(functionScreen, self).__init__(**kwargs) #init parent
functionPage = kivy.uix.floatlayout.FloatLayout()
functionLabel = Label(text='Welcome to the function page. Here you will choose what functions to use',
halign='center', valign='center', size_hint=(0.4,0.2), pox_hint={'top': 1, 'center_x': 0.5})
functionPage.add_widget(functionLabel)
self.add_widget(functionPage)
# sm.add_widget('Name') #Add more names later when you create more screens
# OR#
# for i in ScreenDirectory:
# sm.add_widget(ScreenDirectory[i])
PanelBuilderApp().run()
if __name__ == '__main__':
pass
I understand I can add the definitions to a .kv file, and I will probably do this as the app grows. However, I like being explicit as I am learning how to use kivy.
I think you think using Screen(name='welcomeScreen') you are using welcomeScreen but that is not true, if you want to use welcomeScreen you should use it directly.
On the other hand you have typographical errors so I have corrected, I recommend you follow the kivy tutorials, obviously you must have a solid OOP base (and I think you do not have it so your task is to reinforce).
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
class PanelBuilderApp(App): # display the welcome screen
def build(self):
sm = ScreenManager()
sm.add_widget(WelcomeScreen(name='welcomeScreen'))
sm.add_widget(FunctionScreen(name='functionScreen'))
return sm
class WelcomeScreen(Screen): #welcomeScreen subclass
def __init__(self, **kwargs): #constructor method
super(WelcomeScreen, self).__init__(**kwargs) #init parent
welcomePage = FloatLayout()
box = BoxLayout(orientation='vertical', size_hint=(0.4, 0.3),
padding=8, pos_hint={'top': 0.5, 'center_x': 0.5})
welcomeLabel = Label(text='Hello and welcome to the Panel Builder version 1.0.\nApp by John Vorsten\nClick below to continue',
halign= 'center', valign= 'center', size_hint= (0.4, 0.2), pos_hint= {'top': 1, 'center_x': 0.5})
welcomeBox = Button(text= 'Click to continue', on_press=self.callback)
welcomeBox2 = Button(text='not used')
welcomePage.add_widget(welcomeLabel)
box.add_widget(welcomeBox)
box.add_widget(welcomeBox2)
welcomePage.add_widget(box)
self.add_widget(welcomePage)
def callback(self, instance):
print('The button has been pressed')
self.manager.current = 'functionScreen'
class FunctionScreen(Screen): #For later function navigation
def __init__(self, **kwargs): #constructor method
super(FunctionScreen, self).__init__(**kwargs) #init parent
functionPage = FloatLayout()
functionLabel = Label(text='Welcome to the function page. Here you will choose what functions to use',
halign='center', valign='center', size_hint=(0.4,0.2), pos_hint={'top': 1, 'center_x': 0.5})
functionPage.add_widget(functionLabel)
self.add_widget(functionPage)
if __name__ == '__main__':
PanelBuilderApp().run()
I am writing my first Kivy app in python only (I'm avoiding kv for now). I have created a custom widget called WorldviewWidget, and I'm trying to use it as a place to draw. With the button widgets, I just give a size_hint and a pos_hint, and they show up where I want them. But with my widget, I don't know how to use the size_hint and position_hint to size the rectangle I am drawing in my WorldviewWidget. Here is the code. Thanks in advance!
#! /usr/bin/python
from kivy.app import App
from kivy.graphics import *
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class WorldviewWidget(Widget):
def __init__(self, **kwargs):
super(WorldviewWidget, self).__init__(**kwargs)
self.canvas.clear()
print self.size, self.pos
with self.canvas:
Color(1, 0, 0, 1, mode='rgba')
# HELP! I want the rectangle to be resized when the window changes size so that it always takes up the same proportion of the screen.
self.rect = Rectangle(size=???, pos=???)
class JFROCS_App(App):
def build(self):
Window.clearcolor = [1,1,1,1]
parent = FloatLayout(size=Window.size)
worldview = WorldviewWidget(size_hint=(0.4, 0.4), pos_hint = {'x':0.2, 'y':0.2})
parent.add_widget(worldview)
start_btn = Button(text='Start', size_hint=(0.1, 0.1), pos_hint={'x':.02, 'y':.7}, background_color=[0,1,0,1])
start_btn.bind(on_release=self.start_simulation)
parent.add_widget(start_btn)
pause_btn = Button(text='Pause', size_hint=(0.1,0.1), pos_hint={'x':.02, 'y':.6}, background_color=[1,1,0,1])
pause_btn.bind(on_release=self.pause_simulation)
parent.add_widget(pause_btn)
stop_btn = Button(text='Stop', size_hint=(0.1,0.1), pos_hint={'x':.02, 'y':.5}, background_color=[1,0,0,1])
stop_btn.bind(on_release=self.stop_simulation)
parent.add_widget(stop_btn)
return parent
def start_simulation(self, obj):
print "You pushed the start button!"
def pause_simulation(self, obj):
print "You pushed the pause button!"
def stop_simulation(self, obj):
print "You pushed the stop button!"
if __name__ == '__main__':
JFROCS_App().run()
I think that this sort of task is predestined for the kivy-language but here is a solution in Python. Basically, I have used the bind-method to make your widget listen for changes in its parent's size. Have a look at this for more information on this mechanism.
from kivy.app import App
from kivy.graphics import *
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.button import Button
class WorldviewWidget(Widget):
def __init__(self, **kwargs):
super(WorldviewWidget, self).__init__(**kwargs)
self.canvas.clear()
print self.size, self.pos
with self.canvas:
Color(1, 0, 0, 1, mode='rgba')
# *changed* ##############################################
self.rect = Rectangle(size=self.size, pos=self.pos)
# *new* ##########################################################
def update_size(self, instance, new_size):
print "UPDATING SIZE", instance, new_size
self.size[0] = new_size[0] * 0.4
self.size[1] = new_size[1] * 0.4
self.rect.size = self.size
self.pos[0] = self.parent.size[0] * 0.2
self.pos[1] = self.parent.size[1] * 0.2
self.rect.pos = self.pos
class JFROCS_App(App):
def build(self):
Window.clearcolor = [1,1,1,1]
parent = FloatLayout(size=Window.size)
# *changed* ##################################################
worldview = WorldviewWidget(size=(0.4*parent.size[0], 0.4*parent.size[1]),
pos=(0.2*parent.size[0], 0.2*parent.size[1]))
# makes sure that the widget gets updated when parent's size changes:
parent.bind(size=worldview.update_size)
parent.add_widget(worldview)
start_btn = Button(text='Start', size_hint=(0.1, 0.1), pos_hint={'x':.02, 'y':.7}, background_color=[0,1,0,1])
start_btn.bind(on_release=self.start_simulation)
parent.add_widget(start_btn)
pause_btn = Button(text='Pause', size_hint=(0.1,0.1), pos_hint={'x':.02, 'y':.6}, background_color=[1,1,0,1])
pause_btn.bind(on_release=self.pause_simulation)
parent.add_widget(pause_btn)
stop_btn = Button(text='Stop', size_hint=(0.1,0.1), pos_hint={'x':.02, 'y':.5}, background_color=[1,0,0,1])
stop_btn.bind(on_release=self.stop_simulation)
parent.add_widget(stop_btn)
return parent
def start_simulation(self, obj):
print "You pushed the start button!"
def pause_simulation(self, obj):
print "You pushed the pause button!"
def stop_simulation(self, obj):
print "You pushed the stop button!"
if __name__ == '__main__':
JFROCS_App().run()
And have a look at the kivy-language -- it takes care of all the binding for you :)
Or, how to do so using python, give it an ID, and set it as background image in kv language?
I would like to be able to draw on top of an image instead of a black screen, which I am doing here:
edited
new problem: upload button does not work, here is new code
from random import random
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.graphics import Color, Line, Rectangle
from kivy.uix.filechooser import FileChooserListView, FileChooserIconView
from kivy.uix.floatlayout import FloatLayout
class MyPaintWidget(Widget):
def on_touch_down(self, touch):
color = (random(), random(), random())
with self.canvas:
Color(*color)
d = 30.
touch.ud['line'] = Line(points=(touch.x, touch.y))
def on_touch_move(self, touch):
touch.ud['line'].points += [touch.x, touch.y]
class MyPaintApp(App):
def build(self):
parent = Widget()
painter = MyPaintWidget()
Choose = Button(text = 'upload image')
parent.add_widget(painter)
parent.add_widget(Choose)
def chooose_file(obj):
fc = FileChooserIconView(title= 'upload image')
image_path = self.fc.selection[0]
image_name = file_path.split('/')[-1]
with self.canvas.before:
Rectangle(
size=self.size,
pos=self.pos,
source=image_name)
Choose.bind(on_release=choose_file)
return parent
if __name__ == '__main__':
MyPaintApp().run()
What about this:
If you used the kivy filechooser to get the user to select an image file,
then you could use the .selection attribute of the filechooser to get the name and/or path of that file. Once you have that, you could use it to set the source of a Rectangle on the canvas of the layout etc. where you want the background image.
For instance, to set a background image on a BoxLayout, inside the class that inherits from the BoxLayout:
fc = FileChooserIconView(title="Choose Image")
image_path = self.fc.selection[0]
image_name = file_path.split('/')[-1]
with self.canvas.before:
Rectangle(
size=self.size,
pos=self.pos,
source=image_name)
This is of course a very simplistic example, and isn't really taking the rest of your code into account, but with the kivy docs on FileChooser you should get it. Worth noting also that you could do this in the kv file, perhaps much more cleanly.