Kivy: How can I recode Scrollview and Gridlayout in pure Python? - python

I have an example code and it works nice.
But in this code Scrollview and Gridlayout are created in KV File.
I have a scroll bug problem so i want to create them in pure python.
There are 350 boxlayouts and they are created in Scrollview and each boxlayout has 5 widgets (buttons label etc) So this causes some bugs in Scrollview when clearing screen and recreating results. There are no problem for 80 boxlayout, but they are created dynamically and some times there are 300+ boxlayouts in Scrollview.
I thought if i can create Scrollview and Gridlayout, i can fix this issue.
So i need your help.
How can i do that.
Test code:
PY File:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.metrics import dp
from kivy.uix.behaviors import ButtonBehavior
from kivy.clock import Clock, mainthread
import json
import threading
class Test(BoxLayout):
def __init__(self, **kwargs):
super(Test, self).__init__(**kwargs)
self.data = self.datas()
# Homepage Screen
def homepage(self, screenmanager):
screenmanager.current = 'homepage_screen'
Clock.schedule_once(self.clear_widgets)
# Clear Widgets
def clear_widgets(self, *args):
for child in [child for child in self.ids.gridsonuc.children]:
self.ids.gridsonuc.remove_widget(child)
#Second screen
def second(self,screenmanager):
screenmanager.current = 'second_screen'
Clock.schedule_once(self.clear_widgets)
Clock.schedule_once(self.datas) # Before calculation, each time app pulls data again, but Kivy Does Not Update The Refreshed Data in The Screen!
Clock.schedule_once(self.calculate)
# or, if i can use threading system as well but this time i must add #mainthread above def calculate(self, *args): to make code work.
# in both scenario, Kivy Does Not Update The Refreshed Data in The Screen While APP is Running.
# mythread1 = threading.Thread(target=self.clear_widgets)
# mythread1.start()
# mythread2 = threading.Thread(target=self.datas)
# mythread2.start()
# mythread3 = threading.Thread(target=self.calculate)
# mythread3.start()
# Calculation
##mainthread
def calculate(self, *args):
for i in self.data['home']:
box = BoxLayout(size_hint_y = None, height = dp(50))
hometeams = Label(text = f'{[i]}', font_name = 'Roboto', font_size = dp(15), size_hint = (0.225, 1), halign='center', bold = True )
box.add_widget(hometeams)
self.ids.gridsonuc.add_widget(box)
def datas(self, *args):
# PLEASE CHANGE THE LOCATION!!!!!!!!!!!!!!!!!
with open ("C:\\Users\\Messi\\Desktop\\Python\\Projects\\Football Tips\\Kivy\\Testing Bugs\\Test1\\data.json", "r") as dosya:
dataApi = json.load(dosya)
print('datas updated')
self.data = dataApi # update the self.data
return dataApi
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
KV File:
#:import NoTransition kivy.uix.screenmanager.NoTransition
<Test>:
ScreenManager:
transition: NoTransition()
id: sm
size: root.width, root.height
Screen:
name: 'homepage_screen'
BoxLayout:
size_hint: 1, 0.10
Button:
text: 'Calculate'
id: underOver_button_homepage
on_press: root.second(sm)
background_color: 0, 0, 0, 0
Screen:
name: 'second_screen'
BoxLayout:
spacing: '20dp'
orientation: 'vertical'
BoxLayout:
size_hint: 1, 0.80
ScrollView:
scroll_type: ['bars', 'content']
bar_margin: '5dp'
bar_color: 1, 0.4, 0.769, 1
bar_width: '5dp'
bar_inactive_color: 1, 0.4, 0.769, 1
GridLayout:
id: gridsonuc
cols: 1
spacing: '50dp'
size_hint_y: None
height: self.minimum_height
BoxLayout:
size_hint: 1, 0.10
Button:
text: 'Home'
id: home_button_underOver
on_press: root.homepage(sm)
background_color: 0, 0, 0, 0
For example:
I don't want this structure in KV file
BoxLayout:
size_hint: 1, 0.80
ScrollView:
scroll_type: ['bars', 'content']
bar_margin: '5dp'
bar_color: 1, 0.4, 0.769, 1
bar_width: '5dp'
bar_inactive_color: 1, 0.4, 0.769, 1
GridLayout:
id: gridsonuc
cols: 1
spacing: '50dp'
size_hint_y: None
height: self.minimum_height
My Goal:
BoxLayout:
size_hint: 1, 0.80
Newscroll: # New Scrollview Class and have also Gridlayout in it
GridLayout and Scrollview must have same settings and i want to use like below. I think scroll_type: ['bars', 'content'], bar_margin: '5dp', bar_color: 1, 0.4, 0.769, 1, bar_width: '5dp', bar_inactive_color: 1, 0.4, 0.769, 1, cols: 1, spacing: '50dp', size_hint_y: None, height: self.minimum_height should code in Python
And a new clear def() for cleaning full scrollview class, when clear and create Scrollview in Pure Python, there are created dynamically so I can fix my Scroll bug issue.
Thanks for your help.
I hope one day I can be at a level where I can help other people in Kivy.

