kivy zrobić klasę łączoną z dwóch widgetów

0

Witam
Wymęczyłem takie cósik. Wiem, że jest źle bo nie działa. Jak to zrobić poprawnie.
Label dodany do buttona dla uzyskania możliwości wykorzystania markup i unicode.

import kivy
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button

lay=BoxLayout(orientation='horizontal',size_hint=(1,.1),spacing=4)

class SwitchButton(Button,Label):

	def __init__(self):
		self.txt_on='  [color=00ff00]'+'\u25CF'+'[/color] '
		self.txt_of='  [color=ff0000]'+'\u25CB'+'[/color] '
		self.lbl=Label(halign="right",markup=True,pos=(2,-30),text=self.txt_on+"Nm.Ent.",font_name='DejaVuSansMono.ttf')
		self.btn=Button(size_hint=(None,.6),width=125)
		self.btn.bind(on_release=self.mSwitch)
		self.btn.add_widget(self.lbl)
		self.btn.bind(on_release=self.mSwitch)

		return None
	def mSwitch(self,*data):
		oLblWdg=data[0].children[0]
		sTxt=oLblWdg.text
		posSt=sTxt.find('=')+1
		sPhrase=sTxt[posSt:posSt+6]
		if sPhrase=='00ff00':
			oLblWdg.text=self.txt_of+"Nm.Ent."
		else:
			oLblWdg.text=self.txt_on+"Nm.Ent."
		return None

swBtn=SwitchButton()
lay.add_widget(swBtn)
runTouchApp(lay)

Taki błąd wyskakuje.
AttributeError: 'SwitchButton' object has no attribute '_disabled_count'

Pozdrawiam
Radosław Głębicki

0

Jak wywołujesz __init__ to na końcu musisz też wywołać __init__ rodzica, żeby poprawnie poustawiał sobie rzeczy:

