How to place a button on a loading animation with Kivy? - button

I'm really new to Kivy, i'm trying to make my first app, but i don't really understand how to play with elements and classes...
I'm trying to put a button to stop the sound but it will just stop the animation....
Here's the code, i think i don't code it properly ! :(
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.animation import Animation
from kivy.properties import NumericProperty
from kivy.core.audio import SoundLoader
from kivy.uix.button import Button
from functools import partial
from kivy.uix.boxlayout import BoxLayout
Builder.load_string('''
<App_container>:
canvas.before:
PushMatrix
Rotate:
angle: root.angle
axis: 0, 0, 1
origin: root.center
canvas.after:
PopMatrix
Image:
id: img_anim
source: 'logo.png'
size_hint: 0,0
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
''')
class App_container(FloatLayout):
angle = NumericProperty(0)
def __init__(self, **kwargs):
#Anim
super(App_container, self).__init__(**kwargs)
anim = Animation(angle = 360, duration=2)
anim2 = Animation(size_hint=(2,2), duration=2)
anim.start(self)
anim2.start(self.ids["img_anim"])
#Son
self.sound = SoundLoader.load('zik.wav')
self.sound.loop = True
self.sound.play()
#boutonzik
btn = Button(text ="Push Me !")
self.add_widget(btn)
btn.bind(on_press=partial(self.foo, btn))
def foo(self, instance, *args):
self.sound.volume=0
class TestApp(App):
def build(self):
return App_container()
if __name__ == "__main__":
app = TestApp()
app.run()

In order for the Button to not spin, it must not be in the spinning Layout. To do this, you can add another FloatLayout in your App_container, and only spin that FloatLayout. The following modification of your code does that:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.animation import Animation
from kivy.core.audio import SoundLoader
Builder.load_string('''
<App_container>:
FloatLayout:
# move the angle property into this FloatLayout
angle: 0.0
id: rotate_this
canvas.before:
PushMatrix
Rotate:
angle: self.angle
axis: 0, 0, 1
origin: root.center
canvas.after:
PopMatrix
Image:
id: img_anim
source: 'logo.png'
size_hint: 0,0
pos_hint: {'center_x': 0.5, 'center_y': 0.5}
Button:
text: 'Push Me'
on_press: root.foo(self)
size_hint: 0.1,0.1
pos_hint: {'center_x':0.5, 'center_y':0.5}
''')
class App_container(FloatLayout):
def __init__(self, **kwargs):
#Anim
super(App_container, self).__init__(**kwargs)
anim = Animation(angle = 360, duration=2)
anim2 = Animation(size_hint=(2,2), duration=2)
# rotate the FloatLayout with id "rotate_this"
anim.start(self.ids["rotate_this"])
# animate the "img_anim"
anim2.start(self.ids["img_anim"])
#Son
self.sound = SoundLoader.load('zik.wav')
self.sound.loop = True
self.sound.play()
def foo(self, instance, *args):
self.sound.volume=0
class TestApp(App):
def build(self):
return App_container()
if __name__ == "__main__":
app = TestApp()
app.run()
So the FloatLayout spins, but the Button does not, since it is not inside the spinning FloatLayout.

Related

Qt text alignment in QCheckBox

I am looking for a way to align text in a QCheckBox to both the right and left side. I cannot seems to find any ways to modify just the alignment of the text and not the checkbox itself.
I don't know if you can access the label associated with the checkbox or not, but if you cannot, a hack would be to set the checkbox label with an empty string and use another QLabel where you can use setAlignment(Qt::AlignJustify) to adjust your text to both the right and left side.
But then I don't know if you consider this modify the checkbox itself and not just the alignement.
I solved it using a trick.
Create a QCheckBox without putting any text in it.
And create a QLable so that you can center the text and click it.
Then it's possible.
Below is the example code.
You can refer to it.
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class MyApp(QWidget):
###### Define StyleSheet ######
DEFAULT_COLOR = {
'border': '#000000',
'hover': '#29adff'
}
CP_DEFAULT_STYLE = '''
QCheckBox::indicator:hover {{
border: 1px solid {hover};
background: white;
}}
QCheckBox::indicator {{
border: 1px solid {border};
background: white;
}}
'''
CP_DEFAULT_STYLE_SET_VALUE = CP_DEFAULT_STYLE.format(**DEFAULT_COLOR)
################################
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.cb = QCheckBox(self)
cp_label = QLabel('Click\nHere Me!!', self)
self.cb.setStyleSheet(self.CP_DEFAULT_STYLE_SET_VALUE)
cp_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
cp_label.setGeometry(75+30, 50+15, 60, 30)
self.cb.move(75+90, 50+20)
self.cb.clicked.connect(self.cpClick)
cp_label.mousePressEvent = self.cpLabelClick
cp_label.leaveEvent = self.cpLabelLeave
cp_label.enterEvent = self.cpLabelEnter
self.setWindowTitle('QCheckBox')
self.setGeometry(300, 300, 300, 200)
self.show()
def cpClick(self):
if not self.cb.isChecked():
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE_SET_VALUE
)
else:
self.cb.setStyleSheet('')
def cpLabelClick(self, _):
self.cb.setStyleSheet('')
self.cb.setChecked(
not self.cb.isChecked()
)
def cpLabelLeave(self, _):
self.cb.setStyleSheet('')
def cpLabelEnter(self, _):
if not self.cb.isChecked():
setColer = self.DEFAULT_COLOR.copy()
setColer['border'] = self.DEFAULT_COLOR['hover']
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE.format(**setColer)
)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
sys.exit(app.exec_())
or
If you use Qt Designer,
After creating one Qwidget, make QCheckBox and QLabel inside
Bring the QLabel to the front and make the QCheckBox deselected.
And if you write it like the code below, it works perfectly!
import os
import sys
from PyQt5.uic import loadUi
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
def resource_path(*relative_Path_AND_File):
""" Get absolute path to resource, works for dev and for PyInstaller """
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = getattr(sys, '_MEIPASS', os.path.dirname(
os.path.abspath(__file__)))
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, '/'.join(relative_Path_AND_File))
class MyApp_Define:
cb: QCheckBox
cb_label: QLabel
class MyApp(QMainWindow, MyApp_Define):
###### Define StyleSheet ######
DEFAULT_COLOR = {
'border': '#000000',
'hover': '#29adff'
}
CP_DEFAULT_STYLE = '''
QCheckBox::indicator:hover {{
border: 1px solid {hover};
background: white;
}}
QCheckBox::indicator {{
border: 1px solid {border};
background: white;
}}
'''
CP_DEFAULT_STYLE_SET_VALUE = CP_DEFAULT_STYLE.format(**DEFAULT_COLOR)
################################
def __init__(self):
super().__init__()
loadUi(resource_path("TEST.ui"), self)
self.cb_label.installEventFilter(self)
self.cb_label.leaveEvent = self.cbLabelLeave
self.cb_label.mousePressEvent = self.cbLabelClick
def eventFilter(self, source: QObject, event: QEvent):
# 자동 종료 체크박스 안에 들어갈시
if (
source is self.cb_label and
event.type() == QEvent.Type.MouseMove and
not self.cb.isChecked()
):
self.cbLabelEnter()
return super().eventFilter(source, event)
def cbLabelClick(self, _):
self.cb.setStyleSheet('')
self.cb.setChecked(
not self.cb.isChecked()
)
if not self.cb.isChecked():
self.cbLabelEnter()
def cbLabelLeave(self, _):
self.cb.setStyleSheet('')
def cbLabelEnter(self, _=None):
if not self.cb.isChecked():
setColer = self.DEFAULT_COLOR.copy()
setColer['border'] = self.DEFAULT_COLOR['hover']
self.cb.setStyleSheet(
self.CP_DEFAULT_STYLE.format(**setColer)
)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyApp()
ex.show()
sys.exit(app.exec_())
The image below is an example of QtDesigner.

