How to add mulitple buttons to a Kivy Layout - python

I am trying to a make a music player using Kivy, which will just have two button; one for play and one for pause. When I trying to bind a function to the buttons, when I press the buttons, I get errors. Upon searching this very own site for this, there was a suggestion to use the partial function from the functools library. But even that didn't work. My code is below.
class Layout(FloatLayout):
def __init__(self, **kwargs):
super(Layout, self).__init__(**kwargs)
self.size = Window.size
self.play = Button(text="Play", size_hint=(0.25, 0.25), font_size=36, background_color=color,
pos=(self.size[0]*(3/8), self.size[1]*(4/10)) )
self.pause = Button(text="Pause", size_hint=(0.25, 0.25), font_size=36, background_color=color,
pos=(self.size[0]*(3/8), self.size[1]*(1/10)) )
self.play.bind(on_press=self.play)
self.pause.bind(on_press=self.pause)
self.add_widget(self.play)
self.add_widget(self.pause)
def play(self):
print("PLay")
def pause(self):
print("Pause")
This gave this error
AssertionError: <kivy.uix.button.Button object at 0x000001D1D4792CF0>
is not callable
and by using the partial function
self.play.bind(on_press=partial(self.play))
self.pause.bind(on_press=partial(self.pause))
I get served this
TypeError: the first argument must be callable

There are two errors in your code. the first one is that you have given the event handlers and the buttons the same name. So change one of those. Second, you need a second parameter to event handlers as it gets called with a parameter.
Here is your corrected code:
class Layout(FloatLayout):
def __init__(self, **kwargs):
super(Layout, self).__init__(**kwargs)
self.size = Window.size
self.play = Button(text="Play", size_hint=(0.25, 0.25), font_size=36,
pos=(self.size[0]*(3/8), self.size[1]*(4/10)) )
self.pause = Button(text="Pause", size_hint=(0.25, 0.25), font_size=36,
pos=(self.size[0]*(3/8), self.size[1]*(1/10)) )
self.play.bind(on_press=self.play1)
self.pause.bind(on_press=self.pause1)
self.add_widget(self.play)
self.add_widget(self.pause)
def play1(self, instance):
print("PLay")
def pause1(self, instance):
print("Pause")
If you don't want to add a parameter then you can use lambda function.
Something like this:
self.play.bind(on_press=lambda _:self.play1())
self.pause.bind(on_press=lambda _:self.pause1())
In this case, you can remove the extra parameter in the eventhandler.

Related

Script doesn't stop

