A textfield and a button in a horizontal box layout the textfield taking most of the screen space button getting little
The problem is the MDTextField taking most of the width and the MDButton getting very little space at the right side. Tried size_hint_x = .5 in both l, not working, .7 .3 not working. 1:1 not working. Tried fixed wide for the buttons, not working. And also vertical spacing between the each row is too much high, not compact.
Using pydroid 3, kivymd2, android
The problem reproducible code is here:
from kivymd.app import MDApp from kivymd.uix.screen import MDScreen from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.appbar import MDTopAppBar, MDTopAppBarTitle from kivymd.uix.label import MDLabel from kivymd.toast import toast from kivymd.uix.button import MDButton, MDButtonIcon, MDButtonText from kivymd.uix.widget import MDWidget from kivymd.icon_definitions import md_icons from kivymd.uix.textfield import ( MDTextField, MDTextFieldHintText, MDTextFieldMaxLengthText ) from kivymd.uix.segmentedbutton import ( MDSegmentedButton, MDSegmentedButtonItem, MDSegmentButtonLabel ) from kivymd.uix.dialog import ( MDDialog, MDDialogHeadlineText, MDDialogSupportingText, MDDialogButtonContainer ) from kivy.uix.widget import Widget from kivy.core.window import Window from kivy.core.text import Label as CoreLabel from kivy.metrics import dp class TxtField(MDTextField): def __init__(self, ilen = 7, htxt="", shintx=0.7, **kwargs): super().__init__(**kwargs) self.mode = "outlined" #"filled" self.size_hint_x = .5 self.pos_hint={"center_x": .5} self.input_filter = lambda s, u: s if ( len(self.text) < ilen and s.isdigit() ) else "" if not htxt: htxt = "input:" self.add_widget( MDTextFieldHintText( text = htxt ) ) class mkbtn(MDButton): def __init__( self, text, style="elevated", # radius=[20,20,20,20], icon=None, func=lambda x: dummy_func(x), **kwargs): super().__init__(**kwargs) self.style = style self.radius = radius self.size_hint_x = None self.pos_hint={"center_x": .5} #padding=[5, 5, 5, 5] #self.adaptive_width=True #self.size_hint_min_x = 0.3 self.wide = dp(120) if icon: self.add_widget(MDButtonIcon(icon)) self.add_widget(MDButtonText(text=text)) self.bind(on_release=func) def dummy_func(x="", y=""): pass class MainScreen(MDScreen): def __init__(self, **kwargs): super().__init__(**kwargs) self.md_bg_color = [1, 1, 1, 1] #md_bg_color: self.theme_cls.secondaryContainerColor self.main_layout = MDBoxLayout(orientation="vertical") self.app_bar = MDTopAppBar(type="small") self.abtitle = MDTopAppBarTitle( text="WOS Squad Ratio (Bear/CJ)", halign="center" ) self.app_bar.add_widget(self.abtitle) self.content_layout = MDBoxLayout(orientation="vertical", padding=[5, 5, 5, 5], spacing=dp(10),) self.hlay1 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10)) self.bdctf = TxtField(htxt="Bear DC", ilen=6) self.prof_btn = mkbtn("Profiles", icon="triangle-small-down") self.hlay1.add_widget(self.bdctf) self.hlay1.add_widget(self.prof_btn) #self.hlay1.add_widget(MDWidget()) ### self.hlay2 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10),) self.cdctf = TxtField(htxt="CJ DC", ilen=6) self.aprof_btn = mkbtn("Add Profiles") self.hlay2.add_widget(self.cdctf) self.hlay2.add_widget(self.aprof_btn) ### self.hlay3 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10),) self.titf = TxtField(htxt="Total Infantry", ilen=7) self.cb_btn = mkbtn("Calc Bear") self.hlay3.add_widget(self.titf) self.hlay3.add_widget(self.cb_btn) ### self.hlay4 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10),) self.tltf = TxtField(htxt="Total Lancer", ilen=7) self.cc_btn = mkbtn("Calc CJ") self.hlay4.add_widget(self.tltf) self.hlay4.add_widget(self.cc_btn) ### self.hlay5 = MDBoxLayout(orientation="horizontal", padding=[5, 5, 5, 5], spacing=dp(10),) self.tmtf = TxtField(htxt="Total Lancer", ilen=7) self.sd_btn = mkbtn("Save Data") self.hlay5.add_widget(self.tmtf) self.hlay5.add_widget(self.sd_btn) ### self.tlbl = MDLabel(halign="left",role="large",padding=[5, 0, 0, 500],text = "") def cng_lbl(txt): self.tlbl.text = txt ### self.content_layout.add_widget(self.hlay1) self.content_layout.add_widget(self.hlay2) self.content_layout.add_widget(self.hlay3) self.content_layout.add_widget(self.hlay4) self.content_layout.add_widget(self.hlay5) self.content_layout.add_widget(MDWidget()) ### self.main_layout.add_widget(self.app_bar) self.main_layout.add_widget(self.content_layout) ### self.add_widget(self.main_layout) class MyApp(MDApp): def build(self): root = MainScreen() return root if __name__ == "__main__": MyApp().run()
I don't know if I resolve all problems but I describe what I found.
It seems MDButton works different than other widgets.
Here is original MDButton.__init__
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Clock.schedule_once(self.adjust_width, 0.2)
Clock.schedule_once(self.adjust_pos, 0.2)
It uses Clock to run self.adjust_width after 0.2 seconds to create button with minimal width - so it doesn't allow to use self.width at once in own __init__ because it will be replaced by width calculated in self.adjust_width
(by the way: as I remember __init__ can't calculate it directly because Kivy creates real objects later, sets values from files .kv, and after that code can change sizes - and this is why it has to use Clock to run code later)
I see two methods for this:
Clock to run own function which will change width little laterfrom kivy.clock import Clock
class mkbtn(MDButton):
def __init__(...):
# ... code ...
Clock.schedule_once(self.update_later, 0.2)
def update_later(self, args):
self.width = dp(120)
adjust_width with own function - empty code - and it will not calculate own widthclass mkbtn(MDButton):
def __init__(...):
# ... code ...
self.width = dp(120)
def adjust_width(self, args):
pass
Result on desktop Linux
I also centered text on button (but text+icon may need different method)
#from kivy.clock import Clock
# if you put this before class mkbtn then you can use `func=dummy_func`
def dummy_func(x="", y=""):
pass
class mkbtn(MDButton):
def __init__(
self,
text,
style="elevated", #
radius=[20, 20, 20, 20],
icon=None,
# func=lambda x: dummy_func(x),
func=dummy_func,
**kwargs
):
super().__init__(**kwargs)
self.style = style
self.radius = radius
self.size_hint_x = None
# self.adaptive_width=True
# self.size_hint_min_x = 0.3
# self.wide = dp(120)
self.width = dp(120)
if icon:
self.add_widget(MDButtonIcon(icon))
self.add_widget(MDButtonText(text=text))
self.bind(on_release=func)
# center text on button - has to be after `add_widget(MDButtonText(..>))`
self._button_text.pos_hint = {"center_x": 0.5, "center_y": 0.5}
# center button
# self.pos_hint = {"center_x": 0.5, "center_y": 0.5} # center Y
# Clock.schedule_once(self.update_later, 0.2)
# def update_later(self, args):
# self.width = dp(120)
def adjust_width(self, args):
pass
If in Button and TextField you use pos_hint = {"cente_y": 0.5} then both widgets will looks like in one line.
#from kivy.clock import Clock
class TxtField(MDTextField):
def __init__(self, ilen=7, htxt="", shintx=0.7, **kwargs):
super().__init__(**kwargs)
self.mode = "outlined" # "filled"
self.size_hint_x = 0.5
# center textfield in layout (Box)
self.pos_hint = {"center_x": 0.5, "center_y": 0.5} # center Y
self.input_filter = lambda s, u: (
s if (len(self.text) < ilen and s.isdigit()) else ""
)
if not htxt:
htxt = "input:"
self.add_widget(MDTextFieldHintText(text=htxt))
# if you put this before class mkbtn then you can use `func=dummy_func`
def dummy_func(x="", y=""):
pass
class mkbtn(MDButton):
def __init__(
self,
text,
style="elevated", #
radius=[20, 20, 20, 20],
icon=None,
# func=lambda x: dummy_func(x),
func=dummy_func,
**kwargs
):
super().__init__(**kwargs)
self.style = style
self.radius = radius
self.size_hint_x = None
# self.adaptive_width=True
# self.size_hint_min_x = 0.3
# self.wide = dp(120)
self.width = dp(120)
if icon:
self.add_widget(MDButtonIcon(icon))
self.add_widget(MDButtonText(text=text))
self.bind(on_release=func)
# center text on button - has to be after `add_widget(MDButtonText(..>))`
self._button_text.pos_hint = {"center_x": 0.5, "center_y": 0.5}
# center button in layout (Box)
self.pos_hint = {"center_x": 0.5, "center_y": 0.5} # center Y
# Clock.schedule_once(self.update_later, 0.2)
# def update_later(self, args):
# self.width = dp(120)
def adjust_width(self, args):
pass
If you add background color in MDBoxLayout then you see how it is centered
self.hlay5 = MDBoxLayout(
orientation="horizontal",
padding=[5, 5, 5, 5],
spacing=dp(10),
md_bg_color=[1, 0, 0, 1],
)
Before centering:
After centering:
To reduce height in rows I have to use size_hint_y=None in every hlay* and bind function which will calculates minimal height for every hlay*
self.hlay4.bind(minimum_height=self.hlay4.setter("height"))
class MainScreen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = [1, 1, 1, 1]
# md_bg_color: self.theme_cls.secondaryContainerColor
self.main_layout = MDBoxLayout(orientation="vertical")
self.app_bar = MDTopAppBar(type="small")
self.abtitle = MDTopAppBarTitle(
text="WOS Squad Ratio (Bear/CJ)", halign="center"
)
self.app_bar.add_widget(self.abtitle)
self.content_layout = MDBoxLayout(
orientation="vertical",
padding=[5, 5, 5, 5],
spacing=dp(10),
)
self.hlay1 = MDBoxLayout(
orientation="horizontal",
padding=[5, 5, 5, 5],
spacing=dp(10),
size_hint_y=None,
)
self.bdctf = TxtField(htxt="Bear DC", ilen=6)
self.prof_btn = mkbtn("Profiles", icon="triangle-small-down")
self.hlay1.add_widget(self.bdctf)
self.hlay1.add_widget(self.prof_btn)
self.hlay1.bind(minimum_height=self.hlay1.setter("height"))
###
self.hlay2 = MDBoxLayout(
orientation="horizontal",
padding=[5, 5, 5, 5],
spacing=dp(10),
size_hint_y=None,
)
self.cdctf = TxtField(htxt="CJ DC", ilen=6)
self.aprof_btn = mkbtn("Add Profiles")
self.hlay2.add_widget(self.cdctf)
self.hlay2.add_widget(self.aprof_btn)
self.hlay2.bind(minimum_height=self.hlay2.setter("height"))
###
self.hlay3 = MDBoxLayout(
orientation="horizontal",
padding=[5, 5, 5, 5],
spacing=dp(10),
size_hint_y=None,
)
self.titf = TxtField(htxt="Total Infantry", ilen=7)
self.cb_btn = mkbtn("Calc Bear")
self.hlay3.add_widget(self.titf)
self.hlay3.add_widget(self.cb_btn)
self.hlay3.bind(minimum_height=self.hlay3.setter("height"))
###
self.hlay4 = MDBoxLayout(
orientation="horizontal",
padding=[5, 5, 5, 5],
spacing=dp(10),
size_hint_y=None,
)
self.tltf = TxtField(htxt="Total Lancer", ilen=7)
self.cc_btn = mkbtn("Calc CJ")
self.hlay4.add_widget(self.tltf)
self.hlay4.add_widget(self.cc_btn)
self.hlay4.bind(minimum_height=self.hlay4.setter("height"))
###
self.hlay5 = MDBoxLayout(
orientation="horizontal",
padding=[5, 5, 5, 5],
spacing=dp(10),
md_bg_color=[1, 0, 0, 1],
size_hint_y=None,
)
self.tmtf = TxtField(htxt="Total Lancer", ilen=7)
self.sd_btn = mkbtn("Save Data")
self.hlay5.add_widget(self.tmtf)
self.hlay5.add_widget(self.sd_btn)
self.hlay5.bind(minimum_height=self.hlay5.setter("height"))
###
self.tlbl = MDLabel(
halign="left",
role="large",
padding=[5, 0, 0, 500],
text="",
)
def cng_lbl(txt):
self.tlbl.text = txt
###
self.content_layout.add_widget(self.hlay1)
self.content_layout.add_widget(self.hlay2)
self.content_layout.add_widget(self.hlay3)
self.content_layout.add_widget(self.hlay4)
self.content_layout.add_widget(self.hlay5)
self.content_layout.add_widget(
MDWidget(
# size_hint_y=1, # doesn't need this
md_bg_color=[0, 1, 0, 1],
)
)
###
self.main_layout.add_widget(self.app_bar)
self.main_layout.add_widget(self.content_layout)
###
self.add_widget(self.main_layout)
It keeps sizes after resizing window.
but problem is when window is too small to keep all elements
furas