PyQt button click area (Non rectangular area)

I was working on a PySide interface for Maya and i was wondering if its possible to define a NON RECTANGULAR clickeable area for a button.
I tried using QPushButton and also extending a QLabel object to get button behavior but do you know if its possible to get a button containing a picture with alpha channel and use that alpha to define the click area for a button?
I'd appreciate a lot if you can guide me through how to solve this problem.
Thanks in advance.
I've tried this...
from PySide import QtCore
from PySide import QtGui
class QLabelButton(QtGui.QLabel):
def __init(self, parent):
QtGui.QLabel.__init__(self, parent)
def mousePressEvent(self, ev):
self.emit(QtCore.SIGNAL('clicked()'))
class CustomButton(QtGui.QWidget):
def __init__(self, parent=None, *args):
super(CustomButton, self).__init__(parent)
self.setMinimumSize(300, 350)
self.setMaximumSize(300, 350)
picture = __file__.replace('qbtn.py', '') + 'mario.png'
self.button = QLabelButton(self)
self.button.setPixmap(QtGui.QPixmap(picture))
self.button.setScaledContents(True)
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.onClick)
def onClick(self):
print('Button was clicked')
if __name__ == '__main__':
app = QApplication(sys.argv)
win = CustomButton()
win.show()
app.exec_()
sys.exit()
mario.png
This is the final code i get to solve my above question...
from PySide import QtCore
from PySide import QtGui
class QLabelButton(QtGui.QLabel):
def __init(self, parent):
QtGui.QLabel.__init__(self, parent)
def mousePressEvent(self, ev):
self.emit(QtCore.SIGNAL('clicked()'))
class CustomButton(QtGui.QWidget):
def __init__(self, parent=None, *args):
super(CustomButton, self).__init__(parent)
self.setMinimumSize(300, 350)
self.setMaximumSize(300, 350)
pixmap = QtGui.QPixmap('D:\mario.png')
self.button = QLabelButton(self)
self.button.setPixmap(pixmap)
self.button.setScaledContents(True)
self.button.setMask(pixmap.mask()) # THIS DOES THE MAGIC
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.onClick)
def onClick(self):
print('Button was clicked')
You can do this by catching the press/release events and checking the position of the click with the value of the pixel in the image to decide if the widget should emit a click or not.
class CustomButton(QWidget):
def __init__(self, parent, image):
super(CustomButton, self).__init__(parent)
self.image = image
def sizeHint(self):
return self.image.size()
def mouseReleaseEvent(self, event):
# Position of click within the button
pos = event.pos()
# Assuming button is the same exact size as image
# get the pixel value of the click point.
pixel = self.image.alphaChannel().pixel(pos)
if pixel:
# Good click, pass the event along, will trigger a clicked signal
super(CustomButton, self).mouseReleaseEvent(event)
else:
# Bad click, ignore the event, no click signal
event.ignore()