This is the code
Entrypoint.py
def Data_For_MakePdf_Db(self):
mese = self.MakePdf_Strip_Month_select.currentText()
anno = self.MakePdf_Strip_Year_select.currentText()
MakeStrip(anno, mese)
return ### Breakpoint here
and MakeStrip.py
class SpecialStyledItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self._values = dict()
def add_text(self, text, row):
self._values[row] = text
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
row = index.row()
if row in self._values:
option.text = self._values[row]
option.displayAlignment = QtCore.Qt.AlignCenter
class MakeStrip(QtWidgets.QWidget):
def __init__(self, anno, mese):
super().__init__()
self.initUI(anno, mese)
def initUI(self, anno, mese):
self.title = "MAMbo - Strips di '%s' '%s' "%(mese, anno)
self.setWindowTitle(self.title)
self.setGeometry(50, 100, 900, 800)
self.callTable(anno, mese)
self.button = QtWidgets.QPushButton(self.tableWidget)
self.button.setGeometry(QtCore.QRect(440, 169, 70, 20))
self.button.setObjectName("button")
self.button.setText("close")
self.layout = QtWidgets.QHBoxLayout(self)
self.layout.addWidget(self.tableWidget)
self.show()
self.button.clicked.connect(self.on_click)
pippo = 0 ### Breakpoint here
def on_click(self):
print('Clicked')
# Functions.message_handler(message = "THIS WILL CLOSE THE APP")
return
def callTable(self, anno, mese):
# Create table
self.tableWidget = QtWidgets.QTableWidget()
self.tableWidget.move(100, 700)
self.tableWidget.setRowCount(33)
self.tableWidget.setColumnCount(3)
self.special_delegate = SpecialStyledItemDelegate()
self.tableWidget.setItemDelegate(self.special_delegate)
h_header = self.tableWidget.horizontalHeader()
h_header.hide()
for i in range(h_header.count()):
h_header.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeToContents)
v_header = self.tableWidget.verticalHeader()
v_header.hide()
v_header.setDefaultSectionSize(13)
self.tableWidget.setSpan(1, 0, 1, 3)
self.tableWidget.setSpan(0, 0, 1, 3)
...
pluto = 0
def on_click(self):
print('Clicked')
return
I use pycharm
Main problem
If I run the script I see for a fraction of second the result of Makescript even if there a push button
Alternative problem
If I debug it I need to put some breakpoints as shown in the scripts to see the result of the script
I know that the debugger keeps alive the connection but why do I need to put breakpoints in those positions to see the result?
The problem is in the following line:
MakeStrip(anno, mese)
What happens there is that you create an instance of MakeStrip which has absolutely no reference. The result is that it's created and immediately garbage collected as soon as its __init__ returns.
Imagine doing something like this:
def Data_For_MakePdf_Db(self):
list()
As soon as no reference to an object is left, python automatically destroys it. In the case above, you create an instance without any reference, so it gets immediately deleted.
Note that creating a local reference (a variable that exists only in the function) might be a solution, but since there is no locking, you'll have almost the same results, because the function will immediately returns, causing the instance to be destroyed as well:
def Data_For_MakePdf_Db(self):
strip = MakeStrip(anno, mese)
A possibility is to create a persistent reference, by making the object a member of the current instance:
def Data_For_MakePdf_Db(self):
self.strip = MakeStrip(anno, mese)
This has some, possibly unwanted, side effects: first of all, you could still switch between the current window to the new one (which will probably make things a bit confusing), and, most importantly, if you call Data_For_MakePdf_Db again while another MakeStrip window already exists, it will be probably destroyed and replaced by a new one.
A better and more consistent solution is to have MakeStrip a subclass of QDialog instead, and use its exec_(): it will make it a modal window and will return control only when the window is finally closed.
def Data_For_MakePdf_Db(self):
MakeStrip(self, anno, mese).exec_()
class MakeStrip(QtWidgets.QDialog):
def __init__(self, parent, anno, mese):
super().__init__(parent)
self.initUI(anno, mese)
def initUI(self, anno, mese):
# ...
self.button.clicked.connect(self.accept)
Note that in this specific case we can even create the dialog without the local or instance reference, thanks to the fact that exec_ creates an event loop that blocks the execution until the dialog is closed.
This is always a good idea, though, as you might need to know if the dialog was actually "accepted" or "rejected", or need access to some of the dialog's objects.
In that case, a local reference is required:
def Data_For_MakePdf_Db(self):
dialog = MakeStrip(<b>self</b>, anno, mese)
if dialog.exec_():
print(dialog.tableWidget.rowCount())
PS: capitalized names should always be used only for classes and constants, not for functions, so it should be data_for_MakePdf_Db; while you can name your function as you like, this standard convention is highly suggested, as it improves readability (it makes clear distinction between classes and functions). Read more on the official Style Guide for Python Code.

How to initialize attributes in widget class callable?

I have a custom toggle button (colored rectangle), when i press the button it show a border, if press again hide it.
How i can pass argument to this widget?
class ColorSwatch(ToggleButtonBehavior, Widget):
# Color Button
def __init__(self, **kwargs):
super(ColorSwatch, self).__init__(**kwargs)
#Defaults
self.up_border_color = self.border_color
self.down_border_color = self.background_color
self.border_color = self.down_border_color
def on_state(self, widget, value):
if value == 'down':
# On checked show border
self.border_color = self.up_border_color
else:
# On checked hide border
self.border_color = self.down_border_color
If i run this
ColorSwatch(
border_color=(1,1,1,1),
border_size=10,
background_color=(.32, 0.22, 0.55, 1),
)
i get the following error
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
You need to add properties of border_color, border_size, and background_color to your ColorSwatch:
class ColorSwatch(ToggleButtonBehavior, Widget):
border_size = NumericProperty(0)
border_color = ListProperty([])
background_color = ListProperty([])

Why do we get the 'TypeError' when running the following code(python, Kivy)?

