Kivy Boxlayout Height Causes Nested Labels - python

I want to add a guide text to my app.
Should i use label for it?
There is a WrappedLabel Class Code to wrap all words inside label (only an additional information).
i think there is a Boxlayout height problem.
I create labels and add it to boxlayout then add this boxlayout to gridlayout.
I want whatever i add to labels, Kivy must show me a smooth boxlayot (not nested) so it will seen nice in gridlayout too.
Why there is a nested problem?
How can i fix this?
**** And another question if i had not used WrappedLabel Class how could i have filled the words in the label?
Thanks very much
below solutions did not work to fix height problem.
texts are nested
Labels have different heights so fixed height = 500 not worked.
box3 = BoxLayout(size_hint_y = None, orientation = 'vertical', height = self.minimum_height)
box3 = BoxLayout(size_hint_y = None, orientation = 'vertical', height = 500)
box3 = BoxLayout(size_hint_y = None, orientation = 'vertical')
PY Code:
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.image import Image
from kivy.uix.behaviors import ButtonBehavior
from kivy.clock import Clock
from kivy.uix.popup import Popup
from kivy.factory import Factory
from kivy.properties import ObjectProperty
import requests
# Pop Up
class PopupBox(Popup):
pop_up_text = ObjectProperty()
def update_pop_up_text(self, p_message):
self.pop_up_text.text = p_message
# Wrapped Label
class WrappedLabel(Label):
def __init__(self, **kwargs):
super(WrappedLabel, self).__init__(**kwargs)
self.bind(
width=lambda *x: self.setter('text_size')(self, (self.width, None)),
texture_size = lambda *x: self.setter('height')(self, self.texture_size[1]))
class Test(BoxLayout):
# Homepage Screen
def homepage(self, screenmanager):
screenmanager.current = 'homepage_screen'
Clock.schedule_once(self.clear_widgets)
# Pop Up
def show_popup(self):
self.pop_up = Factory.PopupBox()
self.pop_up.update_pop_up_text('Loading...')
self.pop_up.open()
def clear_widgets(self, *args):
for child in [child for child in self.ids.gridsonuc.children]:
self.ids.gridsonuc.remove_widget(child)
def underOver(self,screenmanager):
screenmanager.current = 'underover_screen'
self.show_popup()
Clock.schedule_once(self.clear_widgets)
Clock.schedule_once(self.underOver_hesaplama)
def underOver_hesaplama(self, *args):
print("""
Welcome to Under Over Goal Statics
""")
box3 = BoxLayout(size_hint_y = None, orientation = 'vertical', height = self.minimum_height)
one = WrappedLabel(text = '''
[color=#ff66c4][b]>>> WHAT IS FOOTBALL PREDICTOR? <<<[/b][/color]
''', font_name = 'Roboto', font_size = dp(20), halign='left', markup = True)
two = WrappedLabel(text = '''
1)Football Predictor is an application that calculates the goals per match ratio and winning percentage.
* Goals per match ratio calculation: Only the home results of the home teams and the away results of the away teams are used, so this algorithm allows us to estimate matches in a high success rate!
2) Football Predictor helps us to find valuable odds.
High odds means high payout and a corresponding low probability of occurring.
Low odds means low payout and a corresponding high probability of occurring.
If there is high odd bet and we know that it has a high probability of occurring, this is a valuable odd.
In this guide i am going to teach you how to find valuable odds.
3) Football Predictions are updated every night at 00:10 AM (UTC+3)
''', font_name = 'Roboto', font_size = dp(15), halign='left', markup = True)
three = WrappedLabel(text = '''
[color=#ff66c4][b]>>> FOOTBALL PREDICTOR'S ALGORITHM <<<[/b][/color]
''', font_name = 'Roboto', font_size = dp(20), halign='left', markup = True)
four = WrappedLabel(text = '''
1) Goals Per Match Ratio Algorithm : Average Goals Per Game Calculation!
(Goals scored by the Home team while playing at Home + Goals conceded by the Away team while playing Away ) / (Number of Home games played by the Home team + Number of Away games played by the Away team) + (Goals scored by the Away team while playing Away + Goals conceded by the Home team while playing at Home) / (Number of Home games played by the Home team + Number of Away games played by the Away team)
2) 1X2 Winning Percentage Algorithm : Home, Draw or Away Team's Winning Chance
Home Team's Winning Percentage:
(Number of matches won by the Home team at Home + Number of matches lost by the Away team at Away) / (Number of Home games played by the Home team + Number of Away games played by the Away team) * 100
Draw Percentage:
(Number of matches that Draw by the Home team at Home + Number of matches that Draw by the Away team at Away) / (Number of Home games played by the Home team + Number of Away games played by the Away team) * 100
Away Team's Winning Percentage:
(Number of matches won by the Away team at Away + Number of matches lost by the Home team at Home) / (Number of Home games played by the Home team + Number of Away games played by the Away team) * 100
''', font_name = 'Roboto', font_size = dp(15), halign='left', markup = True)
box3.add_widget(one)
box3.add_widget(two)
box3.add_widget(three)
box3.add_widget(four)
self.ids.gridsonuc.add_widget(box3)
self.pop_up.dismiss()
class StackoverflowApp(App):
def build(self):
return Test()
if __name__ == '__main__':
StackoverflowApp().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.underOver(sm)
background_color: 0, 0, 0, 0
Screen:
name: 'underover_screen'
BoxLayout:
spacing: '20dp'
orientation: 'vertical'
BoxLayout:
size_hint: 1, 0.10
Label:
size_hint: 1, 1
text: 'GUIDE'
font_size: '30dp'
color: 1, 0.4, 0.769, 1
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
<PopupBox>:
pop_up_text: _pop_up_text
background_color: '#38B6FF'
background: 'white'
size_hint: .5, .5
auto_dismiss: True
title: 'Data'
title_size: '15dp'
BoxLayout:
orientation: "vertical"
Label:
id: _pop_up_text
text: ''
font_size: '30dp'
color: 1, 0.4, 0.769, 1

