I'm trying to make a simple app to monitor some IO And have a push button screen. I'm getting started on the framework of how the screen would work using kivy as my gui development. I'm wanting to do a lot of label updating based on variables, so the desire to do that has led me into using a .kv file and kivy properties. Admittedly, I know very little about these things it's just what I know from googling and trying to figure out what I need to do.
Regardless, I'm trying to do some basic framework to get started with a .kv file and though my main.py compiles, it does not display any buttons. I'm familiar with adding buttons to a layout done through the python language but I'm looking to do it more in the kivy style. Please help!
Here is my main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import StringProperty
import random
class YourWidget(Widget):
random_number = StringProperty()
def __init__(self, **kwargs):
super(YourWidget, self).__init__(**kwargs)
self.random_number = str(random.randint(1, 100))
def change_text(self):
self.random_number = str(random.randint(1, 100))
class YourApp(App):
def build(self):
return YourWidget()
if __name__ == '__main__':
YourApp().run()
and here is my your.kv
#:kivy 1.9.2
<YourWidget>:
BoxLayout:
size: root.size
orientation: 'horizontal'
spacing: 10
padding: 10
Button:
id: btn1
text: "Change Text"
on_release: root.change_text()
size_hint: .3, .3
Button:
id: btn2
text: "Exit App"
on_release: App.get_running_app().stop()
size_hint: .3, .3
Label:
id: lbl1
Font_size: 70
text:root.random_number
size_hint: .3, .3
Let me know anything you could assist with. I'm very new to python and extremely new to packages like kivy. Thank you! Another note is currently I am developing this on a monitor and will port to a touch screen later.
Rename your.kv to yourwidget.kv
when drawing graphics on the canvas the class or Widget name of your game must be reflected also in the kivy file. s
Related
I want to write a simple desktop application on Ubuntu and I thought that an easy way was to use Qt with QML as GUI and Python as the language for the logic, since I am somewhat familiar with Python.
Now I am trying for hours to somehow connect the GUI and the logic, but it is not working.
I managed the connection QML --> Python but not the other way around. I have Python classes which represent my data model and I added JSON encode and decode functions. So for now there is no SQL database involved. But maybe a direct connection between QML view and some database would make things easier?
So now some code.
QML --> Python
The QML file:
ApplicationWindow {
// main window
id: mainWindow
title: qsTr("Test")
width: 640
height: 480
signal tmsPrint(string text)
Page {
id: mainView
ColumnLayout {
id: mainLayout
Button {
text: qsTr("Say Hello!")
onClicked: tmsPrint("Hello!")
}
}
}
}
Then I have my slots.py:
from PySide2.QtCore import Slot
def connect_slots(win):
win.tmsPrint.connect(say_hello)
#Slot(str)
def say_hello(text):
print(text)
And finally my main.py:
import sys
from controller.slots import connect_slots
from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
if __name__ == '__main__':
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.load('view/main.qml')
win = engine.rootObjects()[0]
connect_slots(win)
# show the window
win.show()
sys.exit(app.exec_())
This works fine and I can print "Hello!". But is this the best way to do it or is it better to create a class with slots and use setContextProperty to be able to call them directly without adding additional signals?
Python --> QML
I cannot get this done. I tried different approaches, but none worked and I also don't know which one is the best to use. What I want to do is for example show a list of objects and offer means to manipulate data in the application etc.
include Javascript:
I added an additional file application.js with a function just to print something, but it could probably be used to set the context of a text field etc.
Then I tried to use QMetaObject and invokeMethod, but just got errors with wrong arguments etc.
Does this approach make any sense? Actually I don't know any javascript, so if it is not necessary, I would rather not use it.
ViewModel approach
I created a file viewmodel.py
from PySide2.QtCore import QStringListModel
class ListModel(QStringListModel):
def __init__(self):
self.textlines = ['hi', 'ho']
super().__init__()
And in the main.py I added:
model = ListModel()
engine.rootContext().setContextProperty('myModel', model)
and the ListView looks like this:
ListView {
width: 180; height: 200
model: myModel
delegate: Text {
text: model.textlines
}
}
I get an error "myModel is not defined", but I guess that it can't work anyway, since delegates only take one element and not a list.
Is this approach a good one? and if yes, how do I make it work?
Is there a totally different approach to manipulate data in a QML view?
I appreciate your help!
I know the Qt documentation but I am not happy with it. So maybe I am missing something. But PyQt seems to be way more popular than PySide2 (at least google searches seem to indicate that) and PySide references often use PySide1 or not the QML QtQuick way of doing things...
Your question has many aspects so I will try to be detailed in my answer and also this answer will be continuously updated because this type of questions are often asked but they are solutions for a specific case so I am going to take the liberty of giving it a general approach and be specific in the possible scenarios.
QML to Python:
Your method works because the type conversion in python is dynamic, in C++ it does not happen. It works for small tasks but it is not maintainable, the logic must be separated from the view so it should not be dependent. To be concrete, let's say that the printed text will be taken by the logic to perform some processing, then if you modify the name of the signal, or if the data does not depend on ApplicationWindow but on another element, etc. then you will have to change a lot connection code.
The recommended as you indicate is to create a class that is responsible for mapping the data you need your logic and embed it in QML, so if you change something in the view you just change the connection:
Example:
main.py
import sys
from PySide2.QtCore import QObject, Signal, Property, QUrl
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
class Backend(QObject):
textChanged = Signal(str)
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.m_text = ""
#Property(str, notify=textChanged)
def text(self):
return self.m_text
#text.setter
def setText(self, text):
if self.m_text == text:
return
self.m_text = text
self.textChanged.emit(self.m_text)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
backend = Backend()
backend.textChanged.connect(lambda text: print(text))
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("backend", backend)
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
Column{
TextField{
id: tf
text: "Hello"
}
Button {
text: qsTr("Click Me")
onClicked: backend.text = tf.text
}
}
}
Now if you want the text to be provided by another element you just have to change the line: onClicked: backend.text = tf.text.
Python to QML:
I can not tell you what you did wrong with this method because you do not show any code, but I do indicate the disadvantages. The main disadvantage is that to use this method you must have access to the method and for that there are 2 possibilities, the first one is that it is a rootObjects as it is shown in your first example or searching through the objectName, but it happens that you initially look for the object, you get it and this is removed from QML, for example the Pages of a StackView are created and deleted every time you change pages so this method would not be correct.
The second method for me is the correct one but you have not used it correctly, unlike the QtWidgets that focus on the row and the column in QML the roles are used. First let's implement your code correctly.
First textlines is not accessible from QML since it is not a qproperty. As I said you must access through the roles, to see the roles of a model you can print the result of roleNames():
model = QStringListModel()
model.setStringList(["hi", "ho"])
print(model.roleNames())
output:
{
0: PySide2.QtCore.QByteArray('display'),
1: PySide2.QtCore.QByteArray('decoration'),
2: PySide2.QtCore.QByteArray('edit'),
3: PySide2.QtCore.QByteArray('toolTip'),
4: PySide2.QtCore.QByteArray('statusTip'),
5: PySide2.QtCore.QByteArray('whatsThis')
}
In the case that you want to obtain the text you must use the role Qt::DisplayRole, whose numerical value according to the docs is:
Qt::DisplayRole 0 The key data to be rendered in the form of text. (QString)
so in QML you should use model.display(or only display). so the correct code is as follows:
main.py
import sys
from PySide2.QtCore import QUrl, QStringListModel
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
model = QStringListModel()
model.setStringList(["hi", "ho"])
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("myModel", model)
engine.load(QUrl.fromLocalFile('main.qml'))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
main.qml
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
ListView{
model: myModel
anchors.fill: parent
delegate: Text { text: model.display }
}
}
If you want it to be editable you must use the model.display = foo:
import QtQuick 2.10
import QtQuick.Controls 2.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Test")
width: 640
height: 480
visible: true
ListView{
model: myModel
anchors.fill: parent
delegate:
Column{
Text{
text: model.display
}
TextField{
onTextChanged: {
model.display = text
}
}
}
}
}
There are many other methods to interact with Python/C++ with QML but the best methods involve embedding the objects created in Python/C++ through setContextProperty.
As you indicate the docs of PySide2 is not much, it is being implemented and you can see it through the following link. What exists most are many examples of PyQt5 so I recommend you understand what are the equivalences between both and make a translation, this translation is not hard since they are minimal changes.
I am trying to make my first app in kivy so i copied a basic button code.
class TestApp(App):
def build(self):
btn = Button(text="Press me")
return btn
if name == 'main':
TestApp().run()
however the button doesnt seem to be pressed when i click it with my mouse.
any suggestions how to fix this?
thanks
Try this example.
from kivy.app import App
from kivy.uix.button import Button
class MyButton(Button):
def on_release(self):
print("Button pressed!")
class MyApp(App):
def build(self):
return MyButton(text="Press me!")
MyApp().run()
This should work with python 2 and 3, and Kivy installed. Nothing extra needed.
If this does not work for you, I would try to reinstall Kivy.
I'm writing a simple tool menu for Maya, and I'd like to stick it to the border of model panel (perspective).
from PySide import QtCore, QtGui
from maya import OpenMayaUI as omui
from shiboken import wrapInstance
class TestWidget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent = self.getMayaWindow())
self.setWindowFlags(QtCore.Qt.Tool | QtCore.Qt.FramelessWindowHint)
self.setFixedSize(100, 100)
panelPtr = omui.MQtUtil.findControl('modelPanel4')
panel = wrapInstance(long(panelPtr), QtGui.QWidget)
position = panel.mapToGlobal(panel.pos())
self.move(position.x(), position.y() + panel.geometry().height() / 2 - self.geometry().height() / 2)
mainLayout = QtGui.QVBoxLayout(self)
button = QtGui.QPushButton('CLOSE')
button.setFixedSize(80, 80)
button.clicked.connect(self.deleteLater)
mainLayout.addWidget(button)
def getMayaWindow(self):
omui.MQtUtil.mainWindow()
ptr = omui.MQtUtil.mainWindow()
return wrapInstance(long(ptr), QtGui.QWidget)
w = TestWidget()
w.show()
The main widget is positioned exactly where I want when it is created (horizontally on the left side of model panel, vertically - in the middle of model panel).
I need to reposition it accordingly when the model panel is resized, but model panel does not emit resized() signal. I'd appreciate any advise.
I've been trying many things to get this working yesterday. I did some additionnal researches today and came to this topic: cgsociety: Creating a floating button inside the viewport
In case of broken link, this is one of the answer:
You can use geometry but there are some issues with triggering
commands based on selection and the undo queue. If you want to go that
route, I would suggest looking into zooHud and zooTriggers (Part of
the zooToolbox)
If you are wanting actual GUI control parented to the viewport, mel
only offers hudslider, hudbutton, and headsUpMessage.
You can also use PyQt and parent in your own custom widgets/layouts or
whatever you want using something like this:
import maya.OpenMayaUI as apiUI import sip
from PyQt4 import QtGui
view = apiUI.M3dView()
apiUI.M3dView.getM3dViewFromModelPanel('modelPanel4', view)
viewWidget = sip.wrapinstance(long(view.widget()), QtCore.QObject)
global myBtn
myBtn = QtGui.QPushButton(viewWidget)
myBtn.setText('testing!')
myBtn.move(100, 100) #Relative to top-left corner of viewport myBtn.show()
You can do anything a full qt widget can do with that, so it's
extremely flexible. but it would require having PyQt installed, which
can be a barrier depending on your tools distribution.
I did a mix of this answer and your code:
from PySide import QtCore, QtGui
from maya import OpenMayaUI as omui
from shiboken import wrapInstance
class CustomQWidget(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
mainLayout = QtGui.QVBoxLayout(self)
closeButton = QtGui.QPushButton('CLOSE')
closeButton.setFixedSize(80, 40)
closeButton.clicked.connect(self.deleteLater)
helloButton = QtGui.QPushButton('HELLO')
helloButton.setFixedSize(80, 40)
helloButton.clicked.connect(self.printHello)
#Trying to fix glitchy background / Doesn't work, why?
#Is it because layouts don't have background?
p = self.palette()
p.setColor(self.backgroundRole(), QtCore.Qt.red)
self.setPalette(p)
self.setAttribute(QtCore.Qt.WA_StyledBackground, True)
##############################
mainLayout.addWidget(closeButton)
mainLayout.addWidget(helloButton)
def printHello(self):
print "Hello"
view = omui.M3dView()
omui.M3dView.getM3dViewFromModelPanel('modelPanel4', view) #Given the name of a model panel,
#get the M3dView used by that panel. If this fails, then a panel with the given name could not be located.
viewWidget = wrapInstance(long(view.widget()), QtGui.QWidget)
position = viewWidget.mapToGlobal(viewWidget.pos())
w = CustomQWidget(viewWidget)
w.move(0, viewWidget.geometry().height() / 2 - 100 / 2) #Relative to middle-left corner of viewport
w.show()
One of the issue I have it that the background of the widget is glitched:
If anyone knows why and how to fix it, I'll edit my answer with pleasure.
Else, when running this script from Maya's script editor, the widget follows the panel when it is resized.
I did fix such a problem, but not using Python/PyQt.
The problem itself is, that your Qt Widget is there. I have not found a way to make it not paint its background.
My solution was different: I derived from a Qt Layout, pushed all my widgets into that layout and used MQtUtil to get the QWidget of that modelPanel's modelEditor to attach the "real Qt layout" to it.
Heavy caveat that may make Python not suited: Maya doesn't expect "non-Maya" Layouts to be bound to "real-Maya" Widgets like modelEditors. So you need to listen to QEvents and find out when to destroy your layout, so Maya doesn't crash trying.
set autofillbackground True to fix your background painting issue
Background story
So I recently decided that I should try out Qt. I started making a QtQuick Apllication. In my designer view I have one button and a mouse area.
What I want to do:
When I click the button, I want to display a message box with some text (like "Hello World").
My Question
How can I do that ?
Additional info
I tried googling it, i tried following this answer. But still nothing.
I know how to program in .Net (C# and VB), i have some knowage in C/C++, but Qt seems to hard for me
How about this:
import QtQuick 2.0
import QtQuick.Controls 1.0
import QtQuick.Dialogs 1.1
Rectangle {
width: 360
height: 360
MessageDialog {
id: msg
title: "Title"
text: "Button pressed"
onAccepted: visible = false
}
Button {
text: "press me"
onClicked: msg.visible = true
}
}
And if you prefer to have the dialog dynamically instantiated with arbitrary properties rather than have it "hardcoded", follow the first snippet from this answer. You can also set properties in createQmlObject() and instead of hiding the dialog just use destroy() to delete it.
You have to use signals and slots in order to trigger an event. You could use a QMessageBox that pops up to display Hello world.
I have to create one screen in Qt in which I have to show a remote having lots of buttons in it and when user clicks some button on actual remote, corresponding button in the image get highlighted. So what I have done is, I have used QLabel and set the remote image as background image and then I have put small rectangular label for each button and filled them with semi transparent color and when user click button in actual remote label color changes, but by using this method lot of labels are getting used making code looking inefficient, so I was thinking of drawing on QLabel (which has a remote as background image) over buttons.
Can anybody suggest me, which API of Qt should I use, and how to follow up on this?
I believe QGraphics is the correct route for a completely custom graphical interface, but if you want to try something that doesn't require you to change too much of your existing approach, you can do a widget with a custom paint event:
This is written in PyQt but you can easily translate to Qt
from PyQt4 import QtCore, QtGui
class LabelButton(QtGui.QWidget):
clicked = QtCore.pyqtSignal()
def __init__(self, labelStr, pixStr, parent=None):
super(LabelButton, self).__init__(parent)
self.label = labelStr
self.pix = QtGui.QPixmap(pixStr)
def paintEvent(self, event):
super(LabelButton, self).paintEvent(event)
rect = event.rect()
painter = QtGui.QPainter(self)
painter.drawPixmap(rect, self.pix)
pos = (rect.bottomLeft()+rect.bottomRight()) / 2
pos.setY(pos.y()-10)
painter.drawText(pos, self.label)
painter.end()
def mousePressEvent(self, event):
event.accept()
self.clicked.emit()
def handleClick():
print "CLICK"
if __name__ == "__main__":
app = QtGui.QApplication([])
widget = LabelButton("A Text Label", "myImage.png")
widget.resize(600,400)
widget.show()
widget.raise_()
widget.clicked.connect(handleClick)
app.exec_()
This is a rough example. You can get more fine tuned with the drawing of the text. This widget takes a label string, and a picture path, and will paint the picture as the background, and the text as a label. You can do any number of things with this custom widget in both the paint event, and with custom signals and events.
I have used this code to Draw over Image in Label:
Image is loaded in Ui and the Code is as follows In paintevent
void ColorTab::paintEvent(QPaintEvent *e){
ui->lbl_capture_img->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
ui->Lbl_color_tab_WG->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
Cap_Image = QImage(ui->lbl_capture_img->pixmap()->toImage());
Lbl_Image = QImage(ui->Lbl_color_tab_WG->pixmap()->toImage());
QPainter painter_Lbl(&Lbl_Image);
QPainter painter_Cap(&Cap_Image);
QPen pen(Qt::white, 5, Qt::DotLine, Qt::RoundCap, Qt::RoundJoin);
painter_Lbl.setPen(pen);
painter_Cap.setPen(pen);
painter_Lbl.drawPolygon(blinkRect_Lbl);
painter_Cap.drawPolygon(blinkRect_Cap);
ui->lbl_capture_img->setPixmap(QPixmap::fromImage(Cap_Image));
ui->Lbl_color_tab_WG->setPixmap(QPixmap::fromImage(Lbl_Image));
painter_Cap.end();
painter_Lbl.end();
}