Can't add widget from event call

I want to add a button during a event, but for some reason it doesn't work.
If i run this code, the TempFunc function will run during creation and a button will be created.
from PySide.QtGui import *
from PySide.QtCore import *
import sys
import math
class Example(QWidget):
def __init__(self, val):
super(Example,self).__init__()
self.scrollAreaConstraint = QLabel()
self.scrollAreaConstraint.setFixedSize(QSize(400,400))
self.scroll = QScrollArea()
self.scroll.setWidget(self.scrollAreaConstraint)
self.scroll.setWidgetResizable(True)
layout = QVBoxLayout(self)
layout.addWidget(self.scroll)
self.CountSlider = QSlider()
self.CountSlider.setOrientation(Qt.Orientation(1))
layout.addWidget(self.CountSlider)
self.TempFunc() #THIS WILL CREATE THE BUTTON!!
def TempFunc(self):
print "SLIDER PRESSED!!! NOW I WILL ADD A BUTTON"
QPushButton(self.scrollAreaConstraint)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Example(25)
window.setGeometry(500, 500, 500, 400)
window.show()
sys.exit(app.exec_())
However running this, pressing the slider will cause the same TempFunc function to run, but the button is not created.
from PySide.QtGui import *
from PySide.QtCore import *
import sys
import math
class Example(QWidget):
def __init__(self, val):
super(Example,self).__init__()
self.scrollAreaConstraint = QLabel()
self.scrollAreaConstraint.setFixedSize(QSize(400,400))
self.scroll = QScrollArea()
self.scroll.setWidget(self.scrollAreaConstraint)
self.scroll.setWidgetResizable(True)
layout = QVBoxLayout(self)
layout.addWidget(self.scroll)
self.CountSlider = QSlider()
self.CountSlider.setOrientation(Qt.Orientation(1))
layout.addWidget(self.CountSlider)
#self.TempFunc() #<----Disabled!!
self.CountSlider.sliderPressed.connect(self.TempFunc)
def TempFunc(self):
print "SLIDER PRESSED!!! NOW I WILL ADD A BUTTON"
QPushButton(self.scrollAreaConstraint)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Example(25)
window.setGeometry(500, 500, 500, 400)
window.show()
sys.exit(app.exec_())
Why is the button not created when not being called directly from the "init"?
The button is created, but then your code does nothing useful with it, which explains why it "doesn't work".
I'm guessing that, since you make scrollAreaConstraint the parent of these buttons, you are expecting them to appear inside the scroll-area. But scrollAreaConstraint is a QLabel, which cannot act as a container for other widgets.
So make scrollAreaConstraint a QWidget, give it a layout, and add the buttons to that layout:
self.scrollAreaConstraint = QWidget()
self.scrollAreaConstraint.setLayout(QVBoxLayout())
...
def TempFunc(self):
button = QPushButton(self.scrollAreaConstraint)
self.scrollAreaConstraint.layout().addWidget(button)
I eventually found that QT will set the .visible flag to false by default.
So just setting it to true makes the buttons visible in both cases.
Still not sure how and why it works when calling it from the init function as they are set to visible(false) aswell.