One issue is that when you use a python statement like:
box3 = BoxLayout(size_hint_y = None, orientation = 'vertical', height = self.minimum_height)
the height = self.minimum_height part is evaluated when that python statement is executed, and the height is not updated later when children are added to the BoxLayout. To get it to update, you need to add a binding, or you could specify it in kv (where bindings are added for you automatically).
Also, I don't understand why you are adding your WrappedLabel instances to BoxLayouts and then adding those BoxLayouts to the GridLayout. Why not just add the WrappedLabels to the GridLayout?
Here are some changes that you can make to your code to get what you want:
First, redefine the WrappedLabel class like this:
# Wrapped Label
class WrappedLabel(Label):
pass
# def __init__(self, **kwargs):
# super(WrappedLabel, self).__init__(**kwargs)
#
# self.bind(
# width=lambda *x: self.setter('text_size')(self, (self.width, None)),
# texture_size=lambda *x: self.setter('height')(self, self.texture_size[1]))
and add a <WrappedLabel> rule to the kv:
<WrappedLabel>:
size_hint: None, None
text_size: [self.parent.width*.95, None] if self.parent else [1,1]
size: self.texture_size
This rule allows the WrappedLabel to expand vertically while keeping its width matching its parents width. The if/else construct just avoids exceptions being thrown before a parent is assigned.
Then, in your underOver_hesaplama() method, replace:
box3.add_widget(one)
box3.add_widget(two)
box3.add_widget(three)
box3.add_widget(four)
self.ids.gridsonuc.add_widget(box3)
with:
self.ids.gridsonuc.add_widget(one)
self.ids.gridsonuc.add_widget(two)
self.ids.gridsonuc.add_widget(three)
self.ids.gridsonuc.add_widget(four)
And since it is no longer used, you can eliminate:
box3 = BoxLayout(size_hint_y=None, orientation='vertical', height=self.minimum_height)

Related

Kivy: How do I nest a FloatLayout to cover only part of the window?

In my Kivy window, I have two sections of screen. On the left is a ScrollView with buttons, and the right side is empty space. When I click a button from the ScrollView, it is supposed to create a draggable label that can only be dragged in the right side of the screen. However, when I try to add the label to the empty part of the screen, it instead appears under the scrollbar in the bottom right.
My window is a FloatLayout, with the ScrollView and another FloatLayout to represent the empty space. I've tried giving size hints and position hints to describe the empty area, such that the nested FloatLayout only covers the empty space. When adding a draggable label to this layout, it seems to ignore it altogether, although my test_up function recognizes the labels as children.
Why is my nested FloatLayout still covering the whole screen? If the FloatLayout is correct, then why is the draggable label appearing outside of it?
Here is my code:
class DragLabel(DragBehavior, Label):
pass
class MyBox(FloatLayout):
def __init__(self):
super(MyBox, self).__init__()
def add_draggable(self, words):
lab = DragLabel(text = words)
self.play_area.add_widget(lab)
class KR(App):
def build(self):
self.game = MyBox()
return self.game
def test_up(self):
for _ in self.game.play_area.children:
print("There's a thing!")
print()
if __name__ == "__main__":
app = KR()
app.run()
and my KV file:
#:import win kivy.core.window
<DragLabel>:
size_hint: 0.1, 0.1
drag_rectangle: self.x, self.y, self.width, self.height
drag_timeout: 10000000
drag_distance: 0
on_touch_up: app.test_up()
<MyBox>:
scroll: scroll
screen: screen
play_area: play_area
FloatLayout:
id: screen
ScrollView:
id: scroll
size_hint: 0.2, None
size: win.Window.width, win.Window.height
GridLayout:
cols: 1
spacing: 10
size_hint_y: None
height: self.minimum_height
Button:
text: "This is a button"
size_hint_y: None
height: win.Window.height/10
on_press: root.add_draggable(self.text)
# More buttons go here
FloatLayout: # <---- This still covers the whole screen for some reason?
id: play_area
size_hint: 0.8, 1
pos_hint: {"x":0.2, "top":1}
I know something isn't working because I attempted to add a DragLabel to the ScrollView; and when I dragged out of the ScrollView, it disappeared. I want the label to do the same when it's dragged out of the empty space, but I can't seem to get it in there to begin with.
Any help is greatly appreciated!

