I'm doing a YouTube converter. It's ok but when i download and convert i want to let user know that i'm working: how can i update my interface? I'm using kivy. I tried using a popup, i tried using various kind of loading bar. Now i'm trying to update text in a box in the main interface but nothing, it updates after conversion. Why?
Here's my code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.popup import Popup
from pydub import AudioSegment
from pytube import YouTube
import os
import time
class YouTube2mp3App(App):
def build(self):
self.mylayout = FloatLayout()
mylabel = Label(text= "Benvenuto su YouTube2mp3!\nInserisci il link e premi converti", size_hint=(.8,.2),pos_hint={"x":.1, "y":.7}, font_size=20)
self.txt = TextInput(hint_text="Incolla qui",size_hint=(.9,.1), pos_hint={"x":.05, "y":.4})
self.pb = Label(text="0%", size_hint=(.9,.2), pos_hint={"x":.1, "y":.1}, font_size=20)
#self.txt.bind(on_text_validate=self.convert)
mybutton =Button(text="Nuovo/i video", on_press= self.clearText,size_hint=(.45,.1), pos_hint={"x":.5, "y":.3})
mybutton2 =Button(text="Converti", on_press= self.convert,size_hint=(.45,.1), pos_hint={"x":.05, "y":.3})
self.mylayout.add_widget(mylabel)
self.mylayout.add_widget(self.txt)
self.mylayout.add_widget(self.pb)
self.mylayout.add_widget(mybutton)
self.mylayout.add_widget(mybutton2)
return self.mylayout
def clearText(self, instance):
#clear input
self.txt.text = ''
def convert(self, instance):
try:
#starting to convert using 3 methods
self.pb.text="Preparazione... 10%"
self.mylayout.add_widget(self.pb)
ns=self.check_spazio(self.txt.text)
self.pb.text="Conversione... 33%"
self.mylayout.add_widget(self.pb)
print(ns)
links=self.list_of_links(ns)
self.pb.text="Scaricamento... 80%"
self.mylayout.add_widget(self.pb)
print(links)
self.Tube(links)
self.pb.text="Fatto!... 100%"
self.mylayout.add_widget(self.pb)
content = Button(text='Chiudi', size_hint=(.3,.7))
popup = Popup(title="Fatto!", content=content, auto_dismiss=False, size_hint=(.3,.2))
content.bind(on_press=popup.dismiss)
popup.open()
self.pb.value="0%"
except:
content = Button(text='Chiudi', size_hint=(.3,.7))
popup = Popup(title="Link non valido", content=content, auto_dismiss=False, size_hint=(.3,.2))
content.bind(on_press=popup.dismiss)
popup.open()
def Tube(self, lista):
#convert
for text in lista:
yt=YouTube(text)
ys=yt.streams.get_audio_only()
file=ys.download()
ya=file[:-1]+"3"
AudioSegment.from_file(file).export(ya, format="mp3")
os.remove(file)
def check_spazio(self, string):
#check space between input string
s=list(string)
strig=[]
for i in s:
if i!=" ":
strig.append(i)
string = "".join(strig)
return string
def list_of_links(self, string):
#create a list of strings. Each string is a link
prev=0
links=[]
for i in range (len(string)):
if string[i]=="\n" or string[i]==",":
links.append(string[prev:i])
prev=i+1
elif i==len(string)-1:
links.append(string[prev:i+1])
return links
YouTube2mp3App().run()
When you run a method on the main thread, as you are doing with convert() method, no updates are made to the GUI until that method returns. This is a common problem and is typically handled by running the long running method in a separate thread. See the threading documentation. Then, from inside that method use Clock.schedule_once() to schedule calls that update the GUI on the main thread.
Related
I am making a project involving looking up images. I decided to use kivy, creating a system where you can input a name of a folder (located in a specific directory) into a search bar, and it will return all images inside said folder tagged '.jpg'. I am stuck with updating the window to display these images, and I can't find anything online which helps.
from kivy.properties import StringProperty, ObjectProperty
from kivy.uix.screenmanager import Screen
from kivymd.app import MDApp
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.uix.image import Image
from kivy.core.window import Window
import glob
import os
os.chdir(r'directory/of/imagefolders')
class functions:
def lookup(name):
root = f'imagefolder'
for root, subdirs, files in os.walk(f'imagefolder'):
for d in subdirs:
if d == name:
print(d)
path = f'imagefolder/'+d
print(path)
return path
def load_images(f):
facefile = []
for images in glob.glob(f + '\*.jpg'):
facefile.append(images)
print(facefile)
return facefile
class MainApp(MDApp):
def build(self):
Window.clearcolor = (1,1,1,1)
layout = GridLayout(cols=2, row_force_default=True, row_default_height=40,
spacing=10, padding=20)
self.val = TextInput(text="Enter name")
submit = Button(text='Submit', on_press=self.submit)
layout.add_widget(self.val)
layout.add_widget(submit)
return layout
def submit(self,obj):
print(self.val.text)
layout2 = GridLayout(cols=2, row_force_default=True, row_default_height=40,
spacing=10, padding=20)
name = self.val.text
f = functions.lookup(name)
print(f)
facefile = functions.load_images(f)
x = len(facefile)
print(x)
for i in range(0, x):
print(facefile[i])
self.img = Image(source=facefile[i])
self.img.size_hint_x = 1
self.img.size_hint_y = 1
self.img.pos = (200,100)
self.img.opacity = 1
layout2.add_widget(self.img)
return layout2
MainApp().run()
This is what I tried, but the window doesn't update. All images are returned (demonstrated by print(facefile[i])), but nothing happens with them. Any help would be massively appreciated
Your submit() method is creating Image widgets and adding them to a newly created layout2, You are returning that new layout2 from that method, but that does not add it to your GUI. Try replacing:
layout2.add_widget(self.img)
with:
self.root.add_widget(self.img)
In a Kivy/Python program, I have a class which contains a scrollview. I instantiate this class multiple times. The scrollview is bound to the on_scroll_stop event. My desired outcome is that the on_scroll_stop event will only fire within the instantiated class to which it belongs, but it seems that the event fires across all of the instantiated classes. Am I doing something wrong or is this expected behavior? Working sample below. In this example the "Left" section shows the issue most often, however the error is seen in the right section once you scroll up at the top or scroll down once reaching the bottom. To recreate scroll only one side, preferably the "Left" side. In my actual code which is far more complex the issue is much more prevalent.
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class DisplaySection(Widget):
def CreateSection(self):
self.display_panel_main_layout = BoxLayout(orientation='vertical')
self.elements_layout = FloatLayout(size_hint_y=None)
self.sv = ScrollView()
self.sv.bind(on_scroll_stop=self.on_scrolling_stop)
self.sv.add_widget(self.elements_layout)
self.display_panel_main_layout.add_widget(self.sv)
return self.display_panel_main_layout
def LoadElements(self,section_name):
self.section_name = section_name
number_of_elemements = 100
element_hieght = 40
layout_height = number_of_elemements * element_hieght
self.elements_layout.height = layout_height
xcord = 0
for x in range(number_of_elemements):
ycord = self.elements_layout.height - element_hieght*x
name = Label(text='Name' + str(x),size_hint_y=None, height=40,pos=(xcord,ycord))
self.elements_layout.add_widget(name)
def on_scrolling_stop(self, sv, value):
#print(sv,value)
print('I am',self.section_name)
class testscrollapp(App):
def build(self):
main_layout = BoxLayout()
section_names = ['Left','Right']
for x, section_name in enumerate(section_names):
section_layout = BoxLayout(orientation='vertical')
btn = Button(text=section_name,size_hint=(1,None))
section_layout.add_widget(btn)
# instantiate the class containing the scroll view
scroll_layout = DisplaySection()
scr = scroll_layout.CreateSection()
section_layout.add_widget(scr)
scroll_layout.LoadElements(section_name)
main_layout.add_widget(section_layout)
return main_layout
testscrollapp().run()
The on_scroll_stop event is dispatched just like touch events, so any ScrollView instance that subscribes to on_scroll_stop will get all the on_scroll_stop events. And just like a touch event, the subscribing ScrollView instance must determine which of the events it is interested in, and ignore the rest. Since the value argument to your on_scrolling_stop is the MouseMotionEvent, you can do a collide_point() test to determine if the scroll event is within the ScrollView:
def on_scrolling_stop(self, sv, value):
if sv.collide_point(*value.pos):
print('\tI am',self.section_name)
When pressing a key the pop-up window opens by pressing the button, it closes, but when the key is pressed again, calling the pop-up window gives an error
WidgetException('Cannot add %r, it already has a parent %r'
import json
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.popup import Popup
def read_json(file):
FileJson = open(file)
ObjJsom = json.load(FileJson)
return ObjJsom
data = read_json('Task.json')
counter = 0
task_Headline = data['Tasks'][counter]['Headline']
label = Label(text="Label test for StackoverFlow")
ConBox = BoxLayout(orientation="vertical")
clButt = Button(text="Close", size_hint=(1, 0.1))
ConBox.add_widget(label)
ConBox.add_widget(clButt)
def btn(instance):
show_popup(ConBox)
def show_popup(conten):
show = conten
popupWindow = Popup(title="Popup Window", content=show)
clButt.bind(on_press=popupWindow.dismiss)
popupWindow.open()
class Test(App):
def build(self):
butt = Button(text='Press')
butt.bind(on_press=btn)
return butt
if __name__ == '__main__':
Test().run()
When you create the Popup with:
popupWindow = Popup(title="Popup Window", content=show)
it adds the show to the Popup instance. That set the parent property of show. When the Popup is dismissed an a new Popup is created with the line above, it fails because the show instance still thinks its parent is the old Popup (even though it has been dismissed). And any widget can only have one parent. The fix is to remove the show instance from the old Popup like this:
def show_popup(conten):
# make sure the content has no parent
if conten.parent is not None:
conten.parent.remove_widget(conten)
show = conten
popupWindow = Popup(title="Popup Window", content=show)
clButt.bind(on_press=popupWindow.dismiss)
popupWindow.open()
So I am trying to create a program where you click on a button and it gives you a random task from a list of tasks you provide and another button to list all those tasks. So there is no errors in the code, the only problem is that when I run it, I want each button to call the same function but give different parameters depending on the i variable in the loop. Also I took out the button that gets the task since that is not in any way related to the problem.
GUI code:
#imports
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.textinput import TextInput
import Functions
import sys
import time
class Home(FloatLayout):
#function that setups the first page
def setup(self, obj):
self.clear_widgets()
List = Button(
text="Your List",
font_size=20,
size_hint=(1/6, 1/12),
pos_hint={"x":2.5/6, "y":1/6},
on_press = self.LIISST
)
self.add_widget(List)
#function that lists tasks
def LIISST(self, obj):
self.clear_widgets()
FUNC_ = Functions.Choose_("GE", 5)
FUNC_.sort()
LT = GridLayout(
cols=1,
spacing=10,
size_hint_y=None,
)
LT.bind(minimum_height=LT.setter('height'))
SCR = ScrollView(
size_hint=(1/3.5, 2/3),
pos=(self.width*137/384, self.height/3.25)
)
for i in range(len(FUNC_)):
but_ = Button(text=str(FUNC_[i]),
size_hint=(18/20, None),
height=40,
font_size=self.width/75,
on_press=lambda s:Functions.Choose_("DE", but_.text)
)
LT.add_widget(but_)
SCR.add_widget(LT)
ACC_ = Button(
text="Back",
font_size=20,
size_hint=(1/8, 1/14),
pos_hint={"x":3.5/8, "y":1/6},
on_press=self.setup
)
self.add_widget(SCR)
self.add_widget(ACC_)
def __init__(self, **kwargs):
super(Home, self).__init__(**kwargs)
Window.clearcolor = (255/255, 255/255, 255/255, 255/255)
self.setup(self)
class App_(App):
def build(root):
return Home()
if __name__ == '__main__':
App_().run()
Function to get the task: (separate file)
import random
import sys
def Choose_(vall_, VAAL_):
try:
#variables
cfile = open(r"Choice.txt", "r+")
cfile.seek(0)
dfile = open(r"Done.txt", "r+")
dfile.seek(0)
items = []
DON = []
#appenders for items in file to line
[items.append(line) for line in cfile]
[DON.append(line) for line in dfile]
stripp1 = [s.strip() for s in items]
stripp2 = [s.strip() for s in DON]
stripp1.sort()
stripp2.sort()
if vall_ == "DE":
print(VAAL_)
if vall_ == "GE":
return stripp1
sys.exit()
for s in stripp2:
if s in stripp1:
stripp1.remove(s)
if not stripp1:
dfile.seek(0)
dfile.truncate(0)
return False
sys.exit()
luck = random.randint(0, (len(stripp1)-1))
dfile.write(stripp1[luck])
dfile.write("\n")
return(stripp1[luck])
finally:
cfile.close()
dfile.close()
Task file (same directory as above codes):
ClIP STUDIO PAINT
CYBRARY (HACKING)
CYBRARY (LINUX)
VIRTUAL DJ
RASPBERRY PI
PACKET TRACER
VIRTUALBOX
PHOTOSHOP
BLENDER
SOLIDWORKS
KHAN ACADEMY (ANATOMY)
SOLOLEARN
UNITY
KHAN ACADEMY (ELECTRICAL)
PROGRAMMING
KHAN ACADEMY (PHYSICS)
ADOBE PREMIERE
Tasks already done(again, same directory as above files):
ClIP STUDIO PAINT
CYBRARY (HACKING)
CYBRARY (LINUX)
VIRTUAL DJ
RASPBERRY PI
PACKET TRACER
I expected the output to print the button's text for each different button, but it only texts the very last item in the task file for each button, which is virtualbox. Also the code sorts the tasks in abc order, which is why the last item is virtualbox.
Problem
I expected the output to print the button's text for each different
button, but it only texts the very last item in the task file for each
button, which is virtualbox.
I want each button to call the same function but give different
parameters depending on the i variable in the loop.
Root Cause
The text printed is always "VIRTUALBOX" because it is the last button added and it is referenced in the call by but_.text.
Solution
The following enhancements are required to solve the problem.
main.py
Implement a new method, callback()
Bind button's on_press event to the new method, callback()
Snippets - main.py
for i in range(len(FUNC_)):
but_ = Button(text=str(FUNC_[i]),
size_hint=(18 / 20, None),
height=40,
font_size=self.width / 75,
on_press=self.callback
)
LT.add_widget(but_)
SCR.add_widget(LT)
...
def callback(self, instance):
print(f"\ncallback: instance={instance}, text={instance.text}")
Functions.Choose_("DE", instance.text)
Output
I use a ListAdapter to dispaly data in a ListView.
How do I select an item in the ListView from code?
You can get your list items from ListAdapter using get_view() method. If list item is a ListItemButton then you can simulate pressing using trigger_action() method of ButtonBehavior mixin (ButtonBehavior is parent of Button and Button is parent of ListItemButton). This will also trigger on_selection_change event, so you might need a variable to distinguish this from normal selection. An example:
from kivy.uix.listview import ListView, ListItemButton
from kivy.uix.boxlayout import BoxLayout
from kivy.adapters.dictadapter import ListAdapter
from kivy.uix.button import Button
from random import randint
class MainView(BoxLayout):
def __init__(self, **kwargs):
kwargs['cols'] = 2
super(MainView, self).__init__(**kwargs)
self.orientation = 'vertical'
self.list_adapter = ListAdapter(data=["Item #{0}".format(i) for i in range(10)], cls=ListItemButton, sorted_keys=[])
self.list_adapter.bind(on_selection_change=self.selection_change)
list_view = ListView(adapter=self.list_adapter)
self.add_widget(list_view)
self.add_widget(Button(text="select random item", on_press=self.callback))
def callback(self, instance):
index = randint(0, 9)
self.change_from_code = True
if not self.list_adapter.get_view(index).is_selected:
self.list_adapter.get_view(index).trigger_action(duration=0)
self.change_from_code = False
def selection_change(self, adapter, *args):
if self.change_from_code:
print "selection change from code"
else:
print "selection changed by click"
if __name__ == '__main__':
from kivy.base import runTouchApp
runTouchApp(MainView(width=800))
The ListAdapter function handle_selection already handles this. It's not in the documentation, but it's in the code. All you need to know is the list item that you're looking for.
item = list_adapter.get_data_item(0)
list_adapter.handle_selection(item)
If you don't want to fire a on_selection_change event add True. This tells handle_selection not to fire the dispatch.
item = list_adapter.get_data_item(0)
list_adapter.handle_selection(item, True)