I am a beginner in python and am trying out Kivy to make GUI.
To add a background to a layout, i am trying to follow the example provided in the official documentation.
(Refer this Please, if you need to look at the official documentation)
https://kivy.org/docs/guide/widgets.html#adding-a-background-to-a-layout
In my code below, update_bg(), the function is used to update the size and/or position of the background(a rectangle, drawn on the canvas) whenever its parent(the 'layout', Kivy Jargon) changes its position and/or size.
class ConversationBox(BoxLayout):
def __init__(self, **kwargs):
super(ConversationBox, self).__init__(**kwargs)
beside_message = BoxLayout(orientation='vertical')
whatever_said = Label(text='Someone said Something', size_hint=(None, None), size=(100, 60))
remove_button = Button(text='X', size_hint=(None, None), size=(30, 30))
log = Label(text='Log', size_hint=(None, None), size=(30, 30))
with self.canvas:
Color(0, 1, 0, 1)
self.background = Rectangle(pos_hint=(None, None), size_hint=(None, None), pos=self.pos, size=self.size)
self.bind(pos=self.update_bg, size=self.update_bg)
self.add_widget(whatever_said)
beside_message.add_widget(remove_button)
beside_message.add_widget(log)
self.add_widget(beside_message)
def update_bg(self): # <----------------This is where the problem is
self.background.pos = self.pos
self.background.size = self.size
class test(App):
def build(self):
return ConversationBox(orientation='horizontal')
test().run()
When you run this code, you get and error in the console, that is.
TypeError: update_bg() takes 1 positional argument but 3 were given
When you provide two additional arguments, say,
def update_bg(self, arbitrary_arg_1, arbitrary_arg_2):
You don't get any errors.
Why does this happen?
I have Zero Intuition.
The answer is in the docs:
bind()
[...]
In general, property callbacks are called with 2 arguments (the object
and the property’s new value) and event callbacks with one argument
(the object). The example above illustrates this.
[...]
As it reads, it sends us the object that has the change, in this case the ConversationBox object(self) and the new value of the property.
In general, you should use the following:
def update_bg(self, instance, value):
self.background.pos = self.pos
self.background.size = self.size

Synchronize two element values in PyQt5