Kivy: move the ScrollView to a certain y

I have a basic application and I need to move my ScrollView to a certain y.
I use scroll_to() but this time, I need to move to a certain position taking count of multiple informations. (I didn't find it in the doc)
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
BoxLayout:
ScrollView:
BoxLayout:
orientation: "vertical"
size_hint_y: None
height: "900dp"
MDLabel:
text: "yolo"
size_hint_y: None
height: "300dp"
MDLabel:
text: "yulu"
MDLabel:
text: "yulu"
MDLabel:
text: "yulu"
MDLabel:
text: "yulu"
MDLabel:
text: "yulu"
MDLabel:
text: "yulu"
'''
class App(MDApp):
def build(self):
self.box = Builder.load_string(KV)
return self.box
App().run()
Looks like you need the scroll_y parameter. This a percentage value indicating how far 'down' the scroll is.
From the docs:
a Y scrolling value, between 0 and 1. If 0, the content’s bottom side
will touch the bottom side of the ScrollView. If 1, the content’s top
side will touch the top side.
https://kivy.org/doc/stable/api-kivy.uix.scrollview.html#kivy.uix.scrollview.ScrollView.scroll_y
So if you set ScrollView.scroll_y = 0.5 the scroll box will scroll halfway down.
If you know the distance of your scroll in pixels you can always combine the above with convert_distance_to_scroll(dx, dy) which, (again from the docs):
Converts a distance in pixels to a scroll distance, depending on the
content size and the scrollview size.
https://kivy.org/doc/stable/api-kivy.uix.scrollview.html#kivy.uix.scrollview.ScrollView.convert_distance_to_scroll

Kivy garden graph: axes are drawn but plot is missing

Concerning the following code, which is a subset of my calculation game app, I have two questions:
1) Why is dummy_series not plotted, although the axes (with ticks and labels are drawn)
2) How do I get the quit button to properly exit the app (does it have to remove all widgets from the root widget? Is there an oposite to AppObject.run(), that stops the app? - SOLVED: App.get_running_app().stop()
For the first question, the relevant part of the code is found in the StatisticScreen and the PlotScreen classes. The first one creates the dummy_series, initializes the creation of the graph widget and changes the screen to the PlotScreen. In the PlotScreen class, there is showPlot methods, which is basically copied from the github README.
So far I tried to change the overall background color to white. Both by "canvas before" a white rectangle, and by really changing the background color of the window. Both had no effect (exept the axes and labels were hidden, because they are also white). Then I tried to color the graph differently each time I create it (taken from the same github repo, there is a TestApp in if __name__ == '__main__':). But there is still no graph.
For the second question please consider the changeScreen-method of CalculationRoot. If it is called with quit as the argument, currently it just empties the screen_list and returns False. The idea was to call the callback of the "Back"-Button (key=27,1000). Since closing the App with the "Back"-Button actually works, given the screen_list is empty, I thought I could use this existing process. Also scheduling the keyHandler-method of the app object CalculationApp does not have the desired effect of closing the app.
# Python build-in Modules
import os
import operator # better handling of +, #, *, etc.
import webbrowser # access homepages via the about section
import random # create random math questions
import datetime # for the timer
import itertools # eg for cycling colors
from functools import partial # schedule callback functions that take arguments different from 'dt'
# Kivy
from kivy.lang.builder import Builder
from kivy.app import App
from kivy.core.window import Window
from kivy.utils import platform
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty, NumericProperty, StringProperty
from kivy.storage.dictstore import DictStore
from kivy.utils import get_color_from_hex as rgb
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.clock import Clock
from kivy.uix.label import Label
from kivy.logger import Logger
from kivy.garden.graph import Graph, MeshLinePlot, SmoothLinePlot
# Non-standard python
import numpy as np
###############################################################################
# Constants
###############################################################################
BACKGROUND_COLOR = [0,0,0,0]
TEXT_COLOR = [1,1,1,1]
kv_string = """
#:import Factory kivy.factory.Factory
#:set color_button (0.784, 0.4, 0.216, 1) # brown
#:set color_button_pressed (0.659, 0.3, 0.431, 1) # darker brown
#:set color_background_down '(0.4, 0.4, 0.4, 1)' # purple
<WrappedLabel#Label>:
size_hint_y: None
height: self.texture_size[1] + self.texture_size[1]/2
markup: True
<GridLabel#Label>:
font_size: min(root.width, root.height) * .3
<StatisticsSpinner#Spinner>:
background_color: color_button if self.state == 'normal' else color_button_pressed
background_down: color_background_down
option_cls: Factory.get("SpinnerLabel")
<SpinnerLabel#SpinnerOption>:
background_color: color_button if self.state == 'down' else color_button_pressed
background_down: color_background_down
<CalculationRoot>:
orientation: 'vertical'
cg_screen_manager: cg_screen_manager
statistic_screen: statistic_screen
plot_screen: plot_screen
ScreenManager:
id: cg_screen_manager
StartScreen:
name: 'StartScreen'
StatisticScreen:
id: statistic_screen
name: 'StatisticScreen'
PlotScreen:
id: plot_screen
name: 'PlotScreen'
<StartScreen#Screen>:
BoxLayout:
orientation: 'vertical'
padding: root.width * .02, root.height * .02
spacing: min(root.width, root.height) * .02
WrappedLabel:
text: '[b] Calculation Game [/b]'
font_size: min(root.width, root.height) * .1
Button:
text: 'Statistic'
on_release: app.root.changeScreen(self.text.lower())
Button:
text: 'Quit'
<StatisticScreen#Screen>:
stats_operation_spinner: stats_operation_spinner
stats_difficulty_spinner: stats_difficulty_spinner
stats_num_questions_button: stats_num_questions_button
BoxLayout:
orientation: 'vertical'
padding: root.width * .02, root.height * .02
spacing: min(root.width, root.height) * .02
WrappedLabel:
text: '[b] Statistics [/b]'
halign: 'center'
font_size: min(root.width, root.height) * .1
GridLayout:
size_hint: .9,.4
cols: 2
pos_hint: {'center_x': .5}
GridLabel:
text: 'Operation Type'
StatisticsSpinner:
id: stats_operation_spinner
text: '+'
values: ['+', '-', '*', ':', '%']
GridLabel:
text: 'Difficulty'
StatisticsSpinner:
id: stats_difficulty_spinner
text: '1'
values: ['1','2','3','4']
GridLabel:
text: 'Number of Questions'
Button:
id: stats_num_questions_button
text: '8'
on_release: app.root.statistic_screen.switchNumQuestions(self, self.text)
# on_release: self.text = '16' if self.text == '8' else self.text = '8'
background_color: color_button if self.state == 'normal' else color_button_pressed
Button:
size_hint: 1, .2
text: 'Plot'
on_release: app.root.statistic_screen.showPlot()
font_size: min(root.width, root.height) * .1
<PlotScreen#Screen>:
"""
###############################################################################
# Widgets
###############################################################################
class StatisticScreen(Screen):
""" Selection screen, where you can fix the parameters for
a pot of your statistics.
"""
def __init__(self, *args, **kwargs):
super(StatisticScreen, self).__init__(*args, **kwargs)
def showPlot(self):
""" 'onPlotButtonPress'
callback for the 'plot'-Button on the bottom of StatisticScreen
"""
dummy_series = np.random.randint(1, 10, (12,))
App.get_running_app().root.ids.plot_screen.createKivyPlot(dummy_series)
App.get_running_app().root.changeScreen('plot')
def switchNumQuestions(self, instance, text):
""" 'onNumQuestionsButtonPress'
callback for the 'choose number of questions'-button.
"""
if int(text) == 16:
instance.text = '8'
else:
instance.text = '16'
class PlotScreen(Screen):
def __init__(self, *args, **kwargs):
super(PlotScreen, self).__init__(*args, **kwargs)
self.series = None
self.graph_figure = None # error if uncommented... why? (referred to former name self.canvas)
self.colors = itertools.cycle([rgb('7dac9f'), rgb('dc7062'), rgb('66a8d4'), rgb('e5b060')])
def createKivyPlot(self, series=np.array(range(12))):
graph_theme = {
'label_options': {
'color': rgb('444444'), # color of tick labels and titles
'bold': True},
'background_color': rgb('f8f8f2'), # back ground color of canvas
'tick_color': rgb('808080'), # ticks and grid
'border_color': rgb('808080')} # border drawn around each graph
self.graph_figure = Graph(xlabel='Last 12 games', ylabel='Average Response Time', x_ticks_minor=1,
x_ticks_major=5, y_ticks_major=1,
y_grid_label=True, x_grid_label=True, padding=10,
x_grid=True, y_grid=True, xmin=0, xmax=len(series), ymin=0, ymax=int(1.5*max(series)), **graph_theme)
plot = SmoothLinePlot(mode='line_strip', color=next(self.colors))
plot.points = [(x, series[x]) for x in range(0, len(series))]
self.graph_figure.add_plot(plot)
self.add_widget(self.graph_figure)
def destroy(self):
self.remove_widget(self.graph_figure)
self.graph_figure = None
###############################################################################
# Root Widget
###############################################################################
class CalculationRoot(BoxLayout):
""" Root of all widgets
"""
calculation_screen = ObjectProperty(None)
def __init__(self, *args, **kwargs):
super(CalculationRoot, self).__init__(*args, **kwargs)
self.screen_list = [] # previously visited screens
def changeScreen(self, next_screen):
if self.screen_list == [] or self.ids.cg_screen_manager.current != self.screen_list[-1]:
self.screen_list.append(self.ids.cg_screen_manager.current)
if next_screen == 'start':
self.ids.cg_screen_manager.current = 'StartScreen'
elif next_screen == 'statistic':
self.ids.cg_screen_manager.current = 'StatisticScreen'
elif next_screen == 'plot':
self.ids.cg_screen_manager.current = 'PlotScreen'
elif next_screen == 'quit':
self.screen_list == []
#self.onBackBtnPress() # not working
#Clock.schedule_once(partial(App.get_running_app().keyHandler(27)), 0) # not working
return False
def onBackBtnPress(self):
if self.screen_list:
self.ids.cg_screen_manager.current = self.screen_list.pop()
return True
return False
###############################################################################
# App Object
###############################################################################
class CalculationApp(App):
""" App object
"""
def __init__(self, *args, **kwargs):
super(CalculationApp, self).__init__(*args, **kwargs)
self.use_kivy_settings = False
def keyHandler(self, *args):
key = args[1]
print(key)
if key in (1000, 27):
return self.root.onBackBtnPress()
def post_build_init(self, ev):
if platform == 'android':
pass
win = self._app_window
win.bind(on_keyboard = self.keyHandler)
def build(self):
Builder.load_string(kv_string)
self.bind(on_start=self.post_build_init)
return CalculationRoot()
if __name__ in ('__main__', '__android__'):
CalculationApp().run()
Apparently, there has been a problem with using kivy.garden.graph inside a ScreenManager for some time. According to this issue report, it has been fixed in kivy version v1.10.1.dev0. However, I think you can get around it by adding _with_stencilbuffer=False to your call to Graph().
And to stop the app, you can modify your kv_string in the StartScreen section to include :
Button:
text: 'Quit'
on_release: app.stop()

How to Periodically Update Parent (Screen) Class UI from Child (BoxLayout) Class (Python with Kivy)

Goal:
Periodic update of parent (screen) class / UI from child (boxlayout) class. Theconf2.dat is occasionally updated (from various other screens), and I want the UI to update every 5 seconds or so by re-running this class.
Latest code update:
In the __init__ function, I have Clock.schedule_interval(self.create_button, 1), which should cause the create_button function to rerun every second.
At the top of the create_button function, I have self.box_share.clear_widgets(), which should clear all the widgets so they can be repopulated (per the instructions outlined further down the create_button function).
Action:
I run the code
I navigate to NoTags screen by clicking the button with text title 'updating sequence'
I make changes to buttons that were dynamically created under scrollview by clicking on them. They successfully change color. This information is written to the conf2.dat file.
I navigate to SequenceScreen screen by first clicking 'home' button, then clicking 'sequence display' button. This SequenceScreen screen is the screen I wish to have updated to reflect the changes made to conf2.dat file.
Result:
UI associated withSequenceScreen(Screen) class still does not update per changes made from UI associated with NoTags(Screen) class.
However, when I restart the app altogether, I find the SequenceScreen UI successfully updated.
Suspicion:
I'm just one line of code away from getting this UI to update properly.
Python Code:
from kivy.app import App
# kivy.require("1.10.0")
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.textinput import TextInput
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.properties import NumericProperty
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.uix.scrollview import ScrollView
#from kivy.base import runTouchApp
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.storage.dictstore import DictStore
import pickle
import datetime, threading
import time
from kivy.clock import mainthread
class BackHomeWidget(Widget):
pass
class SequenceBoxLayout_NoEdits(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(25):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)
self.box_share.add_widget(button_share)
#Clock.schedule_interval(self.parent.ids.updatedisplay.create_button(self, *args) , 1)
#self.parent.ids.updatedisplay.create_button(self, *args)
class SequenceBoxLayout_NoTag(BoxLayout):
box_share = ObjectProperty()
config_file = DictStore('conf2.dat')
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
df = pd.read_excel("Test.xlsx","Sheet1")
parts = df['parts'].values.tolist()
top_button_share = 1.1
color = (.4, .4, .4, 1)
for i in range(len(parts)):
top_button_share -= .4
id_ = "part" + str(i + 1)
if self.config_file.exists(id_):
btn_color = self.config_file[id_]["background_color"]
else:
self.config_file.put(id_, background_color=color)
btn_color = color
button_share = Button(background_normal='',
background_color=btn_color,
id=id_,
pos_hint={"x": 0, "top": top_button_share},
size_hint_y=None,
height=60,
font_size = 30,
text= str(i+1)+ ". " + str(parts[i]))
if self.parent.name == 'notags':
button_share.bind(on_press=self.update_buttons_notag)
self.box_share.add_widget(button_share)
def update_buttons_notag(self, button):
button.background_color = 0.86,0.54,0.04,1
self.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class SequenceScreen(Screen):
pass
class NoTags(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("updatelistexample.kv")
class MainApp(App):
def build(self):
return presentation
if __name__ == "__main__":
MainApp().run()
KV Code:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
NoTags:
SequenceScreen:
<SmallNavButton#Button>:
font_size: 32
size: 125, 50
color: 0,1,0,1
<BigButton#Button>:
font_size: 40
size_hint: 0.5, 0.15
color: 0,1,0,1
<BackHomeWidget>:
SmallNavButton:
on_release: app.root.current = "main"
text: "Home"
pos: root.x, root.top - self.height
<MainScreen>:
name: "main"
FloatLayout:
BigButton:
on_release: app.root.current = "notags"
text: "updating sequence"
pos_hint: {"x":0.25, "top": 0.4}
BigButton:
on_release: app.root.current = "sequence"
text: "sequence display"
pos_hint: {"x":0.25, "top": 0.7}
<AnotherScreen>:
name: "newgarage"
<NoTags>:
name: "notags"
SequenceBoxLayout_NoTag:
BackHomeWidget:
FloatLayout:
BigButton:
text: "Select Parts w/o Tags"
pos_hint: {"x":0.5, "top": 0.6}
background_normal: ''
background_color: (0.4,0.4,0.4,1)
<SequenceBoxLayout_NoEdits>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceBoxLayout_NoTag>:
box_share: box_share
ScrollView:
GridLayout:
id: box_share
cols: 1
size_hint_y: None
size_hint_x: 0.5
spacing: 5
padding: 130
height: self.minimum_height
canvas:
Color:
rgb: 0, 0, 0
Rectangle:
pos: self.pos
size: self.size
<SequenceScreen>:
name: "sequence"
SequenceBoxLayout_NoEdits:
id: updatedisplay
BackHomeWidget:
Credit:
Based on advice provided by #Tshirtman in the comments thread of the posted question...
Summary:
The problem with the code had to do with the fact that I had two different DictStore pointing to the same file, which was tripping up the communication between both classes.
The solution was to instead use only one DictStore and define that variable in the App class, then reference that particular variable in the child classes [using App.get_running_app()], like so:
Define config_file in App class:
class MainApp(App):
config_file = DictStore('conf2.dat')
def build(self):
return presentation
Reference App variable in child classes:
class SequenceBoxLayout_NoEdits(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoEdits, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_interval(self.create_button, 1)
def create_button(self, *args):
self.box_share.clear_widgets()
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
class SequenceBoxLayout_NoTag(BoxLayout):
...
def __init__(self, **kwargs):
super(SequenceBoxLayout_NoTag, self).__init__(**kwargs)
self.orientation = "vertical"
Clock.schedule_once(self.create_button)
def create_button(self, *args):
...
app = App.get_running_app()
...
for i in range(len(parts)):
...
if app.config_file.exists(id_):
btn_color = app.config_file[id_]["background_color"]
else:
app.config_file.put(id_, background_color=color)
...
...
def update_buttons_notag(self, button):
app = App.get_running_app()
...
app.config_file.put(button.id, background_color=(0.86,0.54,0.04,1))
Clock has a schedule_interval method, which works much like the schedule_once one, but will be called every n seconds instead of just once after n seconds.
So you could certainly just change that call in __init__ and start the create_button by calling self.box_share.clear_widgets() to remove widgets from the previous call before re-creating them.
This might be a bit wasteful, if you find yourself recreating a lot of widgets even if nothing changed, so you could add some logic to first check if the data didn't change, or even if it did, just reuse the old buttons if possible.
box = self.box_share
old_buttons = box.children[:]
box.clear_widgets()
# [your logic about computing the list of parts]
for i, p in enumerate(parts): # this is much better than doing range(len(parts))
# [the logic to get the content of the button]
if old_buttons: # check there are still buttons in the list of old buttons
btn = old_buttons.pop()
else:
btn = Button(
background_normal='',
background_color=btn_color,
pos_hint={"x": 0, "top": top_button_share},
# etc, all the things that are common to all your buttons
# but really, hardcoding them like this is a bit painful,
# you should use a subclass of button so you can style it
# in a kv rule so it would apply to all of them directly
)
btn.id=id_
btn.text = "{}. {}".format(i+1, p)
btn.pos_hint["top"] = top_button_share
# re-apply any other property that might have changed for this part
box.add_widget(btn)
But this logic is quite a common one, actually, and there are other things you can do to improve things in even more situations, though that's quite some work.
Thankfully, you are not the first one to need such thing, and we have been blessed with the wonderful RecycleView, which automates all of this and more, you just need to feed it a data directory, and it'll create the necessary widgets to fill the visible part of the scrollview if there is enough widgets to warrant scrolling, and automatically update when data changes, and when you scroll to see different parts of the list. I encourage you to check it, yourself. but the end result would certainly be something like.
<PartButton#Button>:
id_: None
part: ''
text: '{}. {}'.format(self.id, self.part)
<SequencedBoxLayout#Recycleview>:
parts: self.get_parts()
viewclass: 'PartButton'
data:
[
{
id_: i,
part: part
} for i, p in enumerate(self.parts or [])
]
RecycleBoxLayout:

How to change Attributes on a Pop Up to equal to the Attributes of the Buttons that opened said Pop Up?

My project is a card game. Each Card is also a Button. When you click on a card in play, a Pop Up opens with a Card Image, a Scrollable Label of Card Text, and various Buttons.
My current problem is trying to change the Image and Text in the Pop Up to match that of the actual Card Button information. I currently have the ability to change the information that shows up but only as the most recent definition of it. From what I understand this is because I'm redefining the class attribute, not an instance of the class attribute.
I've tried to do a def__init__(self, parameters): in the Card (Button Image) file, however this would crash anytime I tried to perform the draw method of the PlayMatWidget.
I think this may have something to do with Kivy Object Properties but I'm not entirely sure on how to use them for this exactly (largely since the buttons aren't created at runtime)
Thank you for reading!
Python File:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.properties import StringProperty
from kivy.properties import ObjectProperty
from kivy.lang import Builder
import random
Builder.load_string('''
<ScrollableLabel>:
Label:
size_hint_y: None
height: self.texture_size[1]
text_size: self.width, None
text: root.text
''')
class ScrollableLabel(ScrollView):
text = StringProperty('')
# Visual Representation of a Card or Deck
class ImageButton(ButtonBehavior, Image):
pass
class CardBackend:
def __init__(self, name, card_art, effect_one):
self.name = name
self.card_art = card_art
self.effect = effect_one
class Card(ImageButton):
# Defining the Popup Window
main_pop = Popup()
main_pop.title = 'Inspect Card'
main_pop.title_align = 'center'
main_pop.size_hint = None, None
main_pop.size = 400, 400
main_pop.auto_dismiss = False
main_pop.opacity = 0.8
# Variables or Variables to Be
card_text = 'ABCDEFGHIJK1234567890' * 100
card_art = Image(source='test.png', size_hint=(.9, .85), pos_hint={'x': -0.18, 'y': .125})
# Primary Child Widget
main_box = FloatLayout()
# Buttons
play_button = Button(text='Play', size_hint=(0.32, 0.1), pos_hint={'x': 0.01, 'y': 0.0})
discard_button = Button(text='Discard', size_hint=(0.32, 0.1), pos_hint={'x': 0.34, 'y': 0.0})
close_button = Button(text='Close', size_hint=(0.32, 0.1), pos_hint={'x': 0.67, 'y': 0.0})
close_button.bind(on_press=main_pop.dismiss)
# Scrolling Card Text Viewer
card_info = ScrollableLabel(text=card_text, size_hint=(0.45, .8), pos_hint={'x': .55, 'y': .15})
# Building main_pop.content
main_box.add_widget(play_button)
main_box.add_widget(discard_button)
main_box.add_widget(close_button)
main_box.add_widget(card_info)
main_box.add_widget(card_art)
main_pop.content = main_box
class PlayMatWidget(FloatLayout):
def draw(self):
# Create Backend
# Draw Backend
# Create Card() using Backend
# Defining Card Data (Removed for imported Decks)
backend_bewd = CardBackend('Blue Eyes White Dragon', 'bewd.png', 'One')
backend_dragon = CardBackend('Dragon Egg', 'test.png', 'Two')
backend_lava = CardBackend('Lava Golem', 'lavagolem.png', 'Three')
# Creating Card Button Objects
card_zero = Card()
card_one = Card()
card_two = Card()
# Defining Card Title
card_zero.main_pop.title = backend_lava.name
card_one.main_pop.title = backend_dragon.name
card_two.main_pop.title = backend_bewd.name
# Defining Card Text for Scrollview
card_zero.card_info.text = backend_bewd.effect
# Defining Card Art
card_zero.source = backend_bewd.card_art
card_one.source = backend_dragon.card_art
card_two.source = backend_lava.card_art
deck_dictionary = {
0: card_zero,
1: card_one,
2: card_two,
}
# Prototype Shuffle for top card
# drawing = random.choice(deck_list)
# Creating a Physical Card Object
# drawn_card = Card()
# OLD
# Applying Card Data from backend to front end Card Object
# drawn_card.main_pop.title = drawing.name
# drawn_card.source = drawing.card_art
# drawn_card.card_art.source = drawing.card_art
# drawn_card.card_info.text = drawing.effect
# Used to check somethings
for x in range(0, 3):
print(deck_dictionary[x].source)
print(deck_dictionary[x].main_pop.title)
self.ids.PlayerField.add_widget(random.choice(deck_dictionary), index=int(len(self.ids.PlayerField.children)/2))
class CardGameApp(App):
def build(self):
return PlayMatWidget()
if __name__ == "__main__":
CardGameApp().run()
Kivy File:
<Card#ImageButton>:
size_hint: 0.8,0.8
pos_hint: {'x':0, 'y':0.1}
on_press: print('confirming press')
on_press: root.main_pop.open()
<PlayMatWidget>:
id: PlayMat
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'tron1.png'
StackLayout:
id: OppHand
size_hint: 0.6, 0.1
pos_hint: {'x':0.2,'y': 0.9}
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'background.png'
StackLayout:
id: OppField
size_hint: 0.6, 0.4
pos_hint: {'x':0.2, 'y':0.5}
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'background.png'
StackLayout:
id: cards
size_hint: 0.6,0.4
pos_hint: {'x':0.2, 'y':0.1}
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'background.png'
AnchorLayout:
id: PlayerFieldAnchor
anchor_x: 'center'
anchor_y: 'center'
size_hint: 0.6,0.4
pos_hint: {'x':0.2, 'y':0.1}
canvas.before:
Rectangle:
size: self.size
pos: self.pos
source: 'background.png'
BoxLayout:
id: PlayerField
orientation: 'horizontal'
StackLayout:
id: PlayerHand
orientation: 'lr-tb'
size_hint: 0.6,0.1
pos_hint: {'x': 0.2, 'y':0}
canvas:
Rectangle:
size: self.size
pos: self.pos
source: 'background.png'
ImageButton:
id: PlayerDeck
source: 'cardback.png'
size_hint: 0.15, 0.15
pos_hint: {'x':0.025,'y':0.05}
on_press: root.draw()
1/ everytime you override an __init__ method, you need to call the super() method, if you don't do so for kivy widgets, they will crash, because they won't be initialised correctly, for python 3 i believe you want to do
def __init__(self, **kwargs):
super().__init__(**kwargs)
…
2/ usually in kivy, it's easier to create a property and pass it by name, instead of passing parameters to init, this way you don't even need to override __init__, avoiding the error in 1/.
class CardBackend:
name = StringProperty()
card_art = StringProperty()
effect = stringProperty()
…
card = CardBackend(
name='Blue Eyes White Dragon',
card_art='bewd.png',
effect='One')
or, to use that in kv
CardBackend:
name: 'Blue Eyes White Dragon'
card_art: 'bewd.png'
effect: 'One'
3/ in Card you define indeed everything in the class instead of the __init__, which means this code will be ran when the class is defined, not when an instance is created, maybe you actually want a function, not a class? or maybe you want to override the on_press method of your ImageButton, and put all of this code in it, using the properties of the card instead of the hardcoded values you use currently.
I solved it. I moved the creation of the Pop Up Box into a Function with the Card Class. In the kivy file, I bound the inspect_card() function to the on_press event of the Kivy Card Button.
Previously the Pop Up was being defined at the creation of the card. So the Pop Up would always have the information correspond to the latest created card, regardless of which card button I clicked.
Moving it to a function meant that the Pop Up is only ever created when that function is initiated, so all of the relevant information corresponds to the source information of the Button that called it. This way it doesn't matter whether the instance is inside the class or not because it will be redefined correctly every time it runs.
Success Video
Card Class w/Function:
class Card(ImageButton):
# Defining the Popup Window
def inspect_card(self):
main_pop = Popup()
main_pop.title = 'Inspecting ' + self.cardname
main_pop.title_align = 'center'
main_pop.size_hint = None, None
main_pop.size = 400, 400
main_pop.auto_dismiss = False
main_pop.opacity = 0.8
# Variables or Variables to Be
card_text = self.effecttext
card_art = Image(source=self.source, size_hint=(.9, .85), pos_hint={'x': -0.18, 'y': .125})
# Primary Child Widget
main_box = FloatLayout()
# Buttons
play_button = Button(text='Play', size_hint=(0.32, 0.1), pos_hint={'x': 0.01, 'y': 0.0})
discard_button = Button(text='Discard', size_hint=(0.32, 0.1), pos_hint={'x': 0.34, 'y': 0.0})
close_button = Button(text='Close', size_hint=(0.32, 0.1), pos_hint={'x': 0.67, 'y': 0.0})
close_button.bind(on_press=main_pop.dismiss)
# Scrolling Card Text Viewer
card_info = ScrollableLabel(text=card_text, size_hint=(0.45, .8), pos_hint={'x': .55, 'y': .15})
# Building main_pop.content
main_box.add_widget(play_button)
main_box.add_widget(discard_button)
main_box.add_widget(close_button)
main_box.add_widget(card_info)
main_box.add_widget(card_art)
main_pop.content = main_box
main_pop.open()
Kivy Card Button with new binding and referenced properties for the Pop Up
<Card#ImageButton>:
size_hint: 0.8,0.8
pos_hint: {'x':0, 'y':0.1}
cardname: 'stuff'
effecttext: 'ABCDEFGHIJKL123456789' * 30
on_press: print('confirming press')
on_press: root.inspect_card()
(Prop Values listed are default, I change them below in the MainGameWidget before their brought to the field. I'll be moving most of this to the Deckbuilder functionality later)

Categories

Resources