Kivy: How can I recode Scrollview and Gridlayout in pure Python?
The shortest answer to your question is:
You can't.
See Pure python gui library? here on stackoverflow to read:
Notion of "pure python gui library" is wrong because ultimately you will be using system level calls and widgets, may be thru ctypes but that doesn't change the fact that if you start implementing your idea you will eventually end with using wxPython or some other module.
along with the statement in the accepted answer:
The path of least effort and best results would be to learn what it takes to deploy an app using some of the existing GUI libraries.
Kivy is a Python module you can use to create GUIs (Graphical User Interfaces) and comes with the possibility to create a Scrollview and a Gridlayout objects/widgets for by it created window with an user interface.
Your goal is to use "pure Python" to replace the objects/widgets, but it is not possible to use "pure Python" for this task, because Python requires usage of a module for GUIs.
Most native GUI module that comes with the standard Python installation and is nearest to the notion of "pure Python" is tkinter, but it needs GTK to be already installed on the system, else it won't be available and you need to learn how to use it properly like you need it when using Kivy.
In other words it will be a good idea to find out what actually causes the problems you experience while using Kivy and solve them instead of learning how to use another GUI library to recreate your application.
And if your idea is to create own widgets within Kivy you have to learn how to write own extensions to Kivy which can take you much more effort compared to finding out the reason for why you face problems while using it.

Related

Python Kivy self.add_widget() doesn't update while code running