QTableWidget set the Background of header Cell

I know how to set the Horizontal Headers background color, but how can i set the Headers Background Color for a specified Row? In this example, say i want to have the Header Row with the Label "3" to be red?
The above screenshots code:
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtGui import *
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 250, 150)
self.rigTable = QTableWidget(1, 3, self)
style = "::section {""background-color: lightblue; }"
self.rigTable.horizontalHeader().setStyleSheet(style)
self.rigTable.setShowGrid(False)
self.rigTable.setCellWidget(0, 0, QLabel("A"))
self.rigTable.setCellWidget(0, 1, QLabel("B"))
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You can use QTableWidgetItem class:
header = QtGui.QTableWidgetItem("3")
header.setBackground(QtGui.QColor("red"))
self.rigTable.setHorizontalHeaderItem(2, header)

kivy: Global variable and ScreenManager

I'm using an input from a First Screen, store this input into a global variable CHOSEN_INPUT, then when I go to the Second Screen, this global variable is displayed there.
I've got the beginning but don't know where to go... Maybe with ObjectProperty but I don't know how to pass it from one class to another.
global_test.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager, NoTransition
from kivy.properties import ObjectProperty
#global variable
CHOSEN_INPUT = ''
class FirstScreen(Screen):
obj_input = ObjectProperty()
obj_label = ObjectProperty()
obj_okay = ObjectProperty()
def buttonClicked(self):
global CHOSEN_INPUT
print('RESULT :', self.obj_input.text)
self.obj_label.text = "You wrote : " + self.obj_input.text
CHOSEN_INPUT = self.obj_input.text
class SecondScreen(Screen):
pass
class FromFirstScreen(BoxLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
global CHOSEN_INPUT
self.orientation = 'vertical'
lbl = Label(Text=CHOSEN_INPUT)
self.add_widget(lbl)
class Global_VariableApp(App):
def build(self):
sm = ScreenManager(transition=NoTransition())
sm.add_widget(FirstScreen(name='firstcreen'))
sm.add_widget(SecondScreen(name='secondscreen'))
return sm
if __name__ == "__main__":
Global_VariableApp().run()
and :
global_variable.kv
<FirstScreen>:
obj_input: input_box
obj_label: label_box
obj_okay: okay_btn
BoxLayout:
orientation: 'vertical'
TextInput:
id: input_box
size_hint_y: None
height: "40dp"
multiline: False
Label:
id: label_box
Button:
id: okay_btn
text: "Okay"
on_press: root.buttonClicked()
Button:
text: "Go To Second Screen"
on_press: root.manager.current = 'secondscreen'
<SecondScreen>:
BoxLayout:
orientation: 'vertical'
Button:
text: "Input from First Screen :"
FromFirstScreen:
Button:
text: "Go To First Screen"
on_press: root.manager.current = 'firstcreen'
Found thanks to #mcastle's solution : Changing Kivy widget attribute from another widget
I've put inside the class SecondScreen an update_text function :
class SecondScreen(Screen):
obj_input1_box = ObjectProperty()
def update_text(self, label_text):
#print('label_text :', label_text)
self.obj_input1_box.text = label_text
and in the kv file, I've inserted a function: root.manager.get_screen('secondscreen').update_text(root.obj_label.text)
(and put an id for the label of text of SecondScreen which is updated) :
<FirstScreen>:
...
Button:
id: okay_btn
text: "Okay"
on_press: root.buttonClicked(); root.manager.get_screen('secondscreen').update_text(root.obj_label.text) ...
<SecondScreen>:
obj_input1_box: input1_box
...
Label:
id: input1_box
...

Resources