I have a slider and a text box that contains an integer (is there a dedicated integer box?) in PyQt5 shown side by side.
I need these two values to be synchronized, and the way I am doing it right now is with a QtTimer and if statements detecting if one value has changed more recently than the other, and then updating the opposite element. I was told this was "hacky" and was wondering if there was a proper way to do this.
You can see the text box values and sliders that I need to synchronize in the clear areas of the image below.
The simple solution is to connect the valueChanged for each slider/number box to a slot which synchronises the values
self.slider1.valueChanged.connect(self.handleSlider1ValueChange)
self.numbox1.valueChanged.connect(self.handleNumbox1ValueChange)
#QtCore.pyqtSlot(int)
def handleSlider1ValueChange(self, value):
self.numbox1.setValue(value)
#QtCore.pyqtSlot(int)
def handleNumbox1ValueChange(self.value):
self.slider1.setValue(value)
A better solution is to define a custom slider class that handles everything internally. This way you only have to handle the synchronisation once.
from PyQt5 import QtCore, QtWidgets
class CustomSlider(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(CustomSlider, self).__init__(*args, **kwargs)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.slider.valueChanged.connect(self.handleSliderValueChange)
self.numbox = QtWidgets.QSpinBox()
self.numbox.valueChanged.connect(self.handleNumboxValueChange)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.numbox)
layout.addWidget(self.slider)
#QtCore.pyqtSlot(int)
def handleSliderValueChange(self, value):
self.numbox.setValue(value)
#QtCore.pyqtSlot(int)
def handleNumboxValueChange(self, value):
# Prevent values outside slider range
if value < self.slider.minimum():
self.numbox.setValue(self.slider.minimum())
elif value > self.slider.maximum():
self.numbox.setValue(self.slider.maximum())
self.slider.setValue(self.numbox.value())
app = QtWidgets.QApplication([])
slider1 = CustomSlider()
slider2 = CustomSlider()
window = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(window)
layout.addWidget(slider1)
layout.addWidget(slider2)
window.show()
app.exec_()
Edit: With regard to comments from ekhumoro, the above class can be simplified to
class CustomSlider(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(CustomSlider, self).__init__(*args, **kwargs)
self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
self.numbox = QtWidgets.QSpinBox()
self.numbox.setRange(self.slider.minimum(), self.slider.maximum())
self.slider.valueChanged.connect(self.numbox.setValue)
self.slider.rangeChanged.connect(self.numbox.setRange)
self.numbox.valueChanged.connect(self.slider.setValue)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(self.numbox)
layout.addWidget(self.slider)
You'll probably also want to mimic some of the QSlider methods to change the range and value. Note we don't need to explicitly set anything on self.numbox as the signal/slot connections made above take care of it.
#QtCore.pyqtSlot(int)
def setMinimum(self, minval):
self.slider.setMinimum(minval)
#QtCore.pyqtSlot(int)
def setMaximum(self, maxval):
self.slider.setMaximum(maxval)
#QtCore.pyqtSlot(int, int)
def setRange(self, minval, maxval):
self.slider.setRange(minval, maxval)
#QtCore.pyqtSlot(int)
def setValue(self, value):
self.slider.setValue(value)
You can just connect each of the sliders to the other one, straight-forward. I don't know the exact connection you want between the sliders, but it could look something like this.
max_player_slider.valueChanged.connect(self.slider1_fu)
npc_stream_slider.valueChanged.conenct(self.slider2_fu)
def slider1_fu(self):
# do stuff with the npc_stream_slider
def slider2_fu(self):
# do stuff with the max_player_slider
Edit: Here is a Tutorial on YouTube that might be helpful.

detecting click/touch in kivy textinput

I have a textinput that I want to focus on when the user clicks/touches on it. (Fairly standard!) It inherits from DragableObject (a user example in the kivy wiki) and GridLayout.
class DragableObject( Widget ):
def on_touch_down( self, touch ):
if self.collide_point( *touch.pos ):
touch.grab( self )
return True
def on_touch_up( self, touch ):
if touch.grab_current is self:
touch.ungrab( self )
return True
def on_touch_move( self, touch ):
if touch.grab_current is self:
self.pos = touch.x-self.width/2, touch.y-self.height/2
class MyWidget(DragableObject, GridLayout):
def __init__(self, **kwargs):
kwargs['orientation'] = 'lr-tb'
kwargs['spacing'] = 10
kwargs['size_hint'] = (None, None)
kwargs['size'] = (200, 80)
self.cols = 2
super(MyWidget, self).__init__(**kwargs)
with self.canvas:
self.rect = Rectangle(pos=self.pos, size=self.size)
with self.canvas.before:
Color(0, .5, 1, mode='rgb')
self.bind(pos=self.update_rect)
self.bind(size=self.update_rect)
self.add_widget(Label(text='t1'))
self.text1 = TextInput(multiline=False)
self.add_widget(self.text1)
self.add_widget(Label(text='t2'))
self.text2 = TextInput(multiline=False)
self.add_widget(self.text2)
# these bind's don't seem to work
self.text1.bind(on_touch_down=self.set_focus)
self.text1.bind(on_touch_up=self.set_focus)
self.text1.bind(on_touch_move=self.set_focus)
def set_focus(self):
print("pressed")
self.text1.focus = True
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
I have two problems.
a. The text input is unfocusable.
b. I can't get an event callback (such as on_touch_down) to work on the textinput widget.
Any ideas?
Short Answer
You can simple use an Scatter. It includes dragging, rotation and scaling functionality and you can deactivate any or all of them:
my_scatter = Scatter(do_rotation=False, do_scale=False)
None of the problems you described should happen inside a Scatter
Long Answer
Your problem is that you are overriding the on_touch_down, on_touch_move and on_touch_up of the parent.
Normally these methods will call the corresponding ones of the children. For example, when the on_touch_down method of a Widget instance is called, then the Widget instance is going to traverse its children, calling the on_touch_down method of each of then (If you are familiarized with recursion and tree structures, we are talking about a recursive traverse method - I think pre-order - Tree traversal).
This functionality is overridden in the DraggableObject class. You got to be sure to call the method of be base class with:
super(DraggableWidget, self).on_touch_down(touch)
Depending on what is the behaviour you are looking for the methods could look like:
(1) If you always want to call the children:
def on_touch_down( self, touch ):
if self.collide_point( *touch.pos ):
touch.grab( self )
return super(DraggableWidget, self).on_touch_down(touch)
(2) If you just want to call the children when there is no collide:
def on_touch_down( self, touch ):
if self.collide_point( *touch.pos ):
touch.grab( self )
return True # Don't call the on_touch_down of the Base Class
return super(DraggableWidget, self).on_touch_down(touch)
And there is more options!. The return value indicates if the event was or not handled by any children. You can, for example, do something like this:
def on_touch_down( self, touch ):
handled = super(DraggableWidget, self).on_touch_down(touch)
if not handled and self.collide_point( *touch.pos ):
touch.grab( self )
return True
return handled
In this case, you will avoid the Widget to be dragged when one of the children handle the event. It all depends on what you do.

Categories