The idea is to create a texting app that works like Messenger. I am having a problem with the chat history which is a "BoxLayer (or GridLayer)" containing all previous text. I want when I insert a new text, it's will appear as a new label or a box and stay below the previous text like this, but when I run the code and insert input text, it's not appearing. I spent hours to find the answer both myself and on the internet, but it's kind of hard for a beginner like me.
.Py file
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.core.image import Image
from kivy.properties import StringProperty
from kivy.core.window import Window
from kivy.graphics.context_instructions import Color
class MainWidget(Widget):
request = StringProperty("This is a previous text, don't mind")
insert_text = StringProperty("Insert Here")
window_size = (305,400)
refresh_key = False
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.Window_Minimum()
def on_size(self,*args):
print(self.width,self.height)
def on_text_validate(self,widget): #<<<<<<<<<<<<<<<<<<<<<<<<<<< input text
request=widget.text
Chat_history_update().chat_history(request)
def Window_Minimum(self):
Window.minimum_width,Window.minimum_height=self.window_size
class Chat_history_update(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
l = Label(text="This is a previous text, don't mind",size_hint=(1, None),height=("30dp"))
self.add_widget(l)
def chat_history(self,request): # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Add Label Function
l = Label(text=request, size_hint=(1, None),height=("30dp"))
self.add_widget(l) # <<<<<<<<<<<<< This won't update my app screen
class Assistant(App):
pass
if __name__ == "__main__":
Assistant().run()
Kv file
MainWidget:
<MainWidget>:
BoxLayout:
size: root.size
orientation: "vertical"
GridLayout:
cols: 3
size_hint: 1,None
height: "50dp"
spacing: "10dp"
padding: "10dp"
Label:
text:"Erza Assistant"
Button:
text:"Edit Path"
Button:
text:"Setting"
GridLayout:
size: self.size
rows: 2
spacing: "10dp"
padding: "10dp"
ScrollView: #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here my text display
Chat_history_update:
orientation: "vertical"
size_hint: 1, None
height: self.minimum_height
TextInput:
size_hint: 1, None
height: "40dp"
text: root.insert_text
multiline: False
on_text_validate: root.on_text_validate(self)
Your code:
Chat_history_update().chat_history(request)
is creating a new instance of Chat_history_update, and calling chat_history() for that new instance. That new instance is not part of your GUI, so you will see no effect. The fix is to access the correct instance of Chat_history_update (the one that is in your GUI). To do that, you can add an id in your kv:
ScrollView: #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Here my text display
Chat_history_update:
id: chu
orientation: "vertical"
size_hint: 1, None
height: self.minimum_height
And then use that id in your py code:
def on_text_validate(self,widget): #<<<<<<<<<<<<<<<<<<<<<<<<<<< input text
request=widget.text
self.ids.chu.chat_history(request)
I think this here might help. You have to reference widgets in the kivy language by using id or ids.
If you do not yet i strongly suggest you learn how to reference widgets by their ids.

Can I load .kv file multiple times when the app is running?

I have two different .kv files:
file defining main interface --> PriceTrackerUI.kv
file defining popup window --> PopupWindow.kv
I want to "refresh" content of popup window, every time I close it.
In PopupWindow.kv the program opens an image by loading file which already exists in a directory. This file is overwritten every time in latter stage of running the app. The problem is, .kv files are loaded at the sheer initialisation of the program and not getting loaded again later. And as it makes sense with the main UI window, it doesn't with popup, which content changes with every click of the button in main UI window
Below is a minimal code
PopupWindow.kv:
<PopupWindow>
orientation: 'vertical'
size_hint: 1, .9
BoxLayout:
orientation: 'horizontal'
cols: 2
AsyncImage:
source: root.daily # loading image stored under that variable
allow_stretch: True
size_hint: .9, 1
pos_hint: {'x': 0, 'center_y': .5}
(...)
PriceTrackerUI:
<SearchPerformer>
GridLayout:
size: root.size
cols: 2
rows: 3
(...)
FloatLayout:
Button:
text: 'Search'
size_hint: .5, .4
pos_hint: {"x": .75, "top": .9}
border: 20, 20, 20, 20
on_press: root.input_grabber(root.dt)
on_release: root.clear()
on_release: root.hit_enter()
main.py:
import UI
UI.PriceTrackerUIApp().run()
UI.py:
import kivy
import database
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.properties import StringProperty
from kivy.lang import Builder
kivy.require('1.11.1')
user_input = []
Builder.load_file('PopupWindow.kv')
class PopupWindow(BoxLayout):
daily = StringProperty('./daily.png')
def popup_show():
pop = PopupWindow()
pop_win = Popup(title="Analysis", content=pop, size_hint=(.9, .9))
pop_win.open()
class SearchPerformer(BoxLayout):
dt = database
def hit_enter(self):
popup_show()
def input_grabber(self, database):
specs = [self.ids.crmk.text, self.ids.crmd.text, self.ids.cryr.text, self.ids.crft.text]
(...)
user_input.extend(specs)
database.add_to_database()
database.query_database()
database.run_plot() # daily.png image gets updated after running that method
class PriceTrackerUIApp(App):
def build(self):
return SearchPerformer()
If you are trying to open a new Popup after changing the daily.png file, your current code should reflect the changed daily.png in the new Popup. Reloading the kv will not affect that.
If you are trying to update the AsyncImage in an already open Popup, then reloading the kv will have no effect. In that case, you just need to call the reload() method of the AsnycImage. Of course, that means that you must retain a reference to the PopupWindow and probably add an id to the AsyncImage. So, to add an id, a slightly modified kv:
<PopupWindow>
orientation: 'vertical'
size_hint: 1, .9
BoxLayout:
orientation: 'horizontal'
cols: 2
AsyncImage:
id: img # id to be used for calling reload()
source: root.daily # loading image stored under that variable
allow_stretch: True
size_hint: .9, 1
pos_hint: {'x': 0, 'center_y': .5}
And use that in your code (along with some small modifications):
def popup_show():
pop = PopupWindow()
pop_win = Popup(title="Analysis", content=pop, size_hint=(.9, .9))
pop_win.open()
return pop # save a reference to the `PopupWindow`
class SearchPerformer(BoxLayout):
dt = database
pop = ObjectProperty(None)
def hit_enter(self):
self.pop = popup_show() # retain reference to `PopupWindow`
def input_grabber(self, database):
specs = [self.ids.crmk.text, self.ids.crmd.text, self.ids.cryr.text, self.ids.crft.text]
(...)
user_input.extend(specs)
database.add_to_database()
database.query_database()
database.run_plot() # daily.png image gets updated after running that method
if self.pop is not None:
self.pop.ids.img.reload() # reload the updated daily.png
You can load a kv file at any point using Builder.load_file or Builder.load_string. When you instantiate a widget, any previously-loaded rules are applied.

Kivy function scope on button release (widget tree)

I am new to Kivy and need some help in understanding function scope. I have built a simple app with two screens. The first screen has two buttons and the second screen has a text label at the centre. On the first screen, I have used app.root.current='new_screen_name' with the on_release attribute for one button and it works fine. It takes me to the next screen which is the intended function. For the second button, I have used a function call which has been defined in the Python file under the class definition of the first screen (the root widget of the button). However, this method does not work and the app window simply closes. I guess I am making a mistake in the function scope and call But I cannot figure out what. Any help would be greatly appreciated.
Python file:
from kivy.config import Config
# Config.set should be used before importing any other Kivy module.
Config.set('kivy','window_icon','sivaicon.png')
# Config set for resizing image button
Config.set('graphics', 'resizable', True)
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.lang.builder import Builder
class SivaLoginScreen(Screen):
def func_authentication(self):
app.root.current='tabbed_screen'
class SivaTabbedScreen(Screen):
pass
class SivaScreenManager(ScreenManager):
pass
class ImageButton(ButtonBehavior, Image):
pass
# Tell Kivy to directly load a file. If this file defines a root widget, it will be returned by the method.
root_widget = Builder.load_file('siva.kv')
class SivaApp(App):
def build(self):
# Initialize root widget
return root_widget
if __name__ == '__main__':
# Run application
SivaApp().run()
Kivy file (.kv):
SivaScreenManager:
SivaLoginScreen:
SivaTabbedScreen:
<ImageButton>:
keep_ratio: True
<SivaLoginScreen>:
name: 'login_screen'
canvas.before:
Color:
rgba: 195/255, 60/255, 35/255, 1
Rectangle:
pos: self.pos
size: self.size
FloatLayout:
size: root.width, root.height
Image:
id: login_logo_siva
source: 'images/sivalogo4.png'
keep_ratio: True
size_hint: 0.2, 0.2
pos_hint: {'center_x':0.5, 'center_y':0.75}
Label:
id: login_label_siva
pos: self.x*0.5-4, self.y*0.5+15
markup: True
font_name: 'roboto/Roboto-Medium.ttf'
text: '[color=#FDFD98]S.[/color][color=#B29DD9]I[/color][color=#FDFD98].[/color][color=#77DD77]V[/color][color=#FDFD98].[/color][color=#779ECB]A[/color]'
font_size: '50sp'
Label:
id: login_label_slogan1
pos: self.x*0.5-3, self.y*0.5-6
markup: True
font_name: 'roboto/Roboto-Regular.ttf'
text: '[color=#FDFD98]SLOGAN TEXT[/color]'
font_size: '15sp'
Label:
id: login_label_slogan2
pos: self.x*0.5-3, self.y*0.5-20
markup: True
font_name: 'roboto/Roboto-Regular.ttf'
text: '[color=#FDFD98]HEADLINE TEXT[/color]'
font_size: '15sp'
BoxLayout:
id:login_button_layout
orientation: 'horizontal'
size_hint: 0.2, 0.2
pos_hint: {'center_x':0.5, 'center_y':0.25}
ImageButton:
id: first_button
source: {'normal': 'images/first.png', 'down': 'images/first-down.png'} [self.state]
on_release: app.root.current='tabbed_screen'
ImageButton:
id: second_button
source: {'normal': 'images/second.png', 'down': 'images/second-down.png'} [self.state]
on_release: app.root.func_authentication()
<SivaTabbedScreen>:
name: 'tabbed_screen'
FloatLayout:
size: root.width, root.height
Label:
pos: self.x*0.5, self.y*0.5
text: 'SECOND SCREEN'
font_size: '50sp'
In your case, app.root link to SivaScreenManager which is the root widget of your application. And in these class, there is not a func_authenticationfunction, why you app crashed.
To refer a class itself in a KV definition, you must just use root, so right code must be :
on_release: root.func_authentication()
see Kivy Language - Reserved Keywords
Definition of func_authentication is not correct also, app is unknown. Use either :
App.get_running_app().root.current='tabbed_screen' or
self.manager.current='tabbed_screen'

how can i use floatLayout inside stacklayout

I am making an app in which, on one screen, I want buttons stacked along the right edge of the screen(for which I need stack layout) and 2 buttons at the centre of the screen(for this I want to use float layout). I have searched for it but nowhere I can see any examples of using two different layouts on one screen.
Can we use two different layouts on a screen? if yes how can we do that?
hers a sample code-
from kivy.uix.stacklayout import StackLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class screen_1(Screen,Stacklayout): ''' here I tried to inherit
floatlayout, but i guess it
doesnt work that way'''
pass
class main(App):
def build(self):
return screen_1()
m = main()
m.run()
kivy code-
<screen_1>:
StackLayout:
orientation: 'tb-rl'
spacing: 10
padding: 90
TextInput:
text: "write your word here"
color: 1,1,1,1
id: word_input
width: 300
size_hint: None, .10
stackLayout:
orientation: 'rl-tb'
spacing: 10
padding: 90
TextInput:
text: "write your word here"
color: 1,1,1,1
Layouts, lake all widgets, can be nested.
If you want two layouts in your screen, you just put them in an appropriate layout to manage their respective size/positions.
First, you likely don't want to do multiple-inheritance with widgets, at least not inheriting from multiple widgets (inheriting from one widget and one or multiple other objects can be fine, but inheriting from multiple widgets will cause issues). Also, Screen is already a RelativeLayout[1] which is almost the same as a FloatLayout (only it makes the pos coordinates of children relative to its own).
Instead, what you want is to compose (by nesting).
Screen:
StackLayout:
size_hint: .5, 1 # let's take half of the screen in width, and all of it in height
# whatever you want inside this layout
BoxLayout:
size_hint: .5, 1 # same, half of the width, all of the height
pos_hint: {'right': 1} # stick to the right of the screen
Button:
[1] https://kivy.org/docs/api-kivy.uix.screenmanager.html?highlight=screen#kivy.uix.screenmanager.Screen
[2] https://kivy.org/docs/api-kivy.uix.relativelayout.html#kivy.uix.relativelayout.RelativeLayout

Scroll contents of GridLayout in ScrollView - Kivy

I will say first off I have tried every single example on the web involving kv lang. Not once have I had any success.
The idea is pretty simple: As I swipe up/down/scroll the contents of GridLayout() within ScrollView() are scrolled up or down.
The best I have been able to do is have the scroll bar fade into view when running the program. Not able to scroll unfortunately.
<Root>
grid_layout: grid_layout
ScreenManager:
...
Screen:
...
ScrollView:
GridLayout:
id: grid_layout
size_hint_y: None
cols: 1
height: self.minimum_height
<list of buttons>
Binding minimum_heightin the __init__ method of the root class (RelativeLayout):
grid_layout = ObjectProperty(None)
self.grid_layout.bind(minimum_height=self.grid_layout.setter('height'))
I have followed https://github.com/kivy/kivy/blob/master/examples/widgets/scrollview.py converting it to kv lang - scroll bar visible, unable to scroll. Also tried every example on Google Groups and here related to using kv lang. Still no scroll :\
Compiling using buildozer and running on Android fails for an unknown reason.
I would appreciate any assistance that can be given.. I am completely clueless at this point
This:
height: self.minimum_height
should be:
minimum_height: self.height
This is unnecessary:
grid_layout = ObjectProperty(None)
self.grid_layout.bind(minimum_height=self.grid_layout.setter('height'))
It also won't scroll unless the contents are larger than the scrollview's height:
Full code:
from kivy.lang.builder import Builder
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
Builder.load_string('''
<Root>:
ScrollView:
size_hint: 1, .1
# setting the width of the scrollbar to 50pixels
bar_width: 50
# setting the color of the active bar using rgba
bar_color: 5, 10, 15, .8
# setting the color of the inactive bar using rgba
bar_inactive_color: 5, 20, 10, .5
# setting the content only to scroll via bar, not content
scroll_type: ['bars']
GridLayout:
size_hint_y: None
cols: 1
minimum_height: self.height
Button
text: 'one'
Button:
text: 'two'
Button:
text: 'three'
Button:
text: 'four'
''')
class Root(FloatLayout):
pass
class DemoApp(App):
def build(self):
return Root()
if __name__ == '__main__':
DemoApp().run()
Being unable to scroll was due to a misunderstanding of Kivy's touch handlers. Completely unrelated to the code mentioned in my question.
The key is to have GridLayout be larger than ScrollView, so GridLayout can be panned within ScrollView.
For those wanting to use ScrollView inside ScreenManager using kvlang only:
ScreenManager:
id: screen_manager
Screen:
manager: screen_manager
id: main_screen
name: 'main'
ScrollView:
bar_width: 4
# pos_hint defaults to 1,1 so no need to declare it
GridLayout:
size_hint_y: None
cols: 1
# you do not need to manually bind to setter('height') in
# python - perfectly possible with kv lang
# this allows for height to update depending on the
# collective heights of its child widgets
height: self.minimum_height
<----- widgets here ----->
# for scroll to show/work there must be more widgets
# then can fit root.height. If not there is no need
# for scrollview :)

Categories