In my project , I use the MDSpinner class from KivyMD to show loading status of a widget. The code is as follows:
KV file( relevant part)
MDSpinner:
id: spinner
size_hint: None, None
size: dp(46), dp(46)
pos_hint: { 'center_x': .5, 'center_y': .9 }
active: True if app.processing else False
I have a Boolean property processing and the corresponding on_processing function as follows
class Mark2MarketApp(MDApp):
processing = BooleanProperty(defaultValue=False)
def __init__(self, **kwargs):
super().__init__(**kwargs)
start = time.time()
Builder.load_file("RootWidget.kv")
self.screen_manager = ScreenManager()
addMainScreen(self.screen_manager, self)
self.screen_manager.current = "Main"
self.current = "Main"
self.processing = False
self.manager_open = False
self.filePath = ""
self.symbol = []
self.qty = []
self.cost = []
self.side = []
Window.bind(on_keyboard=self.events)
self.file_manager = MDFileManager(
exit_manager=self.exit_manager,
select_path=self.select_path,
)
self.file_manager.ext = ['.csv', '.CSV', '.xlsx', '.XLSX']
self.popup = self.get_popup()
self.no_data_popup = self.no_data_popup()
end = time.time()
print('elapsed time for startup is %d seconds ' % (end - start))
def on_processing(self, instance, value):
print('instance, value', instance, value)
I set the processing variable before and after code that does some heavy lifting as follows
def gain_loss(self):
self.processing = True
try:
gl = self.screen_manager.get_screen('GainLoss')
gl.add_widgets()
except ScreenManagerException:
gl = GainLossScreen(self.screen_manager, name='GainLoss')
self.screen_manager.add_widget(gl)
gl.add_widgets()
if len(tryout.product_dict) == 0:
self.no_data_popup.open()
else:
self.screen_manager.current = 'GainLoss'
self.processing = False
However, the spinner doesnt get activated. I debugged the MDSpinner source code and it does call the activation.start method when processing is set to True and stops the animation when processing is set to false. But the spinner just doesnt spin!.
Interestingly, if I comment out the last line self.processing = False, then the spinner starts spinning .
Thanks in advance
Related
I am trying to make some instanced buttons that will trigger the 'on_toggle_button_state' function like the KV snippet below. I'm sure it's something simple I've overlooked, but this has stumped me for longer than I'd like to admit.
AttributeError: 'ToggleButtons' object has no attribute 'state'
class ToggleButtons(StackLayout):
def __init__(self, *args, **kwargs):
super().__init__(**kwargs)
conn = sqlite3.connect('players_db.db')
c = conn.cursor()
c.execute("DELETE FROM players WHERE (name IS NULL OR name = '')") ##Remove empty entries
c.execute("SELECT * FROM players")
records = c.fetchall()
records = list(filter(None, records))
for i in range(0, len(records)):
name = str(records[i])[2:-3]
b = ToggleButton(size_hint = (0.2, 0.2), text = name, on_state = self.on_toggle_button_state())
self.add_widget(b)
def on_toggle_button_state(self, widget):
print("toggle state: " + widget.state)
if widget.state == "normal":
widget.text = "OFF"
self.count_enabled = False
else:
widget.text = "ON"
self.count_enabled = True
KV that works for not-instanced buttons:
StackLayout:
ToggleButton:
text: "Charlie"
on_state: root.on_toggle_button_state(self)
size_hint: 0.2, 0.2
In you kv file use <ToggleButton>: to assign the on_state method with self.parent.on_toggle_button_state(self).
self is the dynamically instantiated button and parent is the ToggleButtons StackLayout where you have defined the on_toggle_button_state method.
I've modified your code a bit so I could run it without the SQL stuff. So ignore these changes.
The difference between ToggleButton and <ToggleButton> is that the first places an instance into the StackLayout - not what you want here.
And the bracket notation, defines methods & styles for each of your instances.
Please find the snippet below with the mentioned change:
from kivy.app import App
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
from kivy.lang import Builder
kv = '''
StackLayout:
<ToggleButton>:
text: "Charlie"
on_state:
self.parent.on_toggle_button_state(self)
size_hint: 0.2, 0.2
'''
Builder.load_string(kv)
class ToggleButton(Button):
pass
class ToggleButtons(StackLayout):
def __init__(self, *args, **kwargs):
super().__init__(**kwargs)
# conn = sqlite3.connect('players_db.db')
# c = conn.cursor()
# c.execute("DELETE FROM players WHERE (name IS NULL OR name = '')") ##Remove empty entries
# c.execute("SELECT * FROM players")
# records = c.fetchall()
# records = list(filter(None, records))
records = ["test1", "test2"]
for i in range(0, len(records)):
# name = str(records[i])[2:-3]
name = records[i] # just for testing
print(name)
b = ToggleButton(size_hint = (0.2, 0.2), text = name, on_state = self.on_toggle_button_state)
self.add_widget(b)
def on_toggle_button_state(self, widget):
print("toggle state: " + widget.state)
if widget.state == "normal":
widget.text = "OFF"
widget.count_enabled = False
else:
widget.text = "ON"
widget.count_enabled = True
class MyApp(App):
def build(self):
return ToggleButtons()
if __name__ == '__main__':
MyApp().run()
How do I increase the width of the dropdown list within a spinner? My button as you can see in the photo is small, and the values in my drop-down list do not appear completely. I did a little research on the internet and I could see that some people say that I need to create a class spinner and add these features. But I don't know how to do this. Could someone show me a code example of how I do this?
main.kv (simplified code)
...
Spinner:
id: spinnerrpi
size_hint: None, None
width: '30sp'
height: '30sp'
border: 0,0,0,0
background_normal: 'seta1.png'
background_down: 'seta2.png'
values: "Branco Neve","Banco Gelo","Amarelo","Rosa Claro","Bege"
on_text: app.spinner_rpiso(spinnerrpi.text)
...
main.py (simplified code)
...
class PrimeiraJanela(Screen):
pass
class GerenciadorDeJanelas(ScreenManager):
pass
class MainApp(App):
texture = ObjectProperty()
def build(self):
self.title = 'MyApp'
self.texture = Image(source = 'wave.png').texture
sm = ScreenManager()
sm.add_widget(PrimeiraJanela(name = 'primeira'))
sm.current = 'primeira'
return sm
def spinner_rpiso(self, value):
if (value=='Branco Neve'):
self.root.get_screen('primeira').ids.rpi.text = str('0.90')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Banco Gelo'):
self.root.get_screen('primeira').ids.rpi.text = str('0.70')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Amarelo'):
self.root.get_screen('primeira').ids.rpi.text = str('0.70')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Rosa Claro'):
self.root.get_screen('primeira').ids.rpi.text = str('0.60')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
if (value=='Bege'):
self.root.get_screen('primeira').ids.rpi.text = str('0.60')
self.root.get_screen('primeira').ids.spinnerrpi.text = ''
def exit(self):
App.get_running_app().stop()
aplicativo = MainApp()
aplicativo.run()
Here is an extension of Spinner that honors an option_width property:
class SpinnerWithOptionWidth(Spinner):
option_width = NumericProperty(0) # the new property
def __init__(self, **kwargs):
self.invisible_attacher = None
super(SpinnerWithOptionWidth, self).__init__(**kwargs)
def on_is_open(self, instance, value):
# This method is modified from Spinner
attacher = self
if value:
if self.option_width > 0:
if self.invisible_attacher is None:
# The DropDown is the same width as the widget it attaches to
# so make an invisible widget with the desired width
self.invisible_attacher = Widget(opacity=0, size_hint=(None, None))
self.add_widget(self.invisible_attacher)
self.invisible_attacher.pos = (self.center_x - self.option_width/2, self.y)
self.invisible_attacher.size = (self.option_width, self.height)
attacher = self.invisible_attacher
# open th DropDown
self._dropdown.open(attacher)
else:
if self._dropdown.attach_to:
if self.invisible_attacher:
self.remove_widget(self.invisible_attacher)
self.invisible_attacher = None
self._dropdown.dismiss()
I'm trying to build an interactive school timetable using Kivy. My logic is written inside Python and all layouts and the general estetic is written in Kivy files. I want to run a specific method right after the Kivy file has loaded so it changes the text of the buttons to display.
Here is how it looks after I start it:
Here is how I want my app to look like after I start it:
The names of days and lessons are in Polish .
Here is the code of planchart.py
class Okienko(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.lessons = []
f = open("/interaktywny_plan/classes.dat", "rb")
pon = pickle.load(f)
wto = pickle.load(f)
sro = pickle.load(f)
czw = pickle.load(f)
pt = pickle.load(f)
f.close()
self.lessons.append(pon)
self.lessons.append(wto)
self.lessons.append(sro)
self.lessons.append(czw)
self.lessons.append(pt)
self.lesson = ""
self.sub_press = ""
def on_release(self):
self.text = self.lesson
def on_press(self):
self.text = self.sub_press
def update_button(self):
if self.name_ == "one_pon":
self.lesson = self.lessons[0][0][0]
self.sub_press = self.lessons[0][0][1] + "\n" + self.lessons[0][0][2]
elif self.name_ == "two_pon":
self.lesson = self.lessons[0][1][0]
self.sub_press = self.lessons[0][1][1] + "\n" + self.lessons[0][1][2]
elif self.name_ == "three_pon":
self.lesson = self.lessons[0][2][0]
self.sub_press = self.lessons[0][2][1] + "\n" + self.lessons[0][2][2]
elif self.name_ == "four_pon":
self.lesson = self.lessons[0][3][0]
self.sub_press = self.lessons[0][3][1] + "\n" + self.lessons[0][3][2]
.............
Here is the Kivy code:
<Okienko>:
background_color: [.5, .9, 1, 1]
halign: "center"
size_hint: None, None
font_size: 24
size: 96, 96
on_press: self.on_press()
on_release: self.on_release()
<PlanChart>:
cols: 11
padding: 2
Dzien:
id: pon
text: "Pon"
Okienko:
id: one_pon
name_: "one_pon"
Okienko:
id: two_pon
name_: "two_pon"
Okienko:
id: three_pon
name_: "three_pon"
Okienko:
id: four_pon
name_: "four_pon"
Okienko:
id: five_pon
name_: "five_pon"
..............
I tried using #mainthread and putting self.update_button() in __init__ but it would still start empty. I tried just putting self.update_button() in __init__ but I would get AtributeError becouse the Kivy file was not loaded. After that I read about Clock module but I couldn't figure how to use it properly.
You can use the Clock module to schedule a method call for after the Kivy file is loaded:
from kivy.clock import Clock
Clock.schedule_once(self.update_button, .1)
I have added GridLayout to ScrollView, and I'm adding widgets in GridLayout dynamically from python program.Instead of using more space of window it's resizing height of older widgets.What am i doing wrong here?
I tried putting BoxLayout inside GridLayout but it's not working.
I also tried to add widgets directly to ScrollView but i found out ScrollView only supports one widget.
My kv code:
<Downloading>:
my_grid: mygrid
GridLayout:
cols: 1
size_hint_y : None
hight: self.minimum_height
id: mygrid
My python code:
class Downloading(ScrollView):
set_text = ObjectProperty()
my_grid = ObjectProperty()
def __init__(self, select, link, path, username, password):
self.select = select
self.link = link
self.path = path
self.username = username
self.password = password
self.p_bar = []
self.stat = []
self.parent_conn, self.child_conn = Pipe()
p = Process(target=main, args=(self.child_conn, self.select,
self.link, self.path,
self.username, self.password))
p.start()
super().__init__()
self.event = Clock.schedule_interval(self.download_GUI, 0.1)
def newFile(self, title):
# self.newId = "stat" + str(len(self.p_bar) + 1)
self.stat.append(Label(text=''))
self.p_bar.append(ProgressBar())
self.my_grid.add_widget(Label(text=title))
self.my_grid.add_widget(self.stat[-1])
self.my_grid.add_widget(self.p_bar[-1])
def download_GUI(self, a):
temp = self.parent_conn.recv()
print(temp)
if temp == "new":
self.downloading = True
return
if self.downloading:
self.newFile(temp)
self.downloading = False
return
if type(temp) == type({}):
self.complete = temp['complete']
if not self.complete:
status = "{0}//{1} # {2} ETA: {3}".format(temp['dl_size'],
temp['total_size'],temp['speed'],temp['eta'])
self.stat[-1].text = status
self.p_bar[-1].value = temp['progress']
return
if temp == "end":
self.event.cancel()
Clock.schedule_once(exit, 3)
I believe you just need to set the height for each widget that you add to your GridLayout. For example:
self.my_grid.add_widget(Label(text=title, size_hint=(1, None), height=50))
You might need to do the same for the other widgets you are adding. The GridLayout may give the initially added widgets more space than that, but will not squeeze them any any tighter than your specified height.
After applying following changes to kv file
<Downloading>:
size: self.size
my_grid: mygrid
GridLayout:
cols: 1
size_hint_y : None
row_default_height: '25dp'
row_force_default: True
id: mygrid
And adding this line in the end of constructor (__init__), it is working as expected.
self.my_grid.bind(minimum_height=self.my_grid.setter('height'))
I'm trying to use a custom widget within a layout in another application, but instead of scaling correctly within the layout, it's taking up the entire screen. Here's the .kv files, broken down to the relevant parts (I took out all the callbacks, which function as expected):
<NodeWidget>
BoxLayout:
orientation: 'horizontal'
size:root.size
ToggleButton:
size_hint: 0.10345, 1
group: 'Ends'
background_normal: 'Left_Up.png'
background_down: 'Left_Down.png'
Button:
size_hint: 0.801724, 1
background_normal: 'Center_Button_Up.png'
background_down: 'Center_Button_Down.png'
ToggleButton:
size_hint: 0.094827, 1
group: 'Ends'
background_normal: 'Right_Up.png'
background_down: 'Right_Down.png'
Below is the .kv file for the separate app that's importing the class from above and using it:
<NodeEditorWidget>:
GridLayout:
size: root.size
cols: 3
Label:
text: 'Test 1'
NodeWidget:
...
Label:
text: 'Test 3'
Here's what I see when I run the test app now:
Thanks in advance for your help!
So I solved this by moving it back to straight python, no .kv files. Here's the code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ListProperty, StringProperty, BooleanProperty, ObjectProperty, NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
#This widget contains two buttons
#The buttons each expose two events
#Names still aren't functioning
class NodeWidget(BoxLayout):
#We expose events:
#on_press when the central button is pressed
#on_release when the central button is released
#on_press_right when the right button is pressed
#on_press_left when the left button is pressed
press = BooleanProperty(False)
release = BooleanProperty(False)
press_right = BooleanProperty(False)
press_left = BooleanProperty(False)
release_right = BooleanProperty(False)
release_left = BooleanProperty(False)
#Properties exposed to set internal properties
title = StringProperty('')
left_background_normal = StringProperty('Left_Up.png')
left_background_down = StringProperty('Left_Down.png')
center_background_normal = StringProperty('Center_Button_Up.png')
center_background_down = StringProperty('Center_Button_Down.png')
right_background_normal = StringProperty('Right_Up.png')
right_background_down = StringProperty('Right_Down.png')
font_name = StringProperty('Roboto')
markup = BooleanProperty(True)
padding_x = NumericProperty(0)
padding_y = NumericProperty(0)
#Which node is active
active_left = BooleanProperty(False)
active_right = BooleanProperty(False)
#Object Properties for internal elements
left_button = ObjectProperty(None)
center_button = ObjectProperty(None)
right_button = ObjectProperty(None)
def __init__(self, **kwargs):
super(NodeWidget, self).__init__(**kwargs)
center = Button()
left = ToggleButton()
right = ToggleButton()
left.size_hint = (0.10345, 1)
center.size_hint = (0.801724, 1)
right.size_hint = (0.094827, 1)
left.background_normal = 'Left_Up.png'
left.background_down = 'Left_Down.png'
center.background_normal = 'Center_Button_Up.png'
center.background_down = 'Center_Button_Down.png'
right.background_normal = 'Right_Up.png'
right.background_down = 'Right_Down.png'
left.group = 'ends'
right.group = 'ends'
left.bind(on_press=self.ActivateNode_Left)
left.bind(on_release=self.ReleaseNode_Left)
right.bind(on_press=self.ActivateNode_Right)
right.bind(on_release=self.ReleaseNode_Right)
center.bind(on_press=self.PressNode)
center.bind(on_release=self.ReleaseNode)
self.left_button = left
self.center_button = center
self.right_button = right
self.bind(title=self.SetTitle)
self.bind(left_background_normal=self.SetLeftBackgroundNormal)
self.bind(left_background_down=self.SetLeftBackgroundDown)
self.bind(center_background_normal=self.SetCenterBackgroundNormal)
self.bind(center_background_down=self.SetCenterBackgroundDown)
self.bind(right_background_normal=self.SetRightBackgroundNormal)
self.bind(right_background_down=self.SetRightBackgroundDown)
self.bind(font_name=self.SetFontName)
self.bind(markup=self.SetMarkup)
self.bind(padding_x=self.SetPaddingX)
self.bind(padding_y=self.SetPaddingY)
self.add_widget(left)
self.add_widget(center)
self.add_widget(right)
def ActivateNode_Left(self, *args):
if self.active_left == False:
self.active_left = True
self.active_right = False
else:
self.active_left = False
self.active_right = True
if self.press_left == True:
self.press_left = False
else:
self.press_left = True
def ActivateNode_Right(self, *args):
if self.active_right == False:
self.active_right = True
self.active_left = False
else:
self.active_right = False
self.active_left = True
if self.press_right == True:
self.press_right = False
else:
self.press_right = True
def ReleaseNode_Left(self, *args):
if self.release_left == True:
self.release_left == False
else:
self.release_left == True
def ReleaseNode_Right(self, *args):
if self.release_right == True:
self.release_right == False
else:
self.release_right == True
def PressNode(self, *args):
if self.press == True:
self.press = False
else:
self.press = True
def ReleaseNode(self, *args):
if self.release == True:
self.release = False
else:
self.release = True
def SetTitle(self, *args):
self.center_button.text = self.title
def SetLeftBackgroundDown(self, *args):
self.left_button.background_down = self.left_background_down
def SetLeftBackgroundNormal(self, *args):
self.left_button.background_normal = self.left_background_normal
def SetCenterBackgroundDown(self, *args):
self.center_button.background_down = self.center_background_down
def SetCenterBackgroundNormal(self, *args):
self.center_button.background_normal = self.center_background_normal
def SetRightBackgroundDown(self, *args):
self.right_button.background_down = self.right_background_down
def SetRightBackgroundNormal(self, *args):
self.right_button.background_normal = self.right_background_normal
def SetFontName(self, *args):
self.center_button.font_name = self.font_name
def SetMarkup(self, *args):
self.center_button.markup = self.markup
def SetPaddingX(self, *args):
self.center_button.padding_x = self.padding_x
def SetPaddingY(self, *args):
self.center_button.padding_y = self.padding_y