class SwitchButton(Button,Label):
    def __init__(self, *args, **kwargs):
        # ...
        return super().__init__(*args, **kwargs

W każdym razie jednak klasa Button już dziedziczy po Label, więc nie musisz robić własnego przycisku i takie coś działa:

from kivy.uix.button import Button
from kivy.app import runTouchApp

runTouchApp(Button(text="[b]Hello [color=ff0000]world[/color][/b]", markup=True))

Co do Unicode, to domyślnie jest wspierany, tyle że domyślnie dostępne fonty mogą nie wyświetlać wszystkich znaków. Dla przykładu ten kod:

from kivy.uix.button import Button
from kivy.app import runTouchApp

runTouchApp(Button(text="ハロー・ワールド"))

Wyświetli krzaczory zamiast japońskich znaczków, jeśli jednak ściągnę sobie jakiś inny font obsługujący te znaki (uwaga na licencje) i użyję, to zadziała. Na przykład stąd ściągnąłem Sazanami Gothic, wrzuciłem w katalog roboczy i ładuję:

from kivy.uix.button import Button
from kivy.app import runTouchApp

runTouchApp(Button(text="ハロー・ワールド", font_name='sazanami-gothic.ttf'))
0

A czy jeśli samemu połączę te dwie klasy to będę mógł manipulować np. pozycją tekstu wewnątrz przycisku lub zmieniać align? Bo w oryginalym Buttonie się nie da, prawda?

Da się, Label (a więc i Button) ma property valign, halign i padding (również padding_x i padding_y). Trzeba tylko, jak mówi dokumentacja kontrolować text_size. Bez kv można to zrobić tak:

from kivy.uix.button import Button
from kivy.app import runTouchApp

button = Button(text="Hello world", valign='center', halign='left', padding_x=20)
button.bind(size=button.setter('text_size'))
runTouchApp(button)
0

A jeszcze takie coś męczę:

import kivy
from kivy.app import runTouchApp,stopTouchApp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button

lay=BoxLayout(orientation='horizontal',size_hint=(1,.1),spacing=4)

class SwitchButton(Button): # tu ten Button nie wiem czy potrzebny
	txt_on ='[color=00ff00]'+'\u25CF'+'[/color] '
	txt_off='[color=ff0000]'+'\u25CB'+'[/color] '

	def __init__(self):
		pass

	def __new__(self,text,ini_state):
		if ini_state=='on':
			self.text=__class__.txt_on+text
		elif ini_state=='off':
			self.text=__class__.txt_off+text
		return Button(text=self.text,font_name='DejaVuSansMono.ttf',markup=True)

	def mState(self):
		sTxt=self.text
		nPosSt=sTxt.find("=")
		if sTxt[nPosSt:nPosSt+6]=='00ff00': return 'on'
		else: return 'off'
		return None

	def mSwitch(self):
		sTxt=self.text
		nPosSt=sTxt.find('=')+1
		if sTxt[nPosSt:nPosSt+6]=='00ff00': self.text=__class__.txt_off+"nrm.Ent."
		else: self.text=__class__.txt_on+"nrm.Ent."
		return None

swBtnNrmEnt=SwitchButton('Nrm.Ent.','on') # fajnie sobie mogę zacząć ze stanem

#print(swBtnNrmEnt.mState()) # tu zgłasza, "AttributeError: 'Button' object has no attribute 'mState'". Rozumiem,
#                                                 ale myślałem, że dodaję swoją metodę, a tu jeszcze pisze, że atrybut.

lay.add_widget(swBtnNrmEnt)

runTouchApp(lay)

Generalnie to chciałem tak sobie po chyba partyzancku stworzyć mój własny przełączalny button. Na funkcjach mi działa, ale przy wielu takich buttonach to zaczyna się z funkcjami za dużo kombinowania. Więc sobie klasę wymyśliłem i utknąłem.

Radek

1

Coś takiego?

from kivy.uix.button import Button
from kivy.app import runTouchApp
from kivy.properties import BooleanProperty

class MyButton(Button):
    markup = True
    font_name = 'DejaVuSansMono.ttf'
    is_on = BooleanProperty(default=True)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._text = kwargs.get('text')
        self.bind(on_press=self._switch)
        self._set_color()

    def _set_color(self):
        color = '00ff00' if self.is_on else 'ff0000'
        self.text = f'[color={color}]\u25CB[/color] {self._text}'

    def _switch(self, instance):
        self.is_on = not self.is_on
        self._set_color()

runTouchApp(MyButton(text='Hello world', is_on=False))
0

Trochę przerobiłem na moje. Jest git.

class SwitchButton(Button):
	markup = True
	font_name = 'DejaVuSansMono.ttf'
	is_on = BooleanProperty(default=True)
	txt_on ='[color=00ff00]'+'\u25CF'+'[/color] ' # Dodane
	txt_off='[color=ff0000]'+'\u25CB'+'[/color] ' # Dodane


	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self._text = kwargs.get('text')
		self.bind(on_press=self._switch)
		self._set_color()

	def _set_color(self):
		sSw = self.txt_on if self.is_on else self.txt_off # Zmienione
		self.text = sSw+self._text # Zmienione

	def _switch(self, instance):
		self.is_on = not self.is_on
		self._set_color()

swBtn=SwitchButton(text='Nrm.Ent.', is_on=True)
runTouchApp(swBtn)

Wielkie dzięki. Miałem to funkcyjnie, ale trochę duże się zrobiło. Tak jest małe i zgrabne.
Informacje dla postronnych.
przez _switch można wymusić zmianę stanu.
czytając atrybut is_on można dowiedzieć się o aktualnym stanie.

Pozdrawiam
Radosław Głębicki

1

Jeszcze proszę o wytłumaczenie lub wskazanie definicji dla property i dispatcher. Jeśli dobrze rozumiem property to taka zmienna pod kontrolą Kivy. Czytam, że można narzucić granice, kontrolować typ i oczywiście odczytywać. Coś jeszcze ciekawego? Czy wszystkie zmienne wykorzystywane przez Kivy tworzyć jako property?

W dokumentacji jest to opisane, w każdym razie property to coś w rodzaju atrybutu klasy, który dodatkowo umożliwia wysyłanie eventów przy każdej zmianie, dzięki czemu można pod to podpinać wywołania zwrotne. W moim przykładzie trzymam is_on jako atrybut, a nie zwykłą zmienną klasy, dzięki czemu mogę używać na tym bind, przy każdej zmianie:

def callback(instance, value):
    print(f"Object {instance} changed is_on to {value}")

button = MyButton(text='Hello world', is_on=False)
button.bind(is_on=callback)
runTouchApp(button)

Mogę też taki callback zaimplementować jako metodę klasy o nazwie on_<nazwa property>:

class MyButton(Button):
    # ...
    def on_is_on(self, instance, value):
        print(f"Object {instance} changed is_on to {value}")

Wreszcie, mogę tę własność ustawić w konstruktorze klasy i korzystać z self.is_on, mimo iż nigdzie jawnie go nie przypisuję - framework robi to za mnie:

class DummyButton(Button):
    a = BooleanProperty(default=True)
    b = BooleanProperty(default=True)

    def on_press(self, *args):
        print(f'{self.a} {self.b}')

button = DummyButton(text="Dummy", a=True, b=False)
runTouchApp(button)

Nie muszę jawnie pisać self.a = True itd. w __init__.

Kiedy korzystać z property, a kiedy ze zwykłego atrybutu klasy - zależy co się potrzebuje i jak wygodnie. W moim przykładzie chodziło właśnie o to, by konstruktor obsługiwał przypisanie is_on.

1 użytkowników online, w tym zalogowanych: 0, gości: 1