I was making a program similiar to exel.
And then I ran into a issue.
StackLayout stacks good, but because the size of inputs is static it leave a blank space in some cases.
I try to do like size=(self.width/5, self.height/5).
If someone saw how exel look now that there are more inputs of the screen this is why I use ScrollLayout and I want only 5 inputs in one row(user can change it in display settings(I will do it then creating rest of UI))
Here is what I had tried for now.
main.py:
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.textinput import TextInput
class EditorGrid(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
size = dp(self.width / 5)
print(size)
for i in range(0, 100):
b = TextInput(text=str(i + 1), size_hint=(None, None), size=(dp(size), dp(size)))
self.add_widget(b)
def on_size(self, *args):
self.size = dp(self.width/5)
print(self.size)
pass
class pxApp(App):
pass
if __name__ == "__main__":
pxApp().run()
kivy file:
Scrolling:
<Scrolling#ScrollView>
do_scroll_x: True
do_scroll_y: True
EditorGrid:
size_hint: 1, None
height: self.minimum_height
<EditorGrid>:
There is next problem that in init window size is 100x100.
I recommend using setters in binding under kv script. If you want to apply it in python, you could use a binding function for each button.
b = TextInput(.......) #omit size_hint and size attributes
#the following function is called every time EditorGrid(self) size_hint changes when the function is bind
#The function could be defined in a outer scope.
#Look at kivy callback functions to better understand the syntax
binding_function = lambda ins, *a: setattr(ins, 'size_hint', tuple((dim/5 for dim in self.size_hint))
b.bind(size_hint=binding_function)
Related
I have a GridLayout with 8 cols, and I add 64 Buttons. (so 8x8).
I want the Buttons to ALWAYS be quadratic, so I made that in my spot_init() function.
That all works great. When I make the Window smaller or bigger, the rest of my Screen gets black and the GridLayout stays in the Corner. But I wanted it to be centered.
For leftright that works perfectly fine but when i try applying that to updown as well, it does some weird things, I really cannot explain.
Some things I (maybe) found out:
When I do it exactly like right now, but in the code, the Y coord is 3 times as high as it should be for some reason.
When I then divide it by 3, it gets 7 times as high...
It doesn't change if I do it in .kv or in .py file
Moving GridLayout without RelativeLayout also doesn't work (almost the same thing happens)
Other askers seemed to have the same problem (or a similiar one) but their fixes didn't help me.
My .kv file:
RMainBoard:
<MainBoard>:
cols:8
# height: self.minimum_height
# size_hint_y: None
# size_hint_x: None
<RMainBoard#RelativeLayout>:
pos:(self.width/2-min(self.width/8,self.height/8)*4,self.height/2-(min(self.width/8,self.height/8))*4)
MainBoard:
My .py file:
#resize window (NEEDS TO BE AT VERY TOP)
from kivy.config import Config
Config.set('graphics', 'width', '600')
Config.set('graphics', 'height', '600')
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.metrics import dp
from kivy.properties import NumericProperty
class MainBoard(GridLayout):
spots = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.spot_init()
def on_size(self,*args):
for spot in self.spots:
spot_size = min(self.width/8,self.height/8)
print(min(self.width/8,self.height/8))
spot.height = spot_size
spot.width = spot_size
def spot_init(self):
for i in range(0,64):
self.spots.append(Button(size_hint=(None,None),height=self.height/8,width=self.width/8))
self.add_widget(self.spots[i])
class TestApp(App):
pass
TestApp().run()
Thanks a lot <3
Rather than writing your own code to position the GridLayout, you can use the kv language to accomplish that. Here is a modified version of your code that does this:
# resize window (NEEDS TO BE AT VERY TOP)
from kivy.config import Config
from kivy.lang import Builder
Config.set('graphics', 'width', '600')
Config.set('graphics', 'height', '600')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
kv = '''
RMainBoard:
<RMainBoard#RelativeLayout>:
MainBoard:
cols: 8
size_hint: None, None
# make MainBoard square
width: min(root.width, root.height)
height: self.width
# position MainBoard in center of RMainBoard
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
<SquareButton>:
size_hint: None, None
# make the Button square
width: min(self.parent.width/8, self.parent.height/8) if self.parent else 100
height: self.width
'''
class SquareButton(Button):
pass
class MainBoard(GridLayout):
spots = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.spot_init()
def spot_init(self):
for i in range(0, 64):
self.spots.append(SquareButton(text=str(i)))
self.add_widget(self.spots[i])
class TestApp(App):
def build(self):
return Builder.load_string(kv)
TestApp().run()
Note that I included the kv as a string in the python code. That was just for my own convenience, and it could just as well be in your kv file.
I have defined a SquareButton class along with a <SquareButton> rule in the kv that keeps the SquareButton square. The Buttons created in the spot_init() method are now SquareButtons.
I am trying to create some themes in kivy. My program currently has 4 classes/screens. I have manged to be able to change the background colour of all the screens if a condition is met. I have tried to change the colour of all the text input's so intead of white, they are black. This is my code so far
Python:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import *
from kivy.core.window import Window
from kivy.uix.textinput import TextInput
class WeatherRoot(ScreenManager, BoxLayout):
pass
class RegisterPage(Screen, BoxLayout):
pass
class WeatherApp(App):
pass
def iii(time):
print("h")
x = 1
if x == 1:
Window.clearcolor = (1, 1, 1, 0)
TextInput.background_color = (1,1,1,0)
pass
if __name__ == "__main__":
Clock.schedule_once(iii)
WeatherApp().run()
Kivy:
WeatherRoot:
<WeatherRoot>:
RegisterPage:
<RegisterPage>:
BoxLayout:
padding: [100, 50, 100, 50]
TextInput:
font_size: 30
TextInput:
font_size: 30
The code to change the TextInput colour isn't working.
The background is white but the text-input isn't black as you can see
How would I be able to change the colour of the TextInput and properties widgets in general (e.g. the text colour of all labels), if a condition is met, with python code?
Thanks in advance
Note - It doesn't work with foreground_color or any of the colour settings like that.
It seems that the concept of object/instance and classes does not differ. By using TextInput.background_color = (1,1,1,0) you add or modify the "background_color" property of the TextInput class, not of the objects/instances created based on the TextInput class.
If you want to modify the property of the TextInputs (a.k.a instances/objects created based on the TextInput class) you must access those objects using the kivy methods through the parents:
def iii(time):
x = 1
if x == 1:
Window.clearcolor = (1, 1, 1, 0)
root = App.get_running_app().root # WeatherRoot instance
screen = root.screens[0] # RegisterPage instance
box_layout = screen.children[0] # BoxLayout instance
for child in box_layout.children: # childs of box_layout
if isinstance(child, TextInput): # verify that the child is a TextInput
child.background_color = (1,1,1,0)
I think you have extrapolated that in the case of Window it behaves similar to TextInput, but they are not the same since the first one is an instance of the WindowBase class, it is not a class unlike the second.
The Goal
I want to create a small script that adds buttons dynamically, but still lets me perform functions on specific ones via root.
My Methods
I made this script.
It is capable of dynamically adding large buttons along the top. Each of these buttons slightly changes its own color when pressed.
It has two smalls buttons at the bottom. The first button dynamically adds new large buttons along the top.
The second button resets the color of the first large button on the top.
My Code
#!/usr/bin/env python3
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
Builder.load_string('''
<RootWidget>:
Button:
text: 'Add'
size_hint: (None, None)
size: (40, 40)
pos: (40, 40)
group: 'action'
on_press: root.createNextTarget()
Button:
text: 'res'
size_hint: (None, None)
size: (40, 40)
pos: (100, 40)
group: 'action'
on_press: root.resetTarget()
''')
class RootWidget(FloatLayout):
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
#note: self.ids isn't populated yet. I guess we can't use it yet.
self.createNextTarget()
def resetTarget(self):
f_target = self.ids['targetbutton0']
f_target.background_color = (1.0, 1.0, 1.0, 1.0)
return True
def countTargets(self):
return [str(x.__class__.__name__) for x in self.children if x != None].count('TargetButton')
def createNextTarget(self):
f_nextButton = TargetButton(id="targetbutton"+str(self.countTargets()),
size_hint=(None, None),
pos=(80 + (10 + 60) * self.countTargets(), 100),
size=(60, 60),
background_normal = '',
background_color = (1, 1, 1, 1),
group = 'target')
self.add_widget(f_nextButton)
f_nextButton.bind(on_press=TargetButton.lowerAllRGB)
class TargetButton(Button):
def __init__(self, **kwargs):
super(TargetButton, self).__init__(**kwargs)
def lowerAllRGB(self):
f_r, f_g, f_b, f_a = self.background_color
if f_r >= 0.1: f_r = f_r - 0.1
if f_g >= 0.1: f_g = f_g - 0.1
if f_b >= 0.1: f_b = f_b - 0.1
self.background_color = (f_r, f_g, f_b, f_a)
return True
class TestApp(App):
def build(self):
return RootWidget()
def on_stop(self):
print("TestApp.on_stop: finishing", self.root.ids)
if __name__ == '__main__':
TestApp().run()
The Problem
If I try to hit the reset button (that accesses the widget via root.ids), I get the error: KeyError: 'targetbutton0'
After finding a post about a similar problem, I thought root.ids just wouldn't work during RootWidget.__init__.
But when I use the button to add buttons after RootWidget.__init__ is finished, TestApp.on_stop() still prints:
TestApp.on_stop: finishing {}
So root.ids is still empty, and doesn't seem to include any dynamically added widgets despite me assigning an id attribute to each of them.
My questions to you
Given the way I am dynamically adding widgets, is using root.ids just worthless for my purposes?
Is there a decent way for me to access my widgets via id? I saw another question here asking something similar. But it didn't answer my question about dynamically added widgets.
Question 1 - root.ids / self.ids
Given the way I am dynamically adding widgets, is using root.ids just
worthless for my purposes?
Answer
id assigned to dynamically added widgets are not store in self.ids or root.ids. Therefore, you cannot access dynamically added widgets using self.ids['targetbutton0'] or self.ids.targetbutton0. If you do that, you will get a KeyError because it is not found in self.ids which is a dictionary type property.
When your kv file is parsed, Kivy collects all the widgets tagged with id’s and places them in this self.ids dictionary type property.
Note:
These type of id (i.e. id assigned to dynamically created widget) is deprecated and will be removed in a future Kivy version.
[WARNING] Deprecated property "<StringProperty name=id>" of object "<kivy.uix.button.Button object at 0x7feeec0968d0>" has been set, it will be removed in a future version
Question 2
Is there a decent way for me to access my widgets via id?
Solution
You could create your own list of ids of dictionary type property.
Snippets
from kivy.properties import DictProperty
class RootWidget(FloatLayout):
dynamic_ids = DictProperty({}) # declare class attribute, dynamic_ids
def __init__(self, **kwargs):
super(RootWidget, self).__init__(**kwargs)
self.createNextTarget()
def resetTarget(self):
f_target = self.dynamic_ids['targetbutton0']
f_target.background_color = (0.0, 1.0, 1.0, 1.0) # cyan colour
return True
...
def createNextTarget(self):
id = "targetbutton" + str(self.countTargets())
f_nextButton = TargetButton(id=id,
size_hint=(None, None),
pos=(80 + (10 + 60) * self.countTargets(), 100),
size=(60, 60),
background_normal = '',
background_color = (1, 1, 1, 1), # white colour
group = 'target')
self.add_widget(f_nextButton)
self.dynamic_ids[id] = f_nextButton
f_nextButton.bind(on_press=TargetButton.lowerAllRGB)
Output
Kivy 2.0 throws an error when you use id='value' when dynamically creating kivy widgets from python main file. But by using weak references you achieve the following success.
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
import weakref
class MyLayout(Widget):
def use_weakref_id_to_replace_text(self, *args):
self.ids.AddUserTextBox.text = "shotta"
print("released and renamed the text to shotta")
def add_textinpt_with_weak_ref_dyn_id(self, *args):
print('Pressed and added text input box')
textinput = TextInput(pos=(380,380),text='123')
self.add_widget(textinput)
# We'll use a weak ref to add our dynamic id
self.ids['AddUserTextBox'] = weakref.ref(textinput)
class MdApp(App):
def build(self):
root = MyLayout()
Btn = Button(size=(250,250), pos=(100,100),text="Dynamic id assign n use id to rename")
Btn.bind(on_release=root.use_weakref_id_to_replace_text,on_press=root.add_textinpt_with_weak_ref_dyn_id)
root.add_widget(Btn)
return root
if __name__ == '__main__':
MdApp().run()
I'm something of a newbie to Python and Kivy. After making some progress, I've hit a brick wall and no amount of internet searching can find an answer.
I have a python/kivy script which starts with a GridLayout selection menu. Then I would like to "Click Next" and replace the GridLayout with a BoxLayout to display the output. The Python script is:
import sys
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.gridlayout import GridLayout
from kivy.uix.pagelayout import PageLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import StringProperty, ListProperty, DictProperty
from kivy.uix.widget import Widget
from brp_stats import *
from dice_roller import *
#from display import *
race = ''
statblock = ''
class Test(GridLayout):
def printcharacter(self,my_sb,my_cr,my_scm,my_scb):
printable_stats = print_stats(my_sb)
printable_rolls = print_rolls(my_cr)
printable_scm = print_scm(my_scm)
printable_scb = print_scb(my_scb)
self.clear_widgets()
self.add_widget(Label(text_size=(300, None),
text='Stats\n' + str(printable_stats)))
self.add_widget(Label(text_size=(300, None),
text='Rolls\n' + str(printable_rolls)))
self.add_widget(Label(text_size=(300, None),
text='SCM\n' + str(printable_scm)))
self.add_widget(Label(text_size=(300, None),
text='SCB\n' + str(printable_scb)))
wayout = Button(text='Way Out')
self.add_widget(wayout)
wayout.bind(on_press=self.canvas.clear)
# def bar():
# print ("BAR")
def human(self,a,b):
if b==True:
self.Status="human"
race=self.Status
statblock = human()
characteristic_rolls = rolls(statblock)
skill_category_modifiers = scm(statblock)
skill_category_bonuses = scb(statblock)
# TestApp.foo()
# Test.bar()
Test.printcharacter( \ self,statblock,characteristic_rolls,skill_category_modifiers,skill_category_bonuses)
class TestApp(App):
# def foo():
# print ("FOO")
def build(self):
self.title="BRP Character Generator"
return Test()
#### MAIN CODE ####
if __name__ == '__main__':
TestApp().run()
And the KV script is
<Test>:
cols: 2
canvas:
Color:
rgb: .2,.2,.2
Rectangle:
pos: self.pos
size: self.size
Label:
text: 'Human'
CheckBox:
group: 'race_group'
on_active: root.human(*args)
Button:
text: 'Next'
on_press: printcharacter()
What's happening is I select an option (Human in this example). It should wait until I click Next before displaying the results. However, as soon as I select Human, it immediately prints the result, in a GridLayout. Two questions spring to mind, which I'm hoping the experts here can help with:
1) Why does the Select screen not wait until I have clicked Next before displaying the results?
2) How do I swap the layout from Grid to Box when I display the second screen?
Any pointers or suggestions would be gratefully received.
Regards,
Colin
Your Checkbox calls Test.human(*args) when it is active (pressed/selected), and the last line of Test.human() calls Test.printcharacter() which explains why it is printing before you hit the 'Next' button.
Since your Test class inherits from GridLayout the widgets you add to a root Test layout will be added as if to a GridLayout. There are several ways to handle switching the layout to a BoxLayout, one of which would be to not inherit directly from a particular Layout class and instead add/remove different Layout widgets to Test as needed.
For example, Test could start off with a GridLayout child widget that is the parent of the Label, CheckBox, and Button widgets. Then when you want to switch layouts for printing your results, you clear those widgets and start by adding a BoxLayout to Test.
I want to generate a dropdown-list in my second screen managed by Kivys ScreenManager. If I do so, I get this traceback:
...
File "C:/Users/ORANG/PycharmProjects/waldi/playground/cw.py", line 76, in on_text
instance.drop_down.open(instance)
File "C:\Kivy-1.9.0-py2.7-win32-x64\kivy27\kivy\uix\dropdown.py", line 215, in open
'Cannot open a dropdown list on a hidden widget')
kivy.uix.dropdown.DropDownException: Cannot open a dropdown list on a hidden widget
This is the code, which is basically the same as in this
example, just embedded in a screenmanager scenario simple as can be:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty, StringProperty
import re
from kivy.lang import Builder
Builder.load_string('''
<Lieferant>:
ComboLayout:
Label:
text: 'Label'
ComboEdit:
size_hint: .5, .3
pos_hint: {'center':(.5, .5)}
# `args` is the keyword for arguments passed to `on_text` in kv language
on_text: self.parent.on_text(self, args[1])
''')
class ComboEdit(TextInput):
"""
This class defines a Editable Combo-Box in the traditional sense
that shows it's options
"""
options = ListProperty(('',))
'''
:data:`options` defines the list of options that will be displayed when
touch is released from this widget.
'''
def __init__(self, **kw):
ddn = self.drop_down = DropDown()
ddn.bind(on_select=self.on_select)
super(ComboEdit, self).__init__(**kw)
def on_options(self, instance, value):
ddn = self.drop_down
# clear old options
ddn.clear_widgets()
for option in value:
# create a button for each option
but = Button(text=option,
size_hint_y=None,
height='36sp',
# and make sure the press of the button calls select
# will results in calling `self.on_select`
on_release=lambda btn: ddn.select(btn.text))
ddn.add_widget(but)
def on_select(self, instance, value):
# on selection of Drop down Item... do what you want here
# update text of selection to the edit box
self.text = value
class ComboLayout(BoxLayout):
rtsstr = StringProperty("".join(("Substrate1,,,Substrate1,,,Substrate1,,,",
"Substrate1,,,Substrate1,,,Substrate_coating",
",,,silicon,,,silicon_Substrate,,,substrate_",
"silicon,,,")))
def on_text(self, instance, value):
if value == '':
instance.options = []
else:
match = re.findall("(?<=,{3})(?:(?!,{3}).)*?%s.*?(?=,{3})" % value, \
self.rtsstr, re.IGNORECASE)
# using a set to remove duplicates, if any.
instance.options = list(set(match))
instance.drop_down.open(instance)
class Intro(Screen):
pass
class Lieferant(Screen):
pass
class CWApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(Intro(name='Intro'))
sm.add_widget(Lieferant(name='Lieferant'))
return sm
if __name__ == '__main__':
CWApp().run()
Is it possible to combine them? How would you do this?
This code is running if I just comment this line out, which adds a screen before the screen with the dropdown:
sm.add_widget(Intro(name='Intro'))
Ok - for this question I got the "Tumbleweed Badget" - LOL
I admit it's a very special one and makes obvious, how much I am a beginner here. So I asked another question based on the same problem and spend some effort to make it easier to understand the code and to reproduce it. This paid off! Look here for the solution if you tumble once into that kind of problem: Is it a bug in Kivy? DropDown + ScreenManager not working as expected