I am trying to implement an html style SelectBox widget in kivy using the kivy.uix.DropDown.
In the actual application I want to use it like this using kvlang:
SelectBox:
id: appeui
label: "Select an application"
SelectOption:
label: 'Option 1'
value: 1
SelectOption:
label: 'Option 2'
value: 2
SelectOption:
label: 'Option 3'
value: 3
To implement this widget, I have defined the SelectBox and SelectOption below. In the constructor of the SelectBox I am checking properties on the SelectBox, that I am expecting to be set in kvlang. One is the label which is then used as the label for the button. And I am also checking the children of the SelectBox and moving all of them (of type SelecOption) to the DropDown. The problem I am encountering is that in the constructor of SelectBox, there is no label argument, and no children yet either.
I think the SelectBox instantiation already happens before the kvlang stuff is read. So during instantiation the attributes defined in kvlang for SelectBox and also its children aren't known yet.
Is there any other function that gets called after the widget is actually built by kvlang? Or any other way I can actually act upon the way the widget was defined in kvlang?
from kivy.graphics import Color, Rectangle
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
from kivy.uix.label import Label
from kivy.uix.widget import Widget
class SelectOption(Label):
def __init__(self,
*args,
label='option',
value=None,
background_color=Color(rgba=(1, 1, 1, 1)),
**kwargs):
super().__init__(*args, **kwargs)
self.label = label
self.value = value
self.background_color = background_color
self.bind(on_touch_down=self.select)
with self.canvas.before:
Color(self.background_color)
Rectangle(pos=self.pos, size=self.size)
def select(self, *args):
self.parent.select(self.value)
class SelectBox(Widget):
def __init__(self, label='Select an option', *args, **kwargs):
super().__init__(*args, **kwargs)
self.dropdown = DropDown()
self.value = None
self.button = Button(text=label)
self.add_widget(self.button)
self.button.id = 'dropdown_label'
self.button.size_hint = (None, None)
self.button.height = '32dp'
self.button.bind(on_release=self.openDropDown)
self.dropdown.bind(on_select=self.set_value)
for child in self.children:
if isinstance(child, SelectOption):
self.dropdown.add_widget(child)
self.remove_widget(child)
def openDropDown(self, *args):
self.dropdown.open(self.button)
def set_value(self, instance, value):
self.ids.dropdown_label.text = value
Ok, I am getting somewhere. The issue here is to just not use the constructor, but use events instead. You can define properties on the SelectBox and use events to intercept changes in that property. When the property is defined in kvlang, this will trigger the event. So in my case I could do this:
class SelectBox(StackLayout, EventDispatcher):
label = StringProperty('Select an option')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.dropdown = DropDown()
self.value = None
self.button = Button(text=self.label)
self.button.id = 'dropdown_label'
self.add_widget(self.button)
self.button.bind(on_release=self.openDropDown)
self.dropdown.bind(on_select=self.set_value)
def on_label(self, instance, value):
print("Label changed")
self.button.text = value
def add_widget(self, child):
print(f"Adding widget {child}")
if isinstance(child, SelectOption):
self.dropdown.add_widget(child)
else:
super().add_widget(child)
def openDropDown(self, *args):
print("Opening dropdown")
self.dropdown.open(self.button)
def set_value(self, instance, value):
self.ids.dropdown_label.text = value
The on_label event gets called as soon as the label property is set in kvlang, and then I just change the button text.
For the child widgets, I am doing something similar by intercepting add_widget. If its a SelectOption, then add it to the dropdown instead of adding it to the SelectBox.
There are still issues with this code, but they are not directly related to this question.
Related
#This class is to be used with other classes of widgets, not with the class of kivy.app.
class TempatureFloatLayout(FloatLayout):
tempature = NumericProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.update, 1)
self.btn = Button()
self.btn.bind(on_press=self.do_something)
self.add_widget(self.btn)
def do_something(self, self_button):
pass
def update(self, dt):
self.btn.size = self.size # I can only resize and reposition the button here.
self.btn.pos = self.pos # pos and size are assigned with the pos and size
# values of the widget used
... the other codes
class TempatureFloatLayout(FloatLayout) can be used successfully in other classes. The code works correctly as it is. But, When every update, the position and size of the button is to be resized and repositioned. This doesn't feel right to me. How to bind a widget that is used and a button that is used in a different class. Where did I do wrong or is this usage correct?
I have a Label and a button that I define in the init of my class. In the init I bind my button to a method that should change the label. However the label does not update on button press even tho the variable does.
Why does my variable change but my label text stays the same even tho the text is an ObjectProperty?
class ReviewLayout(BoxLayout):
Price = Price()
textover = ObjectProperty(None)
ordered_products = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.get_order()
l = Label(text = str(self.textover))
self.add_widget(l)
b = Button(text= 'button')
b.bind(on_press=lambda x: self.get_order())
self.add_widget(b)
def get_order(self):
ordered_products = self.ordered_products
ordered_products.append("%s")
print("this shall be present", ordered_products)
self.textover = ordered_products
When you declare your label you set its value to self.textover value but when self.textover value changes it doesn't update the label.
You need to change the value of your label by storing it as a class property and updating it whenever you want.
Just refer to this Update label's text when pressing a button in Kivy for Python
class ReviewLayout(BoxLayout):
Price = Price()
textover = ObjectProperty(None)
ordered_products = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
# declare label variable as a class property (in self)
self.label = Label(text = str(self.textover))
self.add_widget(self.label)
self.button = Button(text= 'button')
self.add_widget(self.button)
self.button.bind(on_press=lambda x: self.get_order())
def get_order(self):
ordered_products = self.ordered_products
ordered_products.append("%s")
print("this shall be present", ordered_products)
self.textover = ordered_products
# change class variable text property to be the new textover content
self.label.text = str(self.textover)
I think an easier solution is to use kv to allow it to do the updates for you automatically, like this:
Builder.load_string('''
<ReviewLayout>:
Label:
text: str(root.textover)
Button:
text: 'button'
on_press: root.get_order()
''')
class ReviewLayout(BoxLayout):
Price = Price()
textover = ListProperty() # note change to ListProperty
def get_order(self):
self.textover.append("%s")
print("this shall be present", self.textover)
last day i came across on very irritate behave my program.
Mabye at first i show screenshot, describe error, and finnaly shows code.
As you can see, Coin is a button which contains a few sub-buttons. I generate these widgets in loop and dynamically add to layer.
But it works correctly only in last iterating.
Please look now at code.
Here is a code "Coin Button: class.
Note this, that for example: button apx, Refresh, H, DH and D is a member of one class
class SubButtonRefreshCoinData(Button):
def __init__(self, coin_name, **kwargs):
super(SubButtonRefreshCoinData, self).__init__(**kwargs)
self.CoinName = coin_name
self.text = "Refresh"
def on_press(self):
PopupNotImplementedItem().open()
class SubButtonCoinName(Button):
def __init__(self, **kwargs):
super(SubButtonCoinName, self).__init__(**kwargs)
self.text = r'[color=ff3333]{}[/color]'.format(kwargs["text"])
self.markup = True
self.font_size='20sp'
class SubButtonGoToCoinHistory(Button):
def __init__(self, coin_name, **kwargs):
super(SubButtonGoToCoinHistory, self).__init__(**kwargs)
self.CoinName = coin_name
self.text = "H"
def on_press(self):
subprocess.Popen(f'py HistoryWindow.py {self.CoinName}', shell=True)
class SubButtonDeleteCoin(Button):
def __init__(self, coin_name, **kwargs):
super(SubButtonDeleteCoin, self).__init__(**kwargs)
self.CoinName = coin_name
self.text = "D"
def on_press(self):
PopupNotImplementedItem().open()
class SubButtonDeleteCoinHistory(Button):
def __init__(self, coin_name, **kwargs):
super(SubButtonDeleteCoinHistory, self).__init__(**kwargs)
self.CoinName = coin_name
self.text = "DH"
print("sdfecvsdcdfwreafsq3456tedcqr4536wtger34r5cedwt4g5aced erf34")
def on_press(self):
PopupNotImplementedItem().open()
Now, please take a look on the Builder class these pieces:
class CoinButton(FloatLayout):
def __init__(self, coin_name, **kwargs):
super(CoinButton, self).__init__(**kwargs)
self.CoinName = coin_name
topHalfLayout = BoxLayout(pos_hint={"top":1}, size_hint = [1,0.49], orientation = "horizontal")
topHalfLayout.add_widget(SubButtonCoinName(text=str(self.CoinName)))
topHalfLayout.add_widget(SubButtonRefreshCoinData(self.CoinName))
self.add_widget(topHalfLayout)
downHalfLayout = BoxLayout(pos_hint={"down":1}, size_hint = [1,0.49], orientation = "horizontal")
downHalfLayout.add_widget(SubButtonGoToCoinHistory(self.CoinName))
downHalfLayout.add_widget(SubButtonDeleteCoinHistory(self.CoinName))
downHalfLayout.add_widget(SubButtonDeleteCoin(self.CoinName))
self.add_widget(downHalfLayout)
As you can see, everything seems be correct, but only ONE picece of class is visible.
In class SubButtonDeleteCoinHistory i tired primitive debug this problem to see did this code is already run. As i saw in console, text was printed 3 times, that is correct value.
I am not really sure what is happening here, but I take a shot. I believe that every time a CoinButton is initialized, the bottom button were added with respect to the FloatLayout which and using the pos_hint: down push the button off the kivy app window. But for the life of me, when changing the down value to various ints and floats, there was no change. The best approach was to use pos_hint 'y' since we know that the height of the button is 0.49 the top can start at 0.5 upto 0.99 and the bottom can start at 0 upto 0.49.
topHalfLayout = BoxLayout(pos_hint={"y":0.5},size_hint = [1,0.49], orientation = "horizontal")
......
downHalfLayout = BoxLayout(pos_hint={"y":0},size_hint = [1,0.49], orientation = "horizontal")
Another better approach that I used was to create another BoxLayout which holds the top and bottom layouts, allowing the FloatLayout to add the widget as one thing. It provides better spacing in my opinion. 'outside[ [top/bottom] ]outside'
outside = BoxLayout(pos_hint={"top":1},size_hint = [1,2*0.49], orientation = "vertical")
......
topHalfLayout = BoxLayout(size_hint = [1,0.49], orientation = "horizontal")
......
outside.add_widget(topHalfLayout)
......
downHalfLayout = BoxLayout(size_hint = [1,0.49], orientation = "horizontal")
......
outside.add_widget(downHalfLayout)
self.add_widget(outside)
I have a QTabWidget in PyQt which provides the possibility to tear off tabs and the re-attach them when they are closed. It works well except that newly attached tabs aren't immediately showing. A blank widget is showing and the widget is shown only after the current tab has been changed and then changed back.
I've searched StackOverflow and the answers to similar problems have pointed out that the added widget's show()-method needs to be called after the new tab is added. I tried that but the newly added tab is still not showing.
Slimmed down example code:
from PyQt4 import QtGui, QtCore
class DetachableTabWidget(QtGui.QTabWidget):
""" Subclass of QTabWidget which provides the ability to detach
tabs and making floating windows from them which can be reattached.
"""
def __init__(self, *args, **kwargs):
super(DetachableTabWidget, self).__init__(*args, **kwargs)
self.setTabBar(_DetachableTabBar())
def detach_tab(self, i):
""" Make floating window of tab.
:param i: index of tab to detach
"""
teared_widget = self.widget(i)
widget_name = self.tabText(i)
# Shift index to the left and remove tab.
self.setCurrentIndex(self.currentIndex() - 1 if self.currentIndex() > 0 else 0)
self.removeTab(i)
# Store widgets window-flags and close event.
teared_widget._flags = teared_widget.windowFlags()
teared_widget._close = teared_widget.closeEvent
# Make stand-alone window.
teared_widget.setWindowFlags(QtCore.Qt.Window)
teared_widget.show()
# Redirect windows close-event into reattachment.
teared_widget.closeEvent = lambda event: self.attach_tab(teared_widget, widget_name)
def attach_tab(self, widget, name):
""" Attach widget when receiving signal from child-window.
:param widget: :class:`QtGui.QWidget`
:param name: name of attached widget
"""
widget.setWindowFlags(widget._flags)
widget.closeEvent = widget._close
self.addTab(widget, name)
self.setCurrentWidget(widget)
self.currentWidget().show()
class _DetachableTabBar(QtGui.QTabBar):
def __init__(self, *args, **kwargs):
super(_DetachableTabBar, self).__init__(*args, **kwargs)
self._start_drag_pos = None
self._has_dragged = False
self.setMovable(True)
def mousePressEvent(self, event):
# Keep track of where drag-starts.
self._start_drag_pos = event.globalPos()
super(_DetachableTabBar, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
# If tab is already detached, do nothing.
if self._has_dragged:
return
# Detach-tab if drag in y-direction is large enough.
if abs((self._start_drag_pos - event.globalPos()).y()) >= QtGui.QApplication.startDragDistance()*8:
self._has_dragged = True
self.parent().detach_tab(self.currentIndex())
def mouseReleaseEvent(self, event):
self._has_dragged = False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
widget = DetachableTabWidget()
widget.addTab(QtGui.QLabel('Tab 1'), 'tab 1')
widget.addTab(QtGui.QLabel('Tab 2'), 'tab 2')
window.setCentralWidget(widget)
window.show()
sys.exit(app.exec_())
It seems than when you accept the QCloseEvent (default behavior) the widget is hidden at the end of your closeEvent handler. But your call to show() occurs before the end of the handler. The solution is to ignore the QCloseEvent.
...
teared_widget.closeEvent = lambda event: self.attach_tab(teared_widget, widget_name, event)
def attach_tab(self, widget, name, event):
""" Attach widget when receiving signal from child-window.
:param widget: :class:`QtGui.QWidget`
:param name: name of attached widget
:param event: close Event
"""
widget.setWindowFlags(widget._flags)
widget.closeEvent = widget._close
self.addTab(widget, name)
self.setCurrentWidget(widget)
event.ignore()
i've been trying for some time to wrap the text from a list and i don't know how to wrap the text of a simple Label.
I have a list of strings, and i would like to show it with a ListView inside a Popup, and wrapped
class SomeClass(Screen):
def __init__(self, **kwargs):
super(SomeClass, self).__init__(**kwargs)
# TextInput + ListView
...
# Popup
lv_adapter = SimpleListAdapter(data=string_list,cls=Label)
lv = ListView(adapter=lv_adapter)
self.popup = Popup(title='A very large title ..... ....... .... ....',
content=lv,size_hint=[.9,.8])
def some_function(self, *args):
if <some_condition>:
self.popup.open()
Image of the extended Popup
http://oi60.tinypic.com/2cyh5j8.jpg
Image of the shrunken Popup
http://oi61.tinypic.com/2hg4r4m.jpg
#
SOLUTION:
I've found an alternative to wrap text inside a ListView, using a Label inside a BoxLayout, here is the code:
class SomeClass(Screen):
def poplb_update(self, *args):
self.poplb.text_size = self.popup.size
def some_function(self):
self.popup.open()
def __init__(self, **kwargs):
super(SomeClass, self).__init__(**kwargs)
# Some code
. . .
# Popup
self.popup = Popup(title=popupTitle,size_hint=[.9,.6])
self.popbox = BoxLayout()
self.poplb = Label(text=stringPopup,font_size=25,strip=True,
text_size=self.popup.size)
self.popup.bind(size=self.poplb_update)
self.popbox.add_widget(self.poplb)
self.popup.content = self.popbox