PyQt Tips and Tricks
(add link) |
(→Tips and Tricks: move content from PyQt instant startup screen trick) |
||
Line 91: | Line 91: | ||
</source> | </source> | ||
[http://talk.maemo.org/showpost.php?p=714507&postcount=1] | [http://talk.maemo.org/showpost.php?p=714507&postcount=1] | ||
+ | |||
+ | === Instant startup screen === | ||
+ | |||
+ | As most of us already know most PyQt4 apps are taking quite sometimes at start up and most of the time users are getting initial black screen while it's loading, this is just not only the case with Python apps but all the apps on [[Nokia N900|N900]]. Nokia has used a trick to snapshot of apps start up screen and load image instantly when the app icon is pressed, which give users an impression that apps was started up instantly but in reality they were not. (modest, note, sketch, control panel, application manager, pdfviewer, worldclock, xterm, and calculator are using this trick). So why don't we apply the same trick on our apps to make it look fast at startup? Well, Most of us don't even know the trick is there and some debates whether this is a good idea or not, but I for one rather see a false startup image than a black screen. So below is the instruction to make your Python Qt app fly at startup on N900 device. | ||
+ | |||
+ | '''There are three basic things to make it work:''' | ||
+ | |||
+ | <ol> | ||
+ | <li> | ||
+ | A D-Bus service file (name whatever e.g. yourapp.service) in "<code>/usr/share/dbus-1/services/</code>" folder even though yourapp is not using D-Bus related service at all and it should look like this format in <code>yourapp.service</code>. | ||
+ | <pre> | ||
+ | [D-BUS Service] | ||
+ | Name=what.ever.yourapp | ||
+ | Exec=/opt/yourapp/main.py | ||
+ | </pre> | ||
+ | </li> | ||
+ | <li> | ||
+ | Adding "<code>X-Osso-Service=what.ever.yourapp</code>" in your yourapp.desktop file in "<code>/usr/share/applications/hildon/</code>" folder, something like this. | ||
+ | <pre> | ||
+ | [Desktop Entry] | ||
+ | Encoding=UTF-8 | ||
+ | Version=1.0 | ||
+ | Type=Application | ||
+ | Terminal=false | ||
+ | Name=YourApp | ||
+ | Icon=YourApp | ||
+ | Exec=/opt/yourapp/main.py | ||
+ | X-Osso-Service=what.ever.yourapp | ||
+ | </pre> | ||
+ | </li> | ||
+ | <li> | ||
+ | Lastly, A function code in your app to take a snapshot of your app startup screen when it starts up the first time or when it exits whichever your prefer. Below is an basic example of PyQt4 app code which take a snapshot of the screen at start up if it's not already exist and save it into '"<code>/home/user/.cache/launch/what.ever.yourapp.pvr</code>" (p.s. the file name has to match with your <code>X-Osso-Service</code>, and D-BUS Service Name plus .pvr extension). | ||
+ | <source lang="python"> | ||
+ | #!/usr/bin/env python2.5 | ||
+ | # -*- coding: utf-8 -*- | ||
+ | |||
+ | from PyQt4.QtGui import * | ||
+ | from PyQt4.QtCore import * | ||
+ | |||
+ | import osso # only needed for osso_initialize | ||
+ | |||
+ | class MainWindow(QMainWindow): | ||
+ | def __init__(self, parent = None): | ||
+ | self.dbus_service_name = "what.ever.yourapp" #must be the same as your X-osso-service in yourapp.desktop file | ||
+ | """ | ||
+ | self.osso_c = osso.Context(self.dbus_service_name, "0.0.1", False) | ||
+ | is only needed if your app is not launched from run-standalone.sh, | ||
+ | Otherwise, your app will be terminated after ~ 2mins running | ||
+ | don't ask me why :) | ||
+ | """ | ||
+ | self.osso_c = osso.Context(self.dbus_service_name, "0.0.1", False) | ||
+ | |||
+ | QMainWindow.__init__(self, parent) | ||
+ | |||
+ | self.centralwidget = QWidget(self) | ||
+ | self.setCentralWidget(self.centralwidget) | ||
+ | |||
+ | """ | ||
+ | the rest of your code here.... | ||
+ | """ | ||
+ | |||
+ | def takeScreenShot(self): | ||
+ | pvr = "/home/user/.cache/launch/%s.pvr" % self.dbus_service_name | ||
+ | if not QFile.exists(pvr): # skipped if it's already there | ||
+ | QPixmap.grabWidget(self.centralwidget).save(pvr, 'png') # tell it to grab only your self.centralwidget screen, which is just window screen without the menu status bar on top. | ||
+ | |||
+ | if __name__ == '__main__': | ||
+ | import sys | ||
+ | app = QApplication(sys.argv) | ||
+ | app.setApplicationName("YourAppName") | ||
+ | YourApp = MainWindow() | ||
+ | YourApp.show() | ||
+ | YourApp.takeScreenShot() # run this after the YourApp.show() | ||
+ | sys.exit(app.exec_()) | ||
+ | </source> | ||
+ | </li> | ||
+ | </ol> | ||
== Example Applications == | == Example Applications == |
Latest revision as of 09:44, 31 May 2011
With MeeGo there is a lot of interest in developing applications in Qt rather than GTK+. This page spawned from a discussion thread.
Contents |
[edit] Tips and Tricks
[edit] Qt-Specific Performance Considerations
Riverbank claims[1] that decorating slots with pyqtSlot is faster and reduces memory.
Example:
@QtCore.pyqtSlot(int) @QtCore.pyqtSlot('QString') def valueChanged(self, value): """ Two slots will be defined in the QMetaObject. """
Notes:
- Unverified for PyQt
- Unsure if this applies to PySide
[edit] Identifying the Sender of a Signal
There are two main approaches for this. The first is to wrap the slot with a lambda to pass in any additional arguments:
def notifyMe(foo) print foo f = QPushButton("Click me!") x = lambda: notifyMe("they call him tim") self.connect(f, SIGNAL("clicked()"), x)
The second and more general solution is to use the QSignalMapper[3]
[edit] Learning to use Qt CSS
Much like web development, CSS is also available within Qt. There's a list of examples that can be tried out just using Qt Designer. If you're using custom CSS, using palette() in your CSS definitions will allow you to use colors that the theme already knows about. As an example, this is an easy way to get the default blue color on your buttons when you want that color.
Depending on your needs, there's a number of built-in icons that are available for use.[4]
Style sheets and the native QMaemo5Style
do not mix very well, since Maemo 5 lays out and draws some widgets very differently (e.g. QRadioButton
). We recommend using local style sheets on specific widgets only, instead of setting one via QApplication::setStyleSheet()
.[5]
[edit] QString and Python
I've had some Unicode-related issues when moving strings between Python datatypes and Qt datatypes. Somehow, the Unicode stuff would get lost. So, it's likely easiest (At least when you're new to Python and Qt) to use QString
and QStringList
than their pure Python counterparts.
If you're not bringing in data from anywhere else, this isn't that big of a deal, but if you're interacting with data from any other source, Unicode is a pretty likely scenario these days.[6]
Just to note that, in my experience, locales fail to work with PyQt - QTextCodec.codecForLocale()
does not return the correct codec, which also means QString
's toLocal8Bit()
and fromLocal8Bit()
fail.
The Python locale stuff works fine, but then you have the joy of converting between QString
and Python strings (which works perfectly when it does it implicitly, but an explicit conversion seems to go via ASCII unless you specifically tell it to use UTF-8 instead). [7]
[edit] Qt and Threads
There are multiple options for preemptive threading and cooperative threading. The big question is QThread
or threading.Thread
. This has been discussed on the PyQt mailing list and SO
[edit] QThread
It is discouraged to inherit from QThread
and to use moveToThread
on the QThread
.[8]. A C++ example of how to use these is shown in a producer/consumer style application can be found on the trolltech blog.
Note: The "You're Doing it Wrong" post claims run now has a default implementation to call exec_. As of python-qt 4.7 on Ubuntu 10.04 epage is not seeing this and had to create a class that inherits from QThread and provides that behavior.
[edit] import threading
It appears QThread
initializes some TLS for data essential for thread safe signals and slots. Also when creating cross-thread signals it queues the signal emission rather than doing it immediately. These present problems when wanting to use the native Python Threading in a Qt application.[9][10]
If the Python thread would not interact directly with Qt then using Python threads is fine without any consideration.
Sometimes you must use Python threads with Qt like when porting applications. One solution to this is to pump messages into a queue and have an QTimer
poll the queue periodically for tasks to process in the UI thread.[11]
[edit] QTimer
Spread the work across multiple callbacks to allow the UI to remain responsive. See multiple QTimer types. If you're not relying on third parties (anything that will block the interpreter - whether it be waiting on hardware or a call via XMLRPC) QTimer
s are probably a good choice.
As an example, I wrote an app that would play a series of .jpg images as a movie in a player. While using a thread would work, using a QTimer
worked great as well, since there was no waiting for hardware.
[edit] Stacked Windows
class showWindow(QtGui.QMainWindow): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_main_window() self.ui.setupUi(self) try: self.setAttribute(Qt.WA_Maemo5StackedWindow) except: pass
[edit] Instant startup screen
As most of us already know most PyQt4 apps are taking quite sometimes at start up and most of the time users are getting initial black screen while it's loading, this is just not only the case with Python apps but all the apps on N900. Nokia has used a trick to snapshot of apps start up screen and load image instantly when the app icon is pressed, which give users an impression that apps was started up instantly but in reality they were not. (modest, note, sketch, control panel, application manager, pdfviewer, worldclock, xterm, and calculator are using this trick). So why don't we apply the same trick on our apps to make it look fast at startup? Well, Most of us don't even know the trick is there and some debates whether this is a good idea or not, but I for one rather see a false startup image than a black screen. So below is the instruction to make your Python Qt app fly at startup on N900 device.
There are three basic things to make it work:
-
A D-Bus service file (name whatever e.g. yourapp.service) in "
/usr/share/dbus-1/services/
" folder even though yourapp is not using D-Bus related service at all and it should look like this format inyourapp.service
.[D-BUS Service] Name=what.ever.yourapp Exec=/opt/yourapp/main.py
-
Adding "
X-Osso-Service=what.ever.yourapp
" in your yourapp.desktop file in "/usr/share/applications/hildon/
" folder, something like this.[Desktop Entry] Encoding=UTF-8 Version=1.0 Type=Application Terminal=false Name=YourApp Icon=YourApp Exec=/opt/yourapp/main.py X-Osso-Service=what.ever.yourapp
-
Lastly, A function code in your app to take a snapshot of your app startup screen when it starts up the first time or when it exits whichever your prefer. Below is an basic example of PyQt4 app code which take a snapshot of the screen at start up if it's not already exist and save it into '"
/home/user/.cache/launch/what.ever.yourapp.pvr
" (p.s. the file name has to match with yourX-Osso-Service
, and D-BUS Service Name plus .pvr extension).#!/usr/bin/env python2.5 # -*- coding: utf-8 -*- from PyQt4.QtGui import * from PyQt4.QtCore import * import osso # only needed for osso_initialize class MainWindow(QMainWindow): def __init__(self, parent = None): self.dbus_service_name = "what.ever.yourapp" #must be the same as your X-osso-service in yourapp.desktop file """ self.osso_c = osso.Context(self.dbus_service_name, "0.0.1", False) is only needed if your app is not launched from run-standalone.sh, Otherwise, your app will be terminated after ~ 2mins running don't ask me why :) """ self.osso_c = osso.Context(self.dbus_service_name, "0.0.1", False) QMainWindow.__init__(self, parent) self.centralwidget = QWidget(self) self.setCentralWidget(self.centralwidget) """ the rest of your code here.... """ def takeScreenShot(self): pvr = "/home/user/.cache/launch/%s.pvr" % self.dbus_service_name if not QFile.exists(pvr): # skipped if it's already there QPixmap.grabWidget(self.centralwidget).save(pvr, 'png') # tell it to grab only your self.centralwidget screen, which is just window screen without the menu status bar on top. if __name__ == '__main__': import sys app = QApplication(sys.argv) app.setApplicationName("YourAppName") YourApp = MainWindow() YourApp.show() YourApp.takeScreenShot() # run this after the YourApp.show() sys.exit(app.exec_())
[edit] Example Applications
This is an overview of some of the PyQt applications available for Maemo. For some of the apps, there's a brief outline of unique functions they might have that you might want for your app.
The official PyQt bindings come with Python-based examples for many things. There's also examples for PyQt on Maemo 5 with Python examples, as well.
If you want to learn to become better in Python and Qt, reading code is one of the better ways to do it.
- ncalc
- nclock
- maesynth
- Uses QGraphicsView to create a non-standard looking GUI
- Also plays sounds when keys are pressed
- maelophone
- healthcheck
- Gives examples of interacting with much of the hardware on the N900
- qlister
- Gonvert (1.0+)
- WifiEye
- MaeGirls
- pyPianobar
- Threading and Qt
- pyRadio
- Threading and Qt
[edit] Web Resources
- Maemo Version of the Qt Documentation
- Maemo-specific platform notes
- PyQt4 Demo's and Documentation Packages
- This page was last modified on 31 May 2011, at 09:44.
- This page has been accessed